├── Advanced Algorithm ├── Backtracking │ └── queen.py └── Branch and Bound │ └── assignment.py ├── Basic Knowledge └── Data Structure │ ├── list (array implementation).py │ ├── list (linked list implementation).py │ ├── queue (array implementation).py │ └── stack (array implementation).py ├── Brute Force ├── Bubble Selection Sort │ ├── bubble sort.py │ └── selection sort.py ├── Closest-Pair & Convex-Hull Problem │ ├── closest-pair problem.py │ └── convex-hull problem.py ├── Exhaustive Search │ ├── assignment.py │ └── knapsack.py ├── Graph Traversal │ ├── breadth first search.py │ └── depth first search.py └── String Matching │ └── string matching.py ├── Complexity Analysis ├── Non-recursive │ └── matrix multiplication.py └── Recursive │ └── tower of hanoi.py ├── Decrease and Conquer ├── Binary Search Tree │ ├── binary search tree.py │ └── binary search.py ├── Insertion Sort │ ├── array.py │ └── insertion sort.py ├── Interpolation Search │ └── interpolation search.py └── Topological Sorting │ └── topo-sorting.py ├── Divide and Conquer ├── Binary Tree Traversal │ ├── binary tree traversal.py │ └── tree.py ├── Closest-Pair & Convex-Hull Problem │ ├── closest-pair problem.py │ └── convex-hull problem.py ├── Merge Sort │ └── merge sort.py └── Quick Sort │ ├── array.py │ └── quick sort.py ├── Dynamic Programming ├── Classic Problems │ ├── change making.py │ └── coin row.py ├── Floyd's Algorithm │ └── Floyd.py └── Knapsack Problem │ ├── knapsack(bottom-up).py │ └── knapsack(top-down).py ├── Greedy Algorithm ├── Dijkstra's Algorithm │ └── Dijkstra.py ├── Huffman Tree │ └── Huffman.py ├── Kruskal's Algorithm │ └── Kruskal.py └── Prim's Algorithm │ └── Prim.py ├── Introduction └── greatest common divisor.py ├── README.md ├── Time-Space Tradeoff ├── Counting Sort │ └── counting sort.py ├── Hashing │ ├── double hashing.py │ ├── linear probing.py │ └── separate chaining.py └── String Matching │ └── string matching.py └── Transform and Conquer ├── AVL Tree └── AVL tree.py ├── Heap Sort └── heap sort.py ├── Horner's Rule └── horner's rule.py ├── Presorting ├── check element uniqueness.py └── compute mode.py ├── Red-Black Tree └── red-black tree.py └── Two-Three Tree └── 2-3 tree.py /Advanced Algorithm/Backtracking/queen.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # implementation of 8-queen problem solved by backtracking technique 7 | # starting from an empty chessboard, we place the first queen in the 8 | # row 1 column 1, then we attempt to place the second queen in each column in row 2 9 | # if two queens do not attack each other, we place the third queen, and so on 10 | # if either two queens attack each other(dead end), we backtrack and check the next possible option 11 | # loop over the previous steps until a solution is generated 12 | # time complexity: depends on the problem instance, can be exponential or factorial in the worst case 13 | 14 | 15 | def is_promising(table, row): 16 | # whether it is the same column 17 | if len(set(table[: row+1])) != len(table[: row+1]): 18 | return False 19 | # whether it is the same diagonal 20 | for i in range(row): 21 | if abs(table[row] - table[i]) == row - i: 22 | return False 23 | return True 24 | def display_board(table): 25 | n = len(table) 26 | for col in table: 27 | print("+ " * (col) + "Q " + "+ " * (n-1-col)) 28 | def display(table, cnt): 29 | if cnt: 30 | print("Solution %d:" % cnt) 31 | display_board(table) 32 | else: 33 | print("Solution: ") 34 | display_board(table) 35 | 36 | def backtracking(table, n, is_mul, row=0): 37 | if row == n: # initial condition 38 | if is_mul: 39 | global cnt # counter for multiple solutions 40 | display(table, cnt+1); cnt += 1 41 | else: 42 | display(table, None) 43 | exit() # terminate program 44 | return 45 | for col in range(n): 46 | table[row] = col 47 | if is_promising(table, row): 48 | backtracking(table, n, is_mul, row+1) # search the next level 49 | def queen(n, multiple=False): 50 | """ 51 | :param n: size of chessboard 52 | :param multiple: is print multiple solutions 53 | """ 54 | col_tab = [-1] * n 55 | backtracking(col_tab, n, is_mul=multiple) 56 | 57 | 58 | cnt = 0 # a global variable to count the solution 59 | # you can set your own chessboard size and pass the second param `True` to print the entire solution set 60 | queen(8) 61 | 62 | """ 63 | Q + + + + + + + 64 | + + + + Q + + + 65 | + + + + + + + Q 66 | + + + + + Q + + 67 | + + Q + + + + + 68 | + + + + + + Q + 69 | + Q + + + + + + 70 | + + + Q + + + + 71 | """ 72 | 73 | -------------------------------------------------------------------------------- /Advanced Algorithm/Branch and Bound/assignment.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # implementation of assignment problem solved by branch-and-bound technique 7 | # starting from the initial state, for each column of the first row, we cross 8 | # the corresponding row and column and calculate the lower bound of the remaining rows. 9 | # after comparing all states' lower bound, we choose the smallest one as the next state. 10 | # Loop over the previous procedures, until the solution is obtained from the last level 11 | # of the state-space tree 12 | 13 | 14 | import numpy as np 15 | 16 | class State(object): 17 | def __init__(self, matrix, lb, row, process): 18 | self.matrix = matrix # save the current state 19 | self.lb = lb # lower bound for current state 20 | self.process = process # processed values 21 | self.row = row # current row 22 | def compute_lower_bound(a): 23 | return np.sum(np.min(a, axis=1)) 24 | def fix_up(queue): 25 | temp_idx = len(queue) - 1 26 | parent_idx = (temp_idx - 1) // 2 27 | heap = False 28 | while not heap and parent_idx >= 0: 29 | # swap elements if heap property is violated 30 | if queue[temp_idx].lb < queue[parent_idx].lb: 31 | queue[temp_idx], queue[parent_idx] = queue[parent_idx], queue[temp_idx] 32 | else: 33 | heap = True 34 | # update indices 35 | temp_idx = parent_idx 36 | parent_idx = (temp_idx - 1) // 2 37 | def fix_down(queue): 38 | if not queue: 39 | return 40 | temp_idx = 0; size = len(queue) 41 | temp_ver = queue[temp_idx] # save vertex temporarily 42 | heap = False 43 | while not heap and 2 * temp_idx + 1 < size: 44 | j = 2 * temp_idx + 1 # index of left child 45 | # right child exists 46 | if j < size - 1: 47 | # compare two children's weight 48 | if queue[j].lb > queue[j + 1].lb: 49 | j = j + 1 50 | # whether violate heap property or not 51 | if queue[j].lb >= temp_ver.lb: 52 | heap = True 53 | else: 54 | queue[temp_idx] = queue[j] 55 | temp_idx = j # update temp_idx 56 | queue[temp_idx] = temp_ver 57 | def main(c): 58 | row, col = c.shape # get the size of the table 59 | pqueue = [] # priority queue 60 | cur_state = State(c, 0, 0, 0) # initial state 61 | pqueue.append(cur_state) 62 | while cur_state.row < row-1: 63 | cur_state = pqueue.pop() 64 | fix_down(pqueue) # fix the heap 65 | cur_matrix = cur_state.matrix; cur_row = cur_state.row 66 | for cur_col in range(col): 67 | if cur_matrix[cur_row][cur_col] != np.inf: 68 | temp_matrix = cur_matrix.copy() 69 | temp_matrix[cur_row, :] = np.inf; temp_matrix[:, cur_col] = np.inf # mask with infinity 70 | lower_bound = compute_lower_bound(temp_matrix[cur_row+1:]) + cur_state.process + cur_matrix[cur_row][cur_col] 71 | process = cur_state.process + cur_matrix[cur_row][cur_col] # update processed value 72 | pqueue.append(State(temp_matrix, lower_bound, cur_row+1, process)) 73 | fix_up(pqueue) # fix the heap 74 | pqueue[0], pqueue[-1] = pqueue[-1], pqueue[0] 75 | return cur_state.lb 76 | 77 | 78 | C = np.array([[9, 5, 7, 6], 79 | [6, 4, 3, 7], 80 | [5, 8, 1, 8], 81 | [4, 6, 9, 4]], dtype=np.float) 82 | print("The minimal cost is %d." % main(C)) -------------------------------------------------------------------------------- /Basic Knowledge/Data Structure/list (array implementation).py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # sequential list (array implementation) and its three operations: Search, Insert and Remove 7 | # complexity: search Θ(1), insert Θ(n) remove Θ(n) 8 | 9 | def search(array, element): 10 | length = len(array) 11 | for i in range(length): 12 | if array[i] == element: 13 | return i 14 | return -1 15 | 16 | def insert(array, idx, element): 17 | new_array = array.copy() + [None] # copy elements into a new array 18 | length = len(new_array); i = length - 1 19 | # shift elements after idx i to the right by 1 position 20 | while i > idx: 21 | new_array[i] = new_array[i-1] 22 | i -= 1 23 | new_array[idx] = element 24 | return new_array 25 | 26 | def remove(array, element): 27 | length = len(array) 28 | idx = search(array, element) # find index 29 | if idx != -1: 30 | while idx < length - 1: 31 | array[idx] = array[idx+1] 32 | idx += 1 33 | array = array[: -1] 34 | return array 35 | 36 | 37 | array = [24, 22, 76, 41, 7, 61, 15, 34, 80] 38 | print("The original array: \n", array) 39 | # search element 61 40 | print("Search element 61:") 41 | if search(array, 61) != -1: 42 | print("Search is successful!") 43 | else: 44 | print("Search is unsuccessful!") 45 | # remove element 61 46 | array = remove(array, 61) 47 | print("After removing 61: \n", array) 48 | # insert element 61 49 | array = insert(array, 5, 61) 50 | print("After inserting 61: \n", array) -------------------------------------------------------------------------------- /Basic Knowledge/Data Structure/list (linked list implementation).py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # sequential list (linked list implementation) and its three operations: Search, Insert and Remove 7 | # complexity: search Θ(n), insert Θ(1) remove Θ(1) 8 | 9 | class Node(object): 10 | def __init__ (self, value): 11 | self.value = value 12 | self.next = None 13 | 14 | class Linked_List(object): 15 | def __init__(self): 16 | self.length = 0 17 | self.header = None 18 | def search(self, value): 19 | p = self.header # retrieve the first node 20 | while p != None: 21 | if p.value == value: 22 | return p 23 | p = p.next 24 | return None 25 | def insert(self, value): 26 | # instantiate a new node 27 | p = Node(value) 28 | p.next = self.header 29 | self.header = p 30 | self.length += 1 31 | def remove(self): 32 | p = self.header # retrieve the first node 33 | if p is not None: 34 | self.header = p.next 35 | self.length -= 1 36 | del p # delete node p 37 | def traversal(self): 38 | array = [] # for displaying 39 | p = self.header 40 | while p is not None: 41 | array.append(p.value) 42 | p = p.next 43 | return array 44 | 45 | 46 | elements = [24, 22, 76, 41, 7, 61, 15, 34, 80] 47 | # initialize an empty linked list 48 | linked_list = Linked_List() 49 | # construct linked list 50 | for e in elements: 51 | linked_list.insert(e) 52 | print("The original array: \n", linked_list.traversal()) 53 | # search node 61 54 | print("Search node 61:") 55 | if linked_list.search(61): 56 | print("Search is successful!") 57 | else: 58 | print("Search is unsuccessful!") 59 | # remove a node 60 | linked_list.remove() 61 | print("After removing a node: \n", linked_list.traversal()) 62 | # insert node 80 63 | linked_list.insert(80) 64 | print("After inserting node 34: \n", linked_list.traversal()) 65 | 66 | -------------------------------------------------------------------------------- /Basic Knowledge/Data Structure/queue (array implementation).py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # implementation of queue and its two operations: Enqueue and Dequeue 7 | # complexity: enqueue Θ(1), dequeue Θ(1) 8 | 9 | 10 | class Queue (object): 11 | 12 | def __init__ (self, front, rear, array): 13 | self.array = array 14 | self.front = front 15 | self.rear = rear 16 | 17 | def enqueue(self, element): 18 | # examine overflow 19 | if self.rear >= len(self.array) - 1: 20 | raise IndexError("Queue overflow! ") 21 | else: 22 | self.rear += 1 # increment top by 1 23 | self.array[self.rear] = element 24 | return 25 | 26 | def dequeue(self): 27 | # examine underflow 28 | if self.front >= self.rear: 29 | raise IndexError("Queue underflow!") 30 | else: 31 | self.array[self.front] = None 32 | self.front += 1 # decrement top by 1 33 | return 34 | 35 | 36 | array = [24, 22, 76, 41, 7, 61, 15, 34, 80, None, None, None, None] 37 | front = 0; rear = 8 38 | queue = Queue(front, rear, array) # instantiate a queue 39 | print("The original array: \n", queue.array) 40 | # enqueue 24, 85 into the queue 41 | queue.enqueue(24); queue.enqueue(85) 42 | print("After enqueuing 24 and 85: \n", queue.array) 43 | # dequeue three elements 44 | queue.dequeue(); queue.dequeue(); queue.dequeue() 45 | print("After dequeuing three elements: \n", queue.array) 46 | 47 | -------------------------------------------------------------------------------- /Basic Knowledge/Data Structure/stack (array implementation).py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # implementation of stack and its two operations: Push and Pop 7 | # complexity: enqueue Θ(1), dequeue Θ(1) 8 | 9 | class Stack (object): 10 | 11 | def __init__ (self, top, down, array): 12 | self.array = array 13 | self.top = top 14 | self.down = down 15 | 16 | def push(self, element): 17 | # examine overflow 18 | if self.top >= len(self.array) - 1: 19 | raise IndexError("Stack overflow! ") 20 | else: 21 | self.top += 1 # increment top by 1 22 | self.array[self.top] = element 23 | return 24 | 25 | def pop(self): 26 | # examine underflow 27 | if self.top <= 0: 28 | raise IndexError("Stack underflow!") 29 | else: 30 | self.array[self.top] = None 31 | self.top -= 1 # decrement top by 1 32 | return 33 | 34 | 35 | array = [24, 22, 76, 41, 7, 61, 15, 34, 80, None, None, None, None] 36 | down = 0; top = 8 37 | stack = Stack(top, down, array) # instantiate a stack 38 | print("The original array: \n", stack.array) 39 | # push 24, 85 into the stack 40 | stack.push(24); stack.push(85) 41 | print("After pushing 24 and 85: \n", stack.array) 42 | # pop three elements 43 | stack.pop(); stack.pop(); stack.pop() 44 | print("After popping three elements: \n", stack.array) 45 | -------------------------------------------------------------------------------- /Brute Force/Bubble Selection Sort/bubble sort.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | import random 7 | import time 8 | 9 | # implementation of bubble sort 10 | # to sort a random array with 10,000 elements 11 | # two metrics ("number of comparison" and "consumed time") for efficiency evaluation 12 | # time complexity: Θ(n^2) in all cases 13 | # space complexity Θ(1) 14 | # stability: stable 15 | 16 | random.seed(1) # for reproducibility 17 | def bubble_sort(array): 18 | cnt = 0 19 | for i in range(len(array)-1): 20 | for j in range(len(array)-i-1): 21 | if array[j] > array[j+1]: 22 | array[j], array[j+1] = array[j+1], array[j] 23 | cnt += 1 24 | return cnt 25 | 26 | 27 | time_in_total = 0 28 | epoch = 5 # num of iteration 29 | total_comparison = 0 30 | 31 | for i in range(epoch): 32 | time_start = time.time() 33 | array = [random.randint(0,10000) for i in range(10000)] 34 | comparison = bubble_sort(array) 35 | time_finish = time.time() 36 | total_comparison += comparison 37 | time_in_total += time_finish - time_start 38 | print("Epoch {}: \n number of comparison: {}\n time consumed: {:.4f} s".format(i+1, comparison, time_finish-time_start)) 39 | print("average number of comparison: %d" % (total_comparison/epoch)) 40 | print('average time consumed: {:.4f} s'.format(time_in_total/epoch)) -------------------------------------------------------------------------------- /Brute Force/Bubble Selection Sort/selection sort.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | import random 7 | import time 8 | 9 | 10 | # implementation of selection sort 11 | # to sort a random array with 10,000 elements 12 | # two metrics ("number of comparison" and "consumed time") for efficiency evaluation 13 | # time complexity: Θ(n^2) in all cases 14 | # space complexity Θ(1) 15 | # stability: unstable 16 | 17 | random.seed(1) # for reproducibility 18 | def selection_sort(array): 19 | cnt = 0 20 | for i in range(len(array)-1): 21 | minimum = i 22 | for j in range(i+1, len(array)): 23 | if array[j] < array[minimum]: 24 | minimum = j 25 | cnt += 1 26 | array[minimum], array[i] = array[i], array[minimum] 27 | return cnt 28 | 29 | 30 | time_in_total = 0 31 | epoch = 5 # num of iteration 32 | total_comparison = 0 33 | 34 | for i in range(epoch): 35 | time_start = time.time() 36 | array = [random.randint(0,10000) for i in range(10000)] 37 | comparison = selection_sort(array) 38 | time_finish = time.time() 39 | total_comparison += comparison 40 | time_in_total += time_finish - time_start 41 | print("Epoch {}: \n number of comparison: {}\n time consumed: {:.4f} s".format(i+1, comparison, time_finish-time_start)) 42 | print("average number of comparison: %d" % (total_comparison/epoch)) 43 | print('average time consumed: {:.4f} s'.format(time_in_total/epoch)) 44 | -------------------------------------------------------------------------------- /Brute Force/Closest-Pair & Convex-Hull Problem/closest-pair problem.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # solution of closest-pair problem (brute-force implementation) 7 | # step: 8 | # 1. enumerate all pairs of points 9 | # 2. compute each pair's distance 10 | # 3. find the minimal value 11 | # time complexity: Θ(n^2) 12 | 13 | import numpy as np 14 | import matplotlib.pyplot as plt 15 | 16 | np.random.seed(13) # for reproducibility 17 | 18 | def closest_pair_bf(X, Y): 19 | d = np.inf # initialize d to infinity 20 | for i in range(len(X)-1): 21 | for j in range(i+1, len(X)): 22 | if (X[i] - X[j])**2 + (Y[i] - Y[j])**2 < d: 23 | d = (X[i] - X[j])**2 + (Y[i] - Y[j])**2 24 | coordinates = np.array([[X[i][0], Y[i][0]], [X[j][0], Y[j][0]]]) # record coordinates of temporary pair 25 | d = np.sqrt(d) 26 | return d, coordinates 27 | 28 | # for plotting 29 | def plotting(xy_pair, d, pair): 30 | plt.scatter(xy_pair[:, 0], xy_pair[:, 1], s=15) 31 | plt.plot(pair[:, 0], pair[:, 1], c='r') 32 | plt.text(1.35, 3, "closest distance = %.3f" % d) 33 | plt.grid() 34 | plt.show() 35 | 36 | X = np.random.normal(2, 0.5, size=[16, 1]) 37 | Y = np.random.normal(2, 0.5, size=[16, 1]) 38 | XY_pair = np.column_stack((X, Y)) # stack to (xi, yi) 39 | min_d, clo_pair = closest_pair_bf(X, Y) 40 | 41 | plotting(XY_pair, min_d, clo_pair) -------------------------------------------------------------------------------- /Brute Force/Closest-Pair & Convex-Hull Problem/convex-hull problem.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # solution of convex-hull problem (brute-force implementation) 7 | # step: 8 | # 1. enumerate all pairs of points 9 | # 2. for each pair, check the remaining points whether they stand in the same side 10 | # 3. select the pair that satisfy step 2. 11 | # time complexity: Θ(n^3) 12 | 13 | import numpy as np 14 | import matplotlib.pyplot as plt 15 | 16 | np.random.seed(13) # for reproducibility 17 | 18 | def convex_hull_bf(XY_pair): 19 | pairs = [] 20 | for i in range(len(XY_pair) - 1): 21 | for j in range(i+1, len(XY_pair)): 22 | a = XY_pair[j][1] - XY_pair[i][1] 23 | b = XY_pair[i][0] - XY_pair[j][0] 24 | c = XY_pair[i][0] * XY_pair[j][1] - XY_pair[i][1] * XY_pair[j][0] 25 | k = 0; pos = 0; neg = 0 # pos and neg count the number of points on either side respectively 26 | while k < len(XY_pair): 27 | if k != i and k != j: 28 | if a * XY_pair[k][0] + b * XY_pair[k][1] - c > 0: 29 | pos += 1 30 | else: 31 | neg += 1 32 | if pos != 0 and neg != 0: 33 | break 34 | k += 1 35 | else: 36 | pairs.append((XY_pair[i], XY_pair[j])) 37 | return pairs 38 | 39 | # for plotting 40 | def plotting(xy_pair, polygon): 41 | plt.scatter(xy_pair[:, 0], xy_pair[:, 1], s=15) 42 | for i in range(len(polygon)): 43 | np_point_stack = np.stack(polygon[i], axis=1) # stack each point pair to (xi, yi) 44 | plt.plot(np_point_stack[0], np_point_stack[1], c='r') 45 | plt.show() 46 | 47 | X = np.random.normal(2, 0.5, size=[16, 1]) 48 | Y = np.random.normal(2, 0.5, size=[16, 1]) 49 | XY_pair = np.column_stack((X, Y)) 50 | polygon = convex_hull_bf(XY_pair) 51 | plotting(XY_pair, polygon) 52 | -------------------------------------------------------------------------------- /Brute Force/Exhaustive Search/assignment.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # implementation of assignment problem solved by brute-force approach 7 | 8 | import numpy as np 9 | 10 | def perm_gen(source): 11 | permutation = [[source[0]]] 12 | for i in range(1, len(source)): 13 | temp = [] # rewrite for each iteration 14 | for p in permutation: 15 | for j in range(len(p), -1, -1): 16 | p.insert(j, source[i]) 17 | temp.append(p.copy()) 18 | p.pop(j) 19 | permutation = temp.copy() 20 | return permutation 21 | 22 | def assignment_bf(perm, table): 23 | combination = None; min_cost = np.inf 24 | for i in range(len(perm)): 25 | temp_cost = 0 # accumulator 26 | for j, k in enumerate(perm[i]): 27 | temp_cost += table[j][k-1] 28 | if temp_cost < min_cost: 29 | combination = perm[i] 30 | min_cost = temp_cost 31 | return combination, min_cost 32 | 33 | 34 | table = np.array([[9, 2, 7, 8], 35 | [6, 4, 3, 7], 36 | [5, 8, 1, 8], 37 | [7, 6, 9, 4]]) 38 | 39 | num_staff = len(table) 40 | permutation = perm_gen(list(range(1, num_staff+1))) 41 | combination, min_cost = (assignment_bf(permutation, table)) 42 | 43 | print("The optimal assignment is") 44 | for i in range(num_staff-1): 45 | print("assign task {} to staff {}".format(combination[i], i+1), end=', ') 46 | print("task {} to staff {}.".format(combination[-1], num_staff)) 47 | print("The minimal cost is %d." % min_cost) 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /Brute Force/Exhaustive Search/knapsack.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # implementation of knapsack problem solved by brute-force approach 7 | 8 | def power_set_gen(w): 9 | p_set = [set()] 10 | for item in w: 11 | temp_set = [] 12 | for subset in p_set: 13 | subset = subset | {item} 14 | temp_set.append(subset) 15 | p_set.extend(temp_set) 16 | return p_set 17 | 18 | def knapsack_bf(w_v, p_set, W): 19 | w_combination = None; max_value = 0 20 | for instance in p_set: 21 | weight, value = 0, 0 22 | for item in instance: 23 | weight += item; value += w_v[item] 24 | if weight <= W and value > max_value: 25 | w_combination = instance 26 | max_value = value 27 | return w_combination, max_value 28 | 29 | 30 | w_v = {7: 42, 3: 12, 4: 40, 5: 25} # mapping from weight to value 31 | w_i = {7: 1, 3: 2, 4: 3, 5: 4} # mapping from weight to index 32 | W = 10 33 | 34 | power_set = power_set_gen(w_v.keys()) 35 | combination, max_value = knapsack_bf(w_v, power_set, W) 36 | combination = list(combination) 37 | 38 | print("The optimal combination is: ") 39 | for i in range(len(combination)-1): 40 | print(" item %d" % w_i[combination[i]], end=', ') 41 | print("item %d." % w_i[combination[-1]]) 42 | print("The total value is %d." % max_value) 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Brute Force/Graph Traversal/breadth first search.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # implementation of graph traversal (breadth-first search). 7 | # time complexity: Θ(|V|^2) for adjacency matrix; Θ(|V|+|E|) for adjacency list 8 | 9 | class Vertex(object): 10 | def __init__(self, value, visited): 11 | self.value = value 12 | self.visited = False 13 | self.neighbours = [] # for storing all adjacent vertices 14 | 15 | def display(v): 16 | print(v.value, end=' ') 17 | 18 | def bfs(v): 19 | v.visited = True 20 | queue = [] # initialize an empty queue 21 | queue.append(v) # enqueue vertex v 22 | while queue: 23 | for w in v.neighbours: 24 | if not w.visited: 25 | w.visited = True 26 | queue.append(w) 27 | v = queue.pop(0) # dequeue vertex v 28 | display(v) 29 | 30 | # iterate vertices of the graph 31 | def BFS(G): 32 | V = G.keys() 33 | for v in V: 34 | if not v.visited: 35 | bfs(v) 36 | 37 | # instantiate vertices and edges 38 | a = Vertex('a', False) 39 | b = Vertex('b', False) 40 | c = Vertex('c', False) 41 | d = Vertex('d', False) 42 | e = Vertex('e', False) 43 | f = Vertex('f', False) 44 | g = Vertex('g', False) 45 | h = Vertex('h', False) 46 | 47 | # instantiate a graph 48 | G = {a: [b, c], 49 | b: [a, d], 50 | c: [a, d, f], 51 | d: [b, c, e], 52 | e: [d], 53 | f: [c], 54 | g: [h], 55 | h: [g]} 56 | 57 | for v, n in G.items(): 58 | v.neighbours.extend(n) 59 | 60 | print("The traversal order of the graph for breadth-first search is ") 61 | BFS(G) 62 | 63 | 64 | -------------------------------------------------------------------------------- /Brute Force/Graph Traversal/depth first search.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # implementation of graph traversal (depth-first search). 7 | # time complexity: Θ(|V|^2) for adjacency matrix; Θ(|V|+|E|) for adjacency list 8 | 9 | class Vertex(object): 10 | def __init__(self, value, visited): 11 | self.value = value 12 | self.visited = False 13 | self.neighbours = [] # for storing all adjacent vertices 14 | 15 | def display(v): 16 | print(v.value, end=' ') 17 | 18 | def dfs(v): 19 | v.visited = True 20 | display(v) 21 | for w in v.neighbours: 22 | if not w.visited: 23 | w.visited = True 24 | dfs(w) 25 | 26 | # iterate vertices 27 | def DFS(G): 28 | V = G.keys() 29 | for v in V: 30 | if not v.visited: 31 | dfs(v) 32 | 33 | # instantiate vertices and edges 34 | a = Vertex('a', False) 35 | b = Vertex('b', False) 36 | c = Vertex('c', False) 37 | d = Vertex('d', False) 38 | e = Vertex('e', False) 39 | f = Vertex('f', False) 40 | g = Vertex('g', False) 41 | h = Vertex('h', False) 42 | 43 | # instantiate a graph 44 | G = {a: [b, c], 45 | b: [a, d], 46 | c: [a, d, f], 47 | d: [b, c, e], 48 | e: [d], 49 | f: [c], 50 | g: [h], 51 | h: [g]} 52 | 53 | for v, n in G.items(): 54 | v.neighbours.extend(n) 55 | 56 | print("The traversal order of the graph for depth-first search is ") 57 | DFS(G) 58 | 59 | 60 | -------------------------------------------------------------------------------- /Brute Force/String Matching/string matching.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # implementation of string matching solved by brute-force approach 7 | # complementarity of DNA base pair, which has great similarity to string matching 8 | # method `convert` is for converting A to T, T to A, C to G and G to C 9 | # time complexity: Θ(mn) for the worst case, Θ(n) for the average case 10 | 11 | def string_matching_bf(text, pattern): 12 | pattern = convert(pattern) 13 | n = len(text); m = len(pattern) 14 | for i in range(n-m+1): 15 | j = 0 # count the number of matching pairs 16 | while j < m and pattern[j] == text[i+j]: 17 | j += 1 18 | if j == m: 19 | return i 20 | return -1 21 | 22 | 23 | # to convert A-T and C-G pairs 24 | def convert(ori_text): 25 | dic = {'A': 'T', 'T': 'A', 'C': 'G', 'G': 'C'} 26 | temp = [] 27 | for char in ori_text: 28 | conv_char = dic[char] 29 | temp.append(conv_char) 30 | conv_text = ''.join(temp) 31 | return conv_text 32 | 33 | 34 | plain_text = "TCGAGAATTCCTA" 35 | pattern = "CTTAAG" 36 | print("String matching solved by brute force approach:\n") 37 | print("The plain text is %s" % plain_text) 38 | print("The pattern string is %s\n" % pattern) 39 | status = string_matching_bf(plain_text, pattern) 40 | if status != -1: 41 | print("String matching is successful, the index of first occurrence is %d." % status) 42 | else: 43 | print("String matching in unsuccessful.") 44 | -------------------------------------------------------------------------------- /Complexity Analysis/Non-recursive/matrix multiplication.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # implementation of matrix multiplication (2 × 2 square matrix) 7 | # time complexity: Θ(n^3) 8 | 9 | 10 | def matmul(A, B): 11 | n = len(A) 12 | C = [[0 for i in range(n)] for j in range(n)] 13 | for i in range(n): 14 | for j in range(n): 15 | for k in range(n): 16 | C[i][j] = C[i][j] + A[i][k] * B[k][j] 17 | return C 18 | 19 | 20 | A = [[-3, 1], [2, 5]] 21 | B = [[5, 3], [7, -3]] 22 | print("A = \n", A) 23 | print("B = \n", B) 24 | print("AB = \n", matmul(A, B)) 25 | -------------------------------------------------------------------------------- /Complexity Analysis/Recursive/tower of hanoi.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # implementation of Tower of Hanoi (recursive) 7 | # time complexity: Θ(2^n) 8 | 9 | 10 | def hanoi(a, b, c, n): 11 | if n == 1: 12 | print("{} -> {}".format(a, c)) 13 | return 14 | hanoi(a, c, b, n-1) # rod a as source, rod b as target, rod c as pivot 15 | hanoi(a, b, c, 1) # set the number of disk to be moved to 1 16 | hanoi(b, a, c, n-1) # rod b as source, rod c as target, rod a as pivot 17 | 18 | 19 | print("Solution of Tower of Hanoi with 4 disks: ", ) 20 | hanoi('a', 'b', 'c', 3) -------------------------------------------------------------------------------- /Decrease and Conquer/Binary Search Tree/binary search tree.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # implementation of binary search tree 7 | # three operations: search, insert and remove 8 | # time complexity is determined by the structure of the tree 9 | # if the tree is well-balanced, complexity is close to Θ(log n) 10 | # while the complexity can be degraded to linear Θ(n), 11 | # if the tree is constructed by the successive insertions of increasing or decreasing keys 12 | 13 | class Node(object): 14 | def __init__(self, value): 15 | self.value = value 16 | self.left = None 17 | self.right = None 18 | 19 | class BST(object): 20 | def __init__(self): 21 | self.root = None 22 | def insert(self, value): 23 | new_node = Node(value) # create a new node 24 | # check whether the root node is null 25 | if not self.root: # search from the left sub-tree 26 | self.root = new_node 27 | else: # search from the right sub-tree 28 | self.insert_node(self.root, new_node) 29 | def insert_node(self, root, node): 30 | if node.value < root.value: 31 | if root.left: 32 | self.insert_node(root.left, node) 33 | else: 34 | root.left = node 35 | else: 36 | if root.right: 37 | self.insert_node(root.right, node) 38 | else: 39 | root.right = node 40 | def search(self, value): 41 | # throw an exception if root is null 42 | if not self.root: 43 | raise ValueError("The tree is null") 44 | return self.search_node(self.root, value) 45 | def search_node(self, root, value): 46 | if not root: # return None if the node is null 47 | return None 48 | if value < root.value: # search from the left sub-tree 49 | return self.search_node(root.left, value) 50 | elif value > root.value: # search from the right sub-tree 51 | return self.search_node(root.right, value) 52 | else: 53 | return root 54 | def get_max(self, root): 55 | if root.right: 56 | return self.get_max(root.right) 57 | return root 58 | def remove(self, value): 59 | # throw an exception if root is null 60 | if not self.root: 61 | raise ValueError("The tree is null") 62 | self.root = self.remove_node(self.root, value) 63 | def remove_node(self, root, value): 64 | if not root: 65 | return None 66 | # search 67 | if value < root.value: 68 | root.left = self.remove_node(root.left, value) 69 | elif value > root.value: 70 | root.right = self.remove_node(root.right, value) 71 | else: 72 | # no children 73 | if not root.left and not root.right: 74 | del root 75 | return None 76 | # either left child or right child 77 | elif not root.left: 78 | temp = root.right 79 | del root 80 | return temp 81 | elif not root.right: 82 | temp = root.left 83 | del root 84 | return temp 85 | # both left and right child 86 | else: 87 | temp = self.get_max(root.left) # find the maximum value from the left sub-tree 88 | root.value = temp.value 89 | root.left = self.remove_node(root.left, temp.value) 90 | return root 91 | def traversal(self): 92 | collection = [] 93 | if self.root: 94 | return self.traversal_tree(self.root, collection) 95 | return collection 96 | def traversal_tree(self, root, collection): 97 | # in-order traversal 98 | if root.left: 99 | self.traversal_tree(root.left, collection) 100 | collection.append(root.value) 101 | if root.right: 102 | self.traversal_tree(root.right, collection) 103 | return collection 104 | 105 | array = [48, 25, 73, 10, 34, 61, 81] 106 | bst = BST() 107 | # build binary search tree 108 | for element in array: 109 | bst.insert(element) 110 | print("Build binary search tree: \n", bst.traversal()) 111 | # search 112 | key1 = 61; key2 = 89 113 | print("Search: ") 114 | 115 | # success 116 | print("search key = %d" % key1) 117 | if bst.search(key1): 118 | print("Search successful.") 119 | else: 120 | print("Search unsuccessful.") 121 | 122 | # fail 123 | print("search key = %d" % key2) 124 | if bst.search(key2): 125 | print("Search successful.") 126 | else: 127 | print("Search unsuccessful.") 128 | 129 | # insertion 130 | bst.insert(42) 131 | print("After insertion: \n", bst.traversal()) 132 | 133 | # deletion 134 | # case 1 135 | bst.remove(10) 136 | print("After deleting 10: \n", bst.traversal()) 137 | 138 | # case 2 139 | bst.remove(34) 140 | print("After deleting 34: \n", bst.traversal()) 141 | 142 | # case 3 143 | bst.remove(48) 144 | print("After deleting 48: \n", bst.traversal()) 145 | 146 | -------------------------------------------------------------------------------- /Decrease and Conquer/Binary Search Tree/binary search.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # implementation of binary search 7 | # time complexity: Θ(log n) 8 | 9 | def binary_search(array, key): 10 | l = 0; r = len(array) - 1 11 | while l <= r: 12 | m = (l + r) // 2 # round down 13 | if key == array[m]: 14 | return m 15 | elif key < array[m]: 16 | r = m - 1 17 | else: 18 | l = m + 1 19 | return -1 20 | 21 | 22 | array = [10, 25, 34, 48, 61, 73, 81] 23 | key1 = 61; key2 = 76 24 | print("Binary Search: ") 25 | 26 | # success 27 | print("search key = %d" % key1) 28 | if binary_search(array, key1) != -1: 29 | print("Search successful. The index is %d." % binary_search(array, key1)) 30 | else: 31 | print("Search unsuccessful.") 32 | 33 | # fail 34 | print("search key = %d" % key2) 35 | if binary_search(array, key2) != -1: 36 | print("Search successful. The index is %d." % binary_search(array, key2)) 37 | else: 38 | print("Search unsuccessful.") -------------------------------------------------------------------------------- /Decrease and Conquer/Insertion Sort/array.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | import random 7 | 8 | # array generator for different cases (best case, average case, worst case) 9 | 10 | class generator(object): 11 | def __init__(self, size, seed): 12 | self.size = size 13 | random.seed = seed # for reproducibility 14 | def avg(self): 15 | return [random.randint(0,self.size) for i in range(self.size)] 16 | def best(self): 17 | return sorted([random.randint(0,self.size) for i in range(self.size)]) 18 | def worst(self): 19 | return sorted([random.randint(0,self.size) for i in range(self.size)], reverse=True) 20 | 21 | 22 | -------------------------------------------------------------------------------- /Decrease and Conquer/Insertion Sort/insertion sort.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | import time 7 | from array import generator 8 | 9 | # implementation of insertion sort 10 | # to sort a random array with 10,000 elements 11 | # two metrics ("number of comparison" and "consumed time") for efficiency evaluation 12 | # time complexity: Θ(n^2) for the worst and average case, and Θ(n) for the best case 13 | # space complexity Θ(1) 14 | # stability: stable 15 | 16 | def insertion_sort(array): 17 | cnt = 0 18 | for i in range(1, len(array)): 19 | v = array[i] 20 | j = i - 1 21 | while True: 22 | cnt += 1 23 | if j < 0 or array[j] <= v: 24 | break 25 | array[j+1] = array[j] 26 | j = j - 1 27 | array[j+1] = v 28 | return cnt 29 | 30 | time_in_total = 0 31 | epoch = 5 # num of iteration 32 | total_comparison = 0 33 | 34 | # instantiate a generator 35 | gen = generator(10000, 1) 36 | 37 | # best case Θ(n) 38 | print("Best Case: ") 39 | for i in range(epoch): 40 | array = gen.best() 41 | time_start = time.time() 42 | comparison = insertion_sort(array) 43 | time_finish = time.time() 44 | total_comparison += comparison 45 | time_in_total += time_finish - time_start 46 | print("Epoch {}: \n number of comparison: {}\n time consumed: {:.4f} s".format(i+1, comparison, time_finish-time_start)) 47 | print("average number of comparison: %d" % (total_comparison/epoch)) 48 | print('average time consumed: {:.4f} s'.format(time_in_total/epoch)) 49 | 50 | # average case Θ(n^2 / 4) 51 | total_comparison = 0; time_in_total = 0 52 | print("Average Case: ") 53 | for i in range(epoch): 54 | array = gen.avg() 55 | time_start = time.time() 56 | comparison = insertion_sort(array) 57 | time_finish = time.time() 58 | total_comparison += comparison 59 | time_in_total += time_finish-time_start 60 | print("Epoch {}: \n number of comparison: {}\n time consumed: {:.4f} s".format(i+1, comparison, time_finish-time_start)) 61 | print("average number of comparison: %d" % (total_comparison/epoch)) 62 | print('average time consumed: {:.4f} s'.format(time_in_total/epoch)) 63 | 64 | # worst case Θ(n^2) 65 | total_comparison = 0; time_in_total = 0 66 | print("Worst Case: ") 67 | for i in range(epoch): 68 | array = gen.worst() 69 | time_start = time.time() 70 | comparison = insertion_sort(array) 71 | time_finish = time.time() 72 | total_comparison += comparison 73 | time_in_total += time_finish-time_start 74 | print("Epoch {}: \n number of comparison: {}\n time consumed: {:.4f} s".format(i+1, comparison, time_finish-time_start)) 75 | print("average number of comparison: %d" % (total_comparison/epoch)) 76 | print('average time consumed: {:.4f} s'.format(time_in_total/epoch)) 77 | 78 | -------------------------------------------------------------------------------- /Decrease and Conquer/Interpolation Search/interpolation search.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # implementation of interpolation search 7 | # time complexity: Θ(log log n) 8 | 9 | def formula(l, r, key, array): 10 | p = (key - array[l]) / (array[r] - array[l]) 11 | n = r - l 12 | idx = int(n * p) 13 | return idx 14 | 15 | def interpolation_search(array, key): 16 | l = 0; r = len(array) - 1 17 | while l < r: 18 | x = l + formula(l, r, key, array) 19 | x = max(l, min(x, r)) 20 | if array[x] == key: 21 | return x 22 | elif array[x] < key: 23 | l = x + 1 24 | else: 25 | r = x - 1 26 | return -1 27 | 28 | 29 | array = [10, 25, 34, 48, 61, 73, 81] 30 | key1 = 61; key2 = 67 31 | print("Interpolation Search: ") 32 | 33 | # success 34 | print("search key = %d" % key1) 35 | if interpolation_search(array, key1) != -1: 36 | print("Search successful. The index is %d." % interpolation_search(array, key1)) 37 | else: 38 | print("Search unsuccessful.") 39 | 40 | # fail 41 | print("search key = %d" % key2) 42 | if interpolation_search(array, key2) != -1: 43 | print("Search successful. The index is %d." % interpolation_search(array, key2)) 44 | else: 45 | print("Search unsuccessful.") 46 | -------------------------------------------------------------------------------- /Decrease and Conquer/Topological Sorting/topo-sorting.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # implementation of topological sorting (Kahn algorithm) 7 | # step: 8 | # 1. find the source vertex, i.e., in-degree is equal to 0 9 | # 2. remove the vertex associated with its edge(s) 10 | # 3. repeat step 1 and 2 until no such vertex in the graph 11 | 12 | class Vertex(object): 13 | def __init__(self, value): 14 | self.value = value 15 | self.visited = False 16 | 17 | def find_source(G): 18 | source = None 19 | V = G.keys(); E = G.values() # unpack G 20 | for v in V: 21 | for neighbour in E: 22 | for vertex in neighbour: 23 | # check each edges 24 | if vertex is v: 25 | break 26 | else: 27 | continue 28 | break 29 | else: 30 | source = v 31 | break 32 | return source 33 | 34 | def topo_main(G): 35 | order = [] 36 | while len(G) != 0: 37 | s = find_source(G) 38 | if s is None: 39 | return "No such topological order." 40 | order.append(s.value) 41 | G.pop(s) 42 | return order 43 | 44 | 45 | a = Vertex('advanced mathematics') 46 | b = Vertex('linear algebra') 47 | c = Vertex('probability theory') 48 | d = Vertex('data structure') 49 | e = Vertex('machine learning') 50 | f = Vertex('computer vision') 51 | 52 | G = {a: [b, e], 53 | b: [c, e], 54 | c: [e], 55 | d: [e], 56 | e: [f], 57 | f: []} 58 | 59 | print(topo_main(G)) 60 | 61 | 62 | -------------------------------------------------------------------------------- /Divide and Conquer/Binary Tree Traversal/binary tree traversal.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # implementation of binary tree traversal 7 | # pre-order: root node -> left sub-tree -> right sub-tree 8 | # in-order: left sub-tree -> root node -> right sub-tree 9 | # post-order: left sub-tree -> right sub-tree -> root node 10 | # time complexity: Θ(n) 11 | 12 | from tree import bst as T 13 | 14 | # pre-order 15 | def pre_order(T): 16 | print(T.value, end=' ') 17 | if T.left: 18 | pre_order(T.left) 19 | if T.right: 20 | pre_order(T.right) 21 | 22 | # in-order 23 | def in_order(T): 24 | if T.left: 25 | in_order(T.left) 26 | print(T.value, end=' ') 27 | if T.right: 28 | in_order(T.right) 29 | 30 | # post-order 31 | def post_order(T): 32 | if T.left: 33 | post_order(T.left) 34 | if T.right: 35 | post_order(T.right) 36 | print(T.value, end=' ') 37 | 38 | 39 | print("pre-order traversal: ") 40 | pre_order(T.root) 41 | print("\nin-order traversal: ") 42 | in_order(T.root) 43 | print("\npost-order traversal: ") 44 | post_order(T.root) -------------------------------------------------------------------------------- /Divide and Conquer/Binary Tree Traversal/tree.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # build a binary tree for traversal 7 | 8 | class Node(object): 9 | def __init__(self, value): 10 | self.value = value 11 | self.left = None 12 | self.right = None 13 | 14 | class tree(object): 15 | def __init__(self): 16 | self.root = None 17 | def insert(self, value): 18 | new_node = Node(value) # create a new node 19 | # check whether the root node is null 20 | if not self.root: # search from the left sub-tree 21 | self.root = new_node 22 | else: # search from the right sub-tree 23 | self.insert_node(self.root, new_node) 24 | def insert_node(self, root, node): 25 | if node.value < root.value: 26 | if root.left: 27 | self.insert_node(root.left, node) 28 | else: 29 | root.left = node 30 | else: 31 | if root.right: 32 | self.insert_node(root.right, node) 33 | else: 34 | root.right = node 35 | 36 | 37 | array = [48, 25, 73, 10, 34, 61, 81] 38 | bst = tree() 39 | # build binary search tree 40 | for element in array: 41 | bst.insert(element) 42 | -------------------------------------------------------------------------------- /Divide and Conquer/Closest-Pair & Convex-Hull Problem/closest-pair problem.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # solution of closest-pair problem (divide-and-conquer implementation) 7 | # step: 8 | # 1. split scatter points into two parts 9 | # 2. calculate the closest pairs of left and right parts respectively 10 | # 3. choose the shorter one d 11 | # 4. let m equals to the middle point 12 | # 5. copy all points for which |x - m| < d into array S 13 | # 6. if the region x±d exists a pair such that the distance between them is shorter than d, 14 | # let the pair be the closest pair and update d 15 | # 7. do this operation recursively until the number of points is less than or equal to three, 16 | # then the brute-force approach is used to find the closest pair. 17 | # time complexity: Θ(nlog n) 18 | 19 | import numpy as np 20 | import matplotlib.pyplot as plt 21 | 22 | np.random.seed(13) # for reproducibility 23 | 24 | def brute_force(x, y): 25 | dmin = np.inf # initialize dmin to infinity 26 | for i in range(len(x)): 27 | for j in range(i + 1, len(x)): 28 | if (x[i] - x[j]) ** 2 + (y[i] - y[j]) ** 2 < dmin: 29 | dmin = (x[i] - x[j]) ** 2 + (y[i] - y[j]) ** 2 30 | coordinates = np.array([[x[i], y[i]], [x[j], y[j]]]) 31 | return dmin, coordinates 32 | 33 | def closest_pair_dc(P, Q, n): 34 | if n <= 3: 35 | return brute_force(P, Q) 36 | else: 37 | left = (len(P) + 1) // 2; right = len(P) - left # number of points of left and right part 38 | P_left = P[:left]; P_right = P[left:] 39 | Q_left = Q[:left]; Q_right = Q[left:] 40 | d_left, pair_left = closest_pair_dc(P_left, Q_left, left) # find the closest pair from the left recursively 41 | d_right, pair_right = closest_pair_dc(P_right, Q_right, right) # find the closest pair from the right recursively 42 | # choose the shorter one 43 | if d_left < d_right: 44 | d = d_left; temp_pair = pair_left 45 | else: 46 | d = d_right; temp_pair = pair_right 47 | m = P[(len(P)) // 2] 48 | S = [] # points within the region |x - m| < d for temporary storage 49 | # copy 50 | for key in P: 51 | if abs(key - m) < d: 52 | S.append(key) 53 | for i in range(len(S) - 1): 54 | j = i + 1 55 | while j < len(S) and (XY_pair[S[i]] - XY_pair[S[j]]) ** 2 < d: 56 | if (S[i] - S[j]) ** 2 + (XY_pair[S[i]] - XY_pair[S[j]]) ** 2 < d: 57 | d = (S[i] - S[j]) ** 2 + (XY_pair[S[i]] - XY_pair[S[j]]) ** 2 # update d 58 | temp_pair = np.array([[S[i], XY_pair[S[i]]], [S[j], XY_pair[S[j]]]]) # update pair 59 | j += 1 60 | return d, temp_pair 61 | 62 | # for plotting 63 | def plotting(d, pair): 64 | plt.scatter(x, y, s=15) 65 | plt.text(1.35, 3, s="closest distance = %.3f" % d ** (1 / 2)) 66 | plt.plot((pair[0][0], pair[1][0]), (pair[0][1], pair[1][1]), c='r') 67 | plt.grid() 68 | plt.show() 69 | 70 | X = np.random.normal(2, 0.5, size=[16, 1]) 71 | Y = np.random.normal(2, 0.5, size=[16, 1]) 72 | XY_pair = {} 73 | 74 | for i in range(len(X)): 75 | XY_pair[X.flatten()[i]] = Y.flatten()[i] 76 | 77 | x = sorted(X.flatten()) 78 | y = [] 79 | for x_i in x: 80 | y.append(XY_pair[x_i]) 81 | 82 | dmin, pairs = closest_pair_dc(x, y, len(XY_pair)) 83 | plotting(dmin, pairs) -------------------------------------------------------------------------------- /Divide and Conquer/Closest-Pair & Convex-Hull Problem/convex-hull problem.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # solution of convex-hull problem (divide-and-conquer implementation) 7 | # step: 8 | # 1. find the leftmost and rightmost point p1 and pn 9 | # 2. split scatter points into two parts, one of which is placed on the left side of vector p1pn, 10 | # while the other is on the right side 11 | # 3. find a point pmax on both sides where the distance between p_max and vector p1pn is longest. 12 | # 4. let p1p_max and p_maxpn be the new vector 13 | # 5. find the new p_max on the left(right) side of vector in the upper(lower) hull. 14 | # 6. execute step 3-5 recursively until there are no points left. 15 | # time complexity: Θ(nlog n) 16 | 17 | import numpy as np 18 | import matplotlib.pyplot as plt 19 | 20 | np.random.seed(13) 21 | 22 | def compute_area(x1, y1, x2, y2, x3, y3): 23 | """ 24 | | x1 y1 1 | 25 | | x2 y2 1 | 26 | | x3 y3 1 | 27 | compute determinant of the matrix above to obtain the area of a triangle 28 | if positive (x3, y3) is placed on the left side of vector q1q2, 29 | otherwise, on the right side 30 | """ 31 | area = x1*y2 + x3*y1 + x2*y3 - x3*y2 - x2*y1 - x1*y3 32 | return area 33 | def compute_cos(x1, y1, x2, y2, x3, y3): 34 | # law of cosine to calculate angle 35 | a = np.sqrt((y3 - y2)**2 + (x3 - x2)**2) 36 | b = np.sqrt((y3 - y1)**2 + (x3 - x1)**2) 37 | c = np.sqrt((y2 - y1)**2 + (x2 - x1)**2) 38 | return -(b**2 + c**2 - a**2) / (2 * b * c) 39 | def find_p_max(subset, p_left, p_right): 40 | # find point p_max which has the farthest from the line p1pn 41 | x1 = p_left[0]; y1 = p_left[1] 42 | x2 = p_right[0]; y2 = p_right[1] 43 | max_area = 0 44 | for i in range(len(subset)): 45 | x3 = subset[i][0]; y3 = subset[i][1] 46 | area = compute_area(x1, y1, x2, y2, x3, y3) 47 | if area > max_area: 48 | max_area = area 49 | angle = compute_cos(x1, y1, x2, y2, x3, y3) 50 | idx = i 51 | elif area == max_area: 52 | # if the area is the same, choose the point that maximizes angle ∠pmaxppn 53 | cur_angle = compute_cos(x1, y1, x2, y2, x3, y3) 54 | if cur_angle > angle: 55 | angle = cur_angle 56 | idx = i 57 | return subset[idx] 58 | def is_in_set(p, p_left, p_right): 59 | # eliminate p and pn 60 | if p[0] == p_left[0] and p[1] == p_left[1] or p[0] == p_right[0] and p[1] == p_right[1]: 61 | return False 62 | return True 63 | def generate_subset(totalset, p_left, p_right): 64 | # return subset of upper and lower points 65 | subset = np.array([[]]) 66 | x1 = p_left[0]; y1 = p_left[1] 67 | x2 = p_right[0]; y2 = p_right[1] 68 | for i in range(len(totalset)): 69 | x3 = totalset[i][0]; y3 = totalset[i][1] 70 | area = compute_area(x1, y1, x2, y2, x3, y3) 71 | if area > 0 and is_in_set(totalset[i], p_left, p_right): 72 | if subset.shape[1] == 0: 73 | subset = np.append(subset, totalset[i][np.newaxis, :], axis=1) 74 | else: 75 | subset = np.append(subset, totalset[i][np.newaxis, :], axis=0) 76 | return subset 77 | def quick_hull(subset, p_left, p_right): 78 | # return, if no points in the subset 79 | if subset.shape[1] == 0: 80 | return 81 | p_max = find_p_max(subset, p_left, p_right) 82 | global pairs 83 | pairs = np.append(pairs, p_max[np.newaxis, :], axis=0) 84 | upper_subset = generate_subset(subset, p_left, p_max) 85 | lower_subset = generate_subset(subset, p_max, p_right) 86 | quick_hull(upper_subset, p_left, p_max) 87 | quick_hull(lower_subset, p_max, p_right) 88 | # for plotting 89 | def plotting(hull_points, scatter_points): 90 | upper_points = np.array(generate_subset(hull_points, hull_points[0], hull_points[1])) 91 | lower_points = np.array(generate_subset(hull_points, hull_points[1], hull_points[0])) 92 | x_upper = sorted(upper_points[:, 0]) 93 | x_lower = sorted(lower_points[:, 0], reverse=True) 94 | x = [hull_points[0][0]]; x.extend(x_upper); x.append(hull_points[1][0]); x.extend(x_lower); x.append(hull_points[0][0]) 95 | y = [] 96 | for key in x: 97 | y.append(XY_pair[key]) 98 | plt.scatter(scatter_points[:, 0], scatter_points[:, 1], s=15) 99 | plt.plot(x, y, c='r') 100 | plt.grid() 101 | plt.show() 102 | 103 | X = np.random.normal(2, 0.5, size=[16, 1]) 104 | Y = np.random.normal(2, 0.5, size=[16, 1]) 105 | XY = np.column_stack((X, Y)) 106 | pairs = np.array([[]]) 107 | XY_pair = {} 108 | 109 | for i in range(len(X)): 110 | XY_pair[X.flatten()[i]] = Y.flatten()[i] 111 | x = sorted(X.flatten()) 112 | p_left = np.array([[x[0], XY_pair[x[0]]]]) 113 | p_right = np.array([[x[-1], XY_pair[x[-1]]]]) 114 | 115 | pairs = np.append(pairs, p_left, axis=1) 116 | pairs = np.append(pairs, p_right, axis=0) 117 | 118 | upper_subset = np.array(generate_subset(XY, p_left[0], p_right[0])) 119 | lower_subset = np.array(generate_subset(XY, p_right[0], p_left[0])) 120 | 121 | quick_hull(upper_subset, p_left[0], p_right[0]) 122 | quick_hull(lower_subset, p_right[0], p_left[0]) 123 | plotting(pairs, XY) -------------------------------------------------------------------------------- /Divide and Conquer/Merge Sort/merge sort.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # implementation of merge sort 7 | # to sort a random array with 10,000 elements 8 | # two metrics ("number of comparison" and "consumed time") for efficiency evaluation 9 | # time complexity: Θ(nlog n) in all cases 10 | # space complexity Θ(n) 11 | # stability: stable 12 | 13 | import time 14 | import random 15 | 16 | def merge(a, b): 17 | i, j, k = 0, 0, 0 # initialize variables 18 | p, q = len(a), len(b) 19 | global comparison # global variable 20 | c = [0 for i in range(p+q)] 21 | while i < p and j < q: 22 | if a[i] <= b[j]: 23 | c[k] = a[i]; i += 1 24 | else: 25 | c[k] = b[j]; j += 1 26 | k += 1 27 | comparison += 1 28 | while i < p: 29 | c[k] = a[i] 30 | i += 1; k += 1 31 | while j < q: 32 | c[k] = b[j] 33 | j += 1; k += 1 34 | return c 35 | 36 | def merge_sort(array): 37 | if len(array) <= 1: 38 | return array 39 | m = len(array) // 2 # middle position 40 | A = merge_sort(array[: m]) 41 | B = merge_sort(array[m:]) 42 | C = merge(A, B) 43 | return C 44 | 45 | time_in_total = 0 46 | epoch = 5 # num of iteration 47 | total_comparison = 0 48 | 49 | for i in range(epoch): 50 | time_start = time.time() 51 | array = [random.randint(0,10000) for i in range(10000)] 52 | comparison = 0 53 | merge_sort(array) 54 | time_finish = time.time() 55 | total_comparison += comparison 56 | time_in_total += time_finish - time_start 57 | print("Epoch {}: \n number of comparison: {}\n time consumed: {:.4f} s".format(i+1, comparison, time_finish-time_start)) 58 | print("average number of comparison: %d" % (total_comparison/epoch)) 59 | print('average time consumed: {:.4f} s'.format(time_in_total/epoch)) 60 | 61 | -------------------------------------------------------------------------------- /Divide and Conquer/Quick Sort/array.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | import random 7 | 8 | # array generator for different cases (average case and worst case) 9 | 10 | class generator(object): 11 | def __init__(self, size, seed): 12 | self.size = size 13 | random.seed = seed # for reproducibility 14 | def avg(self): 15 | return [random.randint(0,self.size) for i in range(self.size)] 16 | def worst(self): 17 | return sorted([random.randint(0,self.size) for i in range(self.size)]) 18 | 19 | 20 | -------------------------------------------------------------------------------- /Divide and Conquer/Quick Sort/quick sort.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # implementation of quick sort 7 | # to sort a random array with 10,000 elements 8 | # two metrics ("number of comparison" and "consumed time") for efficiency evaluation 9 | # time complexity: Θ(nlog n) for the best and average case and Θ(n^2) for the worst case 10 | # space complexity Θ(1) 11 | # stability: unstable 12 | 13 | import time 14 | import sys 15 | from array import generator 16 | 17 | sys.setrecursionlimit(10010) # set maximum recursion depth to 10000+ 18 | 19 | def partition(a): 20 | l = 0; r = len(a) - 1 21 | pivot = a[l] 22 | s = l 23 | global comparison 24 | for i in range(l+1, r+1): 25 | if a[i] < pivot: 26 | s += 1 27 | a[s], a[i] = a[i], a[s] # swap a[s] with a[i] 28 | comparison += 1 29 | a[l], a[s] = a[s], a[l] # swap a[l] with a[s] 30 | return s 31 | 32 | def quick_sort(array): 33 | if len(array) > 1: 34 | s = partition(array) 35 | array[0: s] = quick_sort(array[0: s]) # left sub-array 36 | array[s+1: len(array)] = quick_sort(array[s+1: len(array)]) # right sub-array 37 | return array 38 | 39 | time_in_total = 0 40 | epoch = 5 # num of iteration 41 | total_comparison = 0 42 | 43 | # instantiate a generator 44 | gen = generator(10000, 1) 45 | 46 | # average case Θ(nlog n) 47 | print("Average Case: ") 48 | for i in range(epoch): 49 | array = gen.avg() 50 | time_start = time.time() 51 | comparison = 0 52 | quick_sort(array) 53 | time_finish = time.time() 54 | total_comparison += comparison 55 | time_in_total += time_finish-time_start 56 | print("Epoch {}: \n number of comparison: {}\n time consumed: {:.4f} s".format(i+1, comparison, time_finish-time_start)) 57 | print("average number of comparison: %d" % (total_comparison/epoch)) 58 | print('average time consumed: {:.4f} s'.format(time_in_total/epoch)) 59 | 60 | total_comparison = 0; time_in_total = 0 61 | # worst case Θ(n^2) 62 | print("Worst Case: ") 63 | for i in range(epoch): 64 | array = gen.worst() 65 | time_start = time.time() 66 | comparison = 0 67 | quick_sort(array) 68 | time_finish = time.time() 69 | total_comparison += comparison 70 | time_in_total += time_finish-time_start 71 | print("Epoch {}: \n number of comparison: {}\n time consumed: {:.4f} s".format(i+1, comparison, time_finish-time_start)) 72 | print("average number of comparison: %d" % (total_comparison/epoch)) 73 | print('average time consumed: {:.4f} s'.format(time_in_total/epoch)) 74 | -------------------------------------------------------------------------------- /Dynamic Programming/Classic Problems/change making.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # change-making problem solved by dynamic programming 7 | # given coins with the different denominations d1 F(i-dj) for each dj in {d}, 15 | # then the denomination dj will be as the part of combination 16 | # time complexity: Θ(mn); space complexity: Θ(n) 17 | 18 | from math import inf 19 | 20 | def change_making_bf(money, amount): 21 | if amount == 0: 22 | return 0 23 | cnt = inf 24 | for m in money: 25 | if m <= amount: 26 | cnt = min(cnt, change_making_bf(money, amount-m) + 1) 27 | return cnt 28 | 29 | def change_making_dp(money, amount): 30 | optima = [0 for i in range(amount+1)] 31 | global combination 32 | for i in range(1, amount+1): 33 | temp = inf 34 | j = 0 35 | # min({F(n-di)|1<=i<=m}) if n-di > 0 36 | while j < len(money) and i >= money[j]: 37 | temp = min(temp, optima[i-money[j]]) 38 | j += 1 39 | optima[i] = temp + 1 40 | # backtracking 41 | while amount > 0: 42 | temp = inf; idx = 0 43 | for k in range(len(money)): 44 | if amount-money[k] < 0: 45 | break 46 | if optima[amount-money[k]] < temp: 47 | temp = optima[amount-money[k]] 48 | idx = k 49 | combination.append(money[idx]) 50 | amount -= money[idx] 51 | return optima[-1] 52 | 53 | 54 | denomination = [1, 5, 10, 20, 50, 100] 55 | amount = 396 56 | combination = [] 57 | print("Change-making problem solved by dynamic programming: ") 58 | print("Denominations:\n", denomination) 59 | print("The minimum number of coins whose values add up to {} is {}".format(amount, change_making_dp(denomination, amount))) 60 | print("The optimal combination is:\n", combination) -------------------------------------------------------------------------------- /Dynamic Programming/Classic Problems/coin row.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # coin-row problem solved by dynamic programming 7 | # suppose there is a row of n coins, whose values are d1, d2, ..., dm 8 | # the goal is to pick up a combination that maximize the amount of money, 9 | # subject to the constraint that two adjacent coins cannot be picked up concurrently 10 | # an array `optima` with length n is created to store temporary optimal value 11 | # each entry in `optima` is calculated by the formula: F(i) = max(F(i-1), F(i-2)+Ci) 12 | # where Ci represents the ith coin in a row of n coins 13 | # optimal combination is found by backtracking 14 | # in particular, if F(i) > F(i-1), then the ith coin will be as the part of combination 15 | # time complexity: Θ(n); space complexity: Θ(n) 16 | 17 | def coin_row_bf(coins, n): 18 | if n == 0: 19 | return 0 20 | if n == 1: 21 | return coins[n-1] # ignore adjacent coin 22 | return max(coin_row_bf(coins, n-2), coin_row_bf(coins, n-1) + coins[n-1]) 23 | 24 | def coin_row_dp(coins, n): 25 | optima = [-1 for i in range(n+1)] 26 | optima[0] = 0; optima[1] = coins[0] 27 | global combination 28 | for i in range(2, n+1): 29 | # max(F(n-2), F(n-1)+Cn) 30 | if optima[i-1] < optima[i-2] + coins[i-1]: 31 | optima[i] = optima[i-2] + coins[i-1] 32 | else: 33 | optima[i] = optima[i-1] 34 | # backtracking 35 | while n > 0: 36 | if optima[n] > optima[n-1]: 37 | combination[n-1] = 1 38 | n -= 2 39 | else: 40 | n -= 1 41 | return optima[-1] 42 | 43 | 44 | coins = [10, 20, 50, 20, 5, 20, 10, 50] 45 | # 1 means the coin in the position will be picked up, while 0 does not 46 | # e.g. [1, 0, 1, 0, 1, 0, 1] means the 1st, 3rd, 5th and 7th coin are selected 47 | combination = [0 for i in range(len(coins))] 48 | print("Coin-row problem solved by dynamic programming:") 49 | print("Coin arrangement: \n", coins) 50 | print("The maximum amount of money is %d." % coin_row_dp(coins, len(coins))) 51 | print("The choice for each coin:\n", combination) -------------------------------------------------------------------------------- /Dynamic Programming/Floyd's Algorithm/Floyd.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # implementation of Floyd's algorithm 7 | # this algorithm is for solving all-pairs shortest-paths problem 8 | # starting from a distance matrix `d`, where infinity represents there is no direct path from one vertex to another 9 | # and real number stands for distance between two vertices, 10 | # the nested `for` loop compares the distance 11 | # from ith vertex to jth vertex with the intermediate vertices numbered not higher than k-1 12 | # and the distance from vertex i to vertex j with the intermediate vertices numbered not higher than k 13 | # specifically, min(d[i][j], d[i][k]+d[k][j]) 14 | # then choose the smaller one as the entry of ith row, jth column 15 | # after iteration, the distance matrix shows all-pairs shortest path 16 | # time complexity: Θ(n^3), space complexity: Θ(n^2) 17 | 18 | import numpy as np 19 | from math import inf 20 | 21 | def Floyd(d): 22 | n = d.shape[0] 23 | for k in range(n): 24 | for i in range(n): 25 | for j in range(n): 26 | d[i][j] = min(d[i][j], d[i][k]+d[k][j]) 27 | print("After %d iteration:\n" % (k+1), d) 28 | return d 29 | 30 | 31 | matrix = np.array([[0, 7, inf, inf, 1, 6], 32 | [7, 0, 2, 4, inf, inf], 33 | [inf, 2, 0, 9, 3, inf], 34 | [inf, 4, 9, 0, inf, inf], 35 | [1, inf, 3, inf, 0, 3], 36 | [6, inf, inf, inf, 3, 0]]) 37 | 38 | print("Original matrix:\n", matrix) 39 | d_matrix = Floyd(matrix) 40 | 41 | # testing 42 | bundle = [[1, 4], [3, 6], [5, 2]] 43 | for i, (start, end) in enumerate(bundle): 44 | print("Test %d" % (i+1)) 45 | print("The shortest distance between vertex {} and vertex {} is {}". 46 | format(start, end, int(d_matrix[start-1][end-1]))) 47 | -------------------------------------------------------------------------------- /Dynamic Programming/Knapsack Problem/knapsack(bottom-up).py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # knapsack problem solved by dynamic programming(bottom-up) 7 | # a two-dimensional array `table` initialized to 0 is created for storing temporary optima: table[i][j], 8 | # which is subject to the constraint that 1<=i<=n, 1<=j<=W, where n = |weights|, and padded with 0 for i, j = 0 9 | # entry in the table is calculated by the formula: 10 | # table[i][j] = max(table[i-1][j], table[i-1][j-wi]+vi) if j < wi 11 | # table[i][j] = table[i-1][j] if j >= wi 12 | # bottom-up method will calculate each entry in the table, i from 1 to n, j from 1 to W 13 | # then the entry in the |weights|th row and Wth column is returned as the final solution 14 | # optimal combination is found by backtracking 15 | # in particular, if table[i][j] > table[i-1][j], then the ith item will be as the part of combination 16 | # time complexity: Θ(nW); space complexity: Θ(nW) 17 | 18 | import numpy as np 19 | 20 | def backtracking(table, weights): 21 | i = len(table)-1; j = len(table[0])-1 22 | items = [] 23 | while table[i][j] != 0: 24 | if table[i][j] > table[i-1][j]: 25 | items.append(i) 26 | j = j - weights[i-1] 27 | i = i - 1 28 | items.reverse() 29 | return items 30 | 31 | def knapsack_bottom_up(weights, values, capacity): 32 | n = len(weights) # number of items 33 | table = np.zeros([n+1, capacity+1], dtype=np.int) 34 | print("Before iteration:\n", table) 35 | for i in range(1, n+1): 36 | for j in range(1, capacity+1): 37 | wi = weights[i-1]; vi = values[i-1] 38 | if j < wi: 39 | table[i][j] = table[i-1][j] 40 | else: 41 | table[i][j] = max(table[i-1][j], table[i-1][j-wi] + vi) 42 | print("After iteration:\n", table) 43 | return table[n][capacity], table 44 | 45 | weights = [7, 3, 4, 5] 46 | values = [42, 12, 40, 25] 47 | W = 10 48 | 49 | print("Knapsack problem solved by dynamic programming(bottom-up):") 50 | max_value, table = knapsack_bottom_up(weights, values, W) 51 | combination = backtracking(table, weights) 52 | print("The optimal combination is: ") 53 | for i in range(len(combination)-1): 54 | print("item %d" % combination[i], end=', ') 55 | print("item %d." % combination[-1]) 56 | print("The total value is %d." % max_value) -------------------------------------------------------------------------------- /Dynamic Programming/Knapsack Problem/knapsack(top-down).py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # knapsack problem solved by dynamic programming(top-down) 7 | # we create a two-dimensional array `table` initialized to special symbol(-1) 8 | # which is subject to the constraint that 1<=i<=n, 1<=j<=W, where n = |weights|, and padded with 0 for i, j = 0 9 | # entry in the table is calculated by the formula: 10 | # table[i][j] = max(table[i-1][j], table[i-1][j-wi]+vi) if j < wi 11 | # table[i][j] = table[i-1][j] if j >= wi 12 | # top-down method only calculates part of entries 13 | # whenever a new value needs to be calculated, the corresponding entry is checked 14 | # if the entry is equal to -1, the recursion goes deeper until base case 15 | # otherwise, the value in the entry is retrieved from the table 16 | # the entry in the |weights|th row and Wth column is returned as the final solution 17 | # optimal combination is found by backtracking 18 | # in particular, if table[i][j] > table[i-1][j], then the ith item will be as the part of combination 19 | # time complexity: Θ(nW); space complexity: Θ(nW) 20 | 21 | import numpy as np 22 | 23 | def backtracking(table, weights): 24 | i = len(table)-1; j = len(table[0])-1 25 | items = [] 26 | while table[i][j] != -1 and table[i][j] != 0: 27 | if table[i][j] > table[i-1][j]: 28 | items.append(i) 29 | j = j - weights[i-1] 30 | i = i - 1 31 | items.reverse() 32 | return items 33 | 34 | def knapsack_top_down(t, w, v, i, j): 35 | if t[i][j] < 0: 36 | if j < w[i-1]: 37 | t[i][j] = knapsack_top_down(t, w, v, i-1, j) 38 | else: 39 | t[i][j] = max(knapsack_top_down(t, w, v, i-1, j), knapsack_top_down(t, w, v, i-1, j-w[i-1])+v[i-1]) 40 | return t[i][j] 41 | 42 | 43 | def knapsack_top_down_main(weights, values, capacity): 44 | n = len(weights) 45 | table = np.zeros([n+1, capacity+1], dtype=np.int) - 1 46 | table[0, :] = 0; table[:, 0] = 0 # pad with 0 in the first row and column 47 | print("Before recursion:\n", table) 48 | maximum = knapsack_top_down(table, weights, values, n, capacity) 49 | print("After recursion:\n", table) 50 | return maximum, table 51 | 52 | 53 | weights = [7, 3, 4, 5] 54 | values = [42, 12, 40, 25] 55 | W = 10 56 | 57 | print("Knapsack problem solved by dynamic programming(top-down):") 58 | max_value, table = knapsack_top_down_main(weights, values, W) 59 | combination = backtracking(table, weights) 60 | print("The optimal combination is: ") 61 | for i in range(len(combination)-1): 62 | print("item %d" % combination[i], end=', ') 63 | print("item %d." % combination[-1]) 64 | print("The total value is %d." % max_value) -------------------------------------------------------------------------------- /Greedy Algorithm/Dijkstra's Algorithm/Dijkstra.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # implementation of Dijkstra's algorithm to solve single-source shortest path problem 7 | # for each vertex, it has two domains, one is for saving the previous vertex, and another 8 | # one is for saving the minimal distance from the source node. 9 | # The algorithm starts picking a source, and set distance value to zero, while others to infinity 10 | # mark the source node as `visited`. For each `unvisited` edges neighbouring the current vertex, 11 | # add them into a min-heap, then `fix_up`, and update adjacent vertex's `min_distance` and `parent` 12 | # based on the formula: min(v, u+edge(u, v)), 13 | # where u is the current vertex, and v is the corresponding adjacent vertex 14 | # pop up the root element from the heap before `fix_down` and if both connecting vertices are not `visited`, 15 | # we assign the `unvisited` vertex as current vertex 16 | # iterate the above steps until `V_set` is empty 17 | 18 | from math import inf 19 | 20 | class Vertex(object): 21 | def __init__(self, name): 22 | self.name = name 23 | self.neighbours = [] 24 | self.visited = False 25 | self.predecessor = None 26 | self.min_dis = inf 27 | 28 | class Edge(object): 29 | def __init__(self, pre, next, weight): 30 | self.pre = pre 31 | self.next = next 32 | self.visited = False 33 | self.weight = weight 34 | 35 | def fix_up(queue): 36 | temp_idx = len(queue) - 1 37 | parent_idx = (temp_idx - 1) // 2 38 | heap = False 39 | while not heap and parent_idx >= 0: 40 | # swap elements if heap property is violated 41 | if queue[temp_idx].weight < queue[parent_idx].weight: 42 | queue[temp_idx], queue[parent_idx] = queue[parent_idx], queue[temp_idx] 43 | else: 44 | heap = True 45 | # update indices 46 | temp_idx = parent_idx 47 | parent_idx = (temp_idx - 1) // 2 48 | def fix_down(queue): 49 | temp_idx = 0; size = len(queue) 50 | temp_ver = queue[temp_idx] # save vertex temporarily 51 | heap = False 52 | while not heap and 2 * temp_idx + 1 < size: 53 | j = 2 * temp_idx + 1 # index of left child 54 | # right child exists 55 | if j < size - 1: 56 | # compare two children's weight 57 | if queue[j].weight > queue[j + 1].weight: 58 | j = j + 1 59 | # whether violate heap property or not 60 | if queue[j].weight >= temp_ver.weight: 61 | heap = True 62 | else: 63 | queue[temp_idx] = queue[j] 64 | temp_idx = j # update temp_idx 65 | queue[temp_idx] = temp_ver 66 | def get_next_ver(u, e): 67 | if e.pre is u: 68 | return e.next 69 | return e.pre 70 | def Dijkstra(G, source): 71 | V, _ = G # retrieve vertices and edges from G 72 | v_set = V # vertices set 73 | pqueue = [] # priority queue 74 | source.visited = True; v_set.remove(source) 75 | cur = source; cur.min_dis = 0 # initialize source's min distance to 0 76 | while v_set: 77 | for e in cur.neighbours: 78 | if not e.visited: 79 | e.visited = True 80 | pqueue.append(e) # add to the priority queue 81 | fix_up(pqueue) 82 | temp = get_next_ver(cur, e) 83 | # min(u.min_dis+e(u, v), v.min_dis) 84 | if cur.min_dis + e.weight < temp.min_dis: 85 | temp.min_dis = cur.min_dis + e.weight 86 | temp.predecessor = cur 87 | pqueue[0], pqueue[-1] = pqueue[-1], pqueue[0] 88 | temp = pqueue.pop() # get rid of the root element 89 | fix_down(pqueue) 90 | if not temp.pre.visited: 91 | cur = temp.pre 92 | elif not temp.next.visited: 93 | cur = temp.next 94 | else: 95 | continue # the next vertex has been visited 96 | cur.visited = True 97 | v_set.remove(cur) 98 | def get_shortest_path(v): 99 | path=[] 100 | while v is not None: 101 | path.insert(0, v.name) 102 | v = v.predecessor 103 | return path 104 | 105 | 106 | # instantiate vertices and edges 107 | A = Vertex('A'); B = Vertex('B'); C = Vertex('C') 108 | D = Vertex('D'); E = Vertex('E'); F = Vertex('F') 109 | E1 = Edge(A, B, 7); E2 = Edge(A, E, 1); E3 = Edge(A, F, 6); E4 = Edge(B, C, 2) 110 | E5 = Edge(B, D, 4); E6 = Edge(C, D, 9); E7 = Edge(C, E, 3); E8 = Edge(E, F, 3) 111 | 112 | A.neighbours = [E1, E2, E3]; B.neighbours = [E1, E4, E5]; C.neighbours = [E4, E6, E7] 113 | D.neighbours = [E5, E6]; E.neighbours = [E2, E7, E8]; F.neighbours = [E3, E8] 114 | 115 | # stack them together 116 | V_set = [A, B, C, D, E, F] 117 | E_set = [E1, E2, E3, E4, E5, E6, E7, E8] 118 | 119 | Graph = [V_set, E_set] 120 | source = A 121 | targets = [C, D, F] 122 | 123 | print("Dijkstra's algorithm:") 124 | Dijkstra(Graph, source) 125 | print("The source vertex is %c" % source.name) 126 | for target in targets: 127 | print("The shortest path from %c to %c is: " % (source.name, target.name)) 128 | path = get_shortest_path(target) 129 | for v in path[:-1]: 130 | print(v, end='->') 131 | print(path[-1]) 132 | print("The minimal distance is %d" % target.min_dis) -------------------------------------------------------------------------------- /Greedy Algorithm/Huffman Tree/Huffman.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # implementation of Huffman tree 7 | # initialize n single-node trees containing symbols of the alphabet given, 8 | # and compute the frequency of each symbol and record it into the node 9 | # create a priority queue for saving nodes 10 | # for each iteration, we pop two nodes with the smallest frequency out of the queue, 11 | # replacing them with a new node whose frequency is the sum of these two 12 | # and set the left child and right child to these two nodes respectively 13 | # the loop is over until a single tree is obtained 14 | # for Huffman code, starting from the Huffman tree's root node, 15 | # the codeword is obtained by searching the symbol leaf node, 16 | # and during the search process, append 0 to the node which is the left child of its parent node 17 | # otherwise, if it is the right child, append 1 18 | 19 | 20 | class Node(object): 21 | def __init__ (self, char, freq): 22 | self.char = char # character 23 | self.freq = freq # frequency 24 | self.left = None # left child 25 | self.right = None # right child 26 | self.code = '' # Huffman code 27 | class Queue(object): 28 | def __init__ (self): 29 | self.queue = None # for saving nodes 30 | self.length = 0 # length of queue 31 | def create(self, dict): 32 | queue = [] 33 | # enqueue 34 | for char, freq in dict: 35 | queue.append(Node(char, freq)) 36 | self.queue = queue 37 | self.length = len(queue) 38 | def add(self, node): 39 | # queue is empty 40 | if self.length == 0: 41 | self.queue.append(node) 42 | else: 43 | # search and insert 44 | for i in range(self.length): 45 | if self.queue[i].freq >= node.freq: 46 | self.queue.insert(i+1, node) 47 | break 48 | else: 49 | # node to be inserted is the tail of the queue 50 | self.queue.insert(self.length, node) 51 | self.length += 1 # increment queue's length by 1 52 | def remove(self): 53 | self.length -= 1 # decrement queue's length by 1 54 | return self.queue.pop(0) 55 | class HuffmanTree(object): 56 | def __init__ (self, string): 57 | self.string = string 58 | self.char2code = {} # map from characters to codewords 59 | self.code2char = {} # map from codewords to characters 60 | def compute_freq(self): 61 | string = self.string 62 | char_freq = {} 63 | for char in string: 64 | char_freq[char] = char_freq.get(char, 0) + 1 # count the frequency of each character 65 | return sorted(char_freq.items(), key=lambda x:x[1]) 66 | def build(self, queue): 67 | while queue.length != 1: 68 | u = queue.remove(); v = queue.remove() 69 | w = Node(None, u.freq + v.freq) # create a new node `w` whose `freq` is the sum of u's `freq` and v's `freq` 70 | w.left = u; w.right = v # node w points to u and v 71 | queue.add(w) 72 | return queue.remove() 73 | def map(self, root, x=''): 74 | if root: 75 | # visit children nodes recursively 76 | self.map(root.left, x+'0') 77 | root.code = x 78 | if root.char: 79 | self.char2code[root.char] = root.code 80 | self.code2char[root.code] = root.char 81 | self.map(root.right, x+'1') 82 | def encode(self, string): 83 | code = '' 84 | for char in string: 85 | code += self.char2code[char] 86 | return code 87 | def decode(self, code): 88 | string = ''; pattern = '' 89 | for c in code: 90 | pattern += c 91 | # pattern in the dictionary 92 | if pattern in self.code2char: 93 | string += self.code2char[pattern] 94 | pattern = '' 95 | return string 96 | 97 | 98 | string = 'ADCEBDABCCADEDBCACBD' 99 | print("The original string is " + string) 100 | ht = HuffmanTree(string) 101 | dic = ht.compute_freq() 102 | print("The frequency of each character: ", dic) 103 | # instantiate an empty priority queue 104 | pq = Queue() 105 | # create a priority queue and construct Huffman tree 106 | pq.create(dic) 107 | root = ht.build(pq) 108 | ht.map(root) 109 | print("Map from character to codeword: ", ht.char2code) 110 | print("Map from codeword to character: ", ht.code2char) 111 | code = ht.encode(string) 112 | print("After encoding: ", code) 113 | print("After decoding: ", ht.decode(code)) -------------------------------------------------------------------------------- /Greedy Algorithm/Kruskal's Algorithm/Kruskal.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # implementation of Kruskal's algorithm to solve minimum spanning tree 7 | # this algorithm starts sorting the entire edge set in nondecreasing order based on their weight 8 | # then create a forest and each vertex is a separate vertex 9 | # repeat the following steps until the spanning tree is complete(num_processed_edge = |V| - 1): 10 | # - remove the minimum weighted edge from edge set 11 | # - if two vertices connected to this edge comes from the separate set, 12 | # merge them together by its rank and the edge can be as the part of spanning tree 13 | # - otherwise, ignore this edge 14 | # time complexity: Θ(|E|log|E|) 15 | 16 | class Vertex(object): 17 | def __init__(self, name): 18 | self.name = name 19 | self.parent = self # point to itself 20 | self.rank = 0 21 | 22 | class Edge(object): 23 | def __init__(self, pre, next, weight): 24 | self.pre = pre 25 | self.next = next 26 | self.weight = weight 27 | 28 | class DisjointSet(object): 29 | def find(self, v): 30 | if v.parent is not v: 31 | return self.find(v.parent) 32 | return v 33 | def is_same_set(self, u, v): 34 | return self.find(u) is self.find(v) 35 | def union(self, u, v): 36 | u_rep = self.find(u); v_rep = self.find(v) # get the representative vertex 37 | # u_rep's rank is greater than v_rep's rank 38 | if u_rep.rank > v_rep.rank: 39 | v_rep.parent = u_rep 40 | u_rep.rank = max(u_rep.rank, v_rep.rank+1) 41 | # v_rep's rank is smaller than or equal to u_rep's rank 42 | else: 43 | u_rep.parent = v_rep 44 | v_rep.rank = max(v_rep.rank, u_rep.rank+1) 45 | 46 | def Kruskal(G): 47 | V, E = G # retrieve vertices and edges from G 48 | E.sort(key=lambda x: x.weight) # sort edges in nondecreasing order such that e1.w < e2.w < ... < e|E|.w 49 | total_cost = 0 # initialize total cost to 0 50 | encounter = 0 # num of processed edges 51 | d_set = DisjointSet() 52 | for e in E: 53 | u = e.pre; v = e.next # retrieve connecting vertices from an edge 54 | # whether two vertices are in the same set 55 | if not d_set.is_same_set(u, v): 56 | print(u.name + '--' + v.name) 57 | d_set.union(u, v) 58 | encounter += 1 59 | total_cost += e.weight 60 | else: 61 | continue 62 | # spanning tree has completed 63 | if encounter >= len(V) - 1: 64 | break 65 | return total_cost 66 | 67 | 68 | # instantiate vertices and edges 69 | A = Vertex('A'); B = Vertex('B'); C = Vertex('C'); D = Vertex('D') 70 | E = Vertex('E'); F = Vertex('F'); G = Vertex('G') 71 | E1 = Edge(A, B, 2); E2 = Edge(A, C, 6); E3 = Edge(A, E, 5); E4 = Edge(A, F, 10); E5 = Edge(B, D, 3); E6 = Edge(B, E, 3) 72 | E7 = Edge(C, D, 1); E8 = Edge(C, F, 2); E9 = Edge(D, E, 4); E10 = Edge(D, G, 5); E11 = Edge(F, G, 5) 73 | 74 | A.neighbours = [E1, E2, E3, E4]; B.neighbours = [E1, E5, E6]; C.neighbours = [E2, E7, E8] 75 | D.neighbours = [E5, E7, E9, E10]; E.neighbours = [E3, E6, E9]; F.neighbours = [E4, E8, E11]; G.neighbours = [E10, E11] 76 | 77 | # stack them together 78 | V_set = [A, B, C, D, E, F, G] 79 | E_set = [E1, E2, E3, E4, E5, E6, E7, E8, E9, E10, E11] 80 | 81 | Graph = [V_set, E_set] 82 | 83 | print("Kruskal's algorithm:") 84 | print("The minimum spanning tree comprises of the following edges:") 85 | cost = Kruskal(Graph) 86 | print("The total cost is %d units" % cost) 87 | -------------------------------------------------------------------------------- /Greedy Algorithm/Prim's Algorithm/Prim.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # implementation of Prim's algorithm to solve minimum spanning tree problem 7 | # starting from a picked vertex as source, 8 | # the algorithm builds a min-heap from edges associated with the current vertex by `fix_up`, 9 | # then pop the root element out of the heap as the part of spanning tree and reconstruct heap by `fix_down`. 10 | # after that, we mark the current vertex as `visited`, remove from the `V_set`, and jump to the next vertex, 11 | # if the next one has been `unvisited`, otherwise, pop another element from the heap's top and check again. 12 | # the previous several steps are looped until `V_set` is empty 13 | # time complexity: Θ(|E|log|V|) 14 | 15 | class Vertex(object): 16 | def __init__(self, name): 17 | self.name = name 18 | self.neighbours = [] 19 | self.visited = False 20 | 21 | class Edge(object): 22 | def __init__(self, pre, next, weight): 23 | self.pre = pre 24 | self.next = next 25 | self.visited = False 26 | self.weight = weight 27 | 28 | def fix_up(queue): 29 | temp_idx = len(queue) - 1 30 | parent_idx = (temp_idx - 1) // 2 31 | heap = False 32 | while not heap and parent_idx >= 0: 33 | # swap elements if heap property is violated 34 | if queue[temp_idx].weight < queue[parent_idx].weight: 35 | queue[temp_idx], queue[parent_idx] = queue[parent_idx], queue[temp_idx] 36 | else: 37 | heap = True 38 | # update indices 39 | temp_idx = parent_idx 40 | parent_idx = (temp_idx - 1) // 2 41 | def fix_down(queue): 42 | temp_idx = 0; size = len(queue) 43 | temp_ver = queue[temp_idx] # save vertex temporarily 44 | heap = False 45 | while not heap and 2 * temp_idx + 1 < size: 46 | j = 2 * temp_idx + 1 # index of left child 47 | # right child exists 48 | if j < size - 1: 49 | # compare two children's weight 50 | if queue[j].weight > queue[j + 1].weight: 51 | j = j + 1 52 | # whether violate heap property or not 53 | if queue[j].weight >= temp_ver.weight: 54 | heap = True 55 | else: 56 | queue[temp_idx] = queue[j] 57 | temp_idx = j # update temp_idx 58 | queue[temp_idx] = temp_ver 59 | def Prim(G, source): 60 | V, _ = G # retrieve vertices and edges from G 61 | v_set = V # vertices set 62 | pqueue = [] # priority queue 63 | source.visited = True; v_set.remove(source); cur = source 64 | total_cost = 0 # initialize total cost to 0 65 | while v_set: 66 | for e in cur.neighbours: 67 | if not e.visited: 68 | e.visited = True 69 | pqueue.append(e) # add to the priority queue 70 | fix_up(pqueue) 71 | pqueue[0], pqueue[-1] = pqueue[-1], pqueue[0] 72 | temp = pqueue.pop() # get rid of the root element 73 | fix_down(pqueue) 74 | if not temp.pre.visited: 75 | cur = temp.pre 76 | elif not temp.next.visited: 77 | cur = temp.next 78 | else: 79 | continue # the next vertex has been visited 80 | cur.visited = True 81 | print(temp.pre.name + '--' + temp.next.name) 82 | total_cost += temp.weight 83 | v_set.remove(cur) 84 | return total_cost 85 | 86 | 87 | # instantiate vertices and edges 88 | A = Vertex('A'); B = Vertex('B'); C = Vertex('C'); D = Vertex('D') 89 | E = Vertex('E'); F = Vertex('F'); G = Vertex('G') 90 | E1 = Edge(A, B, 2); E2 = Edge(A, C, 6); E3 = Edge(A, E, 5); E4 = Edge(A, F, 10); E5 = Edge(B, D, 3); E6 = Edge(B, E, 3) 91 | E7 = Edge(C, D, 1); E8 = Edge(C, F, 2); E9 = Edge(D, E, 4); E10 = Edge(D, G, 5); E11 = Edge(F, G, 5) 92 | 93 | A.neighbours = [E1, E2, E3, E4]; B.neighbours = [E1, E5, E6]; C.neighbours = [E2, E7, E8] 94 | D.neighbours = [E5, E7, E9, E10]; E.neighbours = [E3, E6, E9]; F.neighbours = [E4, E8, E11]; G.neighbours = [E10, E11] 95 | 96 | # stack them together 97 | V_set = [A, B, C, D, E, F, G] 98 | E_set = [E1, E2, E3, E4, E5, E6, E7, E8, E9, E10, E11] 99 | 100 | Graph = [V_set, E_set] 101 | source = A 102 | 103 | print("Prim's algorithm:") 104 | print("The source vertex is %c" % source.name) 105 | print("The minimum spanning tree comprises of the following edges:") 106 | cost = Prim(Graph, source) 107 | print("The total cost is %d units" % cost) 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /Introduction/greatest common divisor.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | -*- coding: utf-8 -*- 4 | @author: Hongzhi Fu 5 | """ 6 | 7 | # Euclid's algorithm to find the greatest common divisor with the complexity Θ(log n) 8 | 9 | def gcd(m, n): 10 | while n != 0: 11 | r = m 12 | m = n 13 | n = r % n 14 | gcd(m, n) 15 | return m 16 | 17 | 18 | m = 24; n = 16 19 | print("The greatest common divisor of {} and {} is {}".format(m, n, gcd(m, n))) 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Algorithm and Complexity 2 | 3 | ## 目录 4 | 5 | ### 介绍 (Introduction) 6 | 7 |  [1.1 什么是算法](https://github.com/infinityglow/Algorithm-and-Complexity/tree/master/Introduction) 8 | 9 | ### 基本知识 (Basic Knowledge) 10 | 11 |  [2.1 基本数据结构](https://github.com/infinityglow/Algorithm-and-Complexity/tree/master/Basic%20Knowledge/Data%20Structure)
12 |  2.2 算法的问题类型
13 | 14 | ### 复杂度分析 (Complexity Analysis) 15 | 16 |  3.1 三种表示方法:O, Ω, Θ
17 |  [3.2 复杂度分析(非递归)](https://github.com/infinityglow/Algorithm-and-Complexity/tree/master/Complexity%20Analysis/Non-recursive)
18 |  [3.3 复杂度分析(递归)](https://github.com/infinityglow/Algorithm-and-Complexity/tree/master/Complexity%20Analysis/Recursive)
19 | 20 | ### 暴力求解 (Brute Force) 21 | 22 |  [4.1 冒泡排序与选择排序](https://github.com/infinityglow/Algorithm-and-Complexity/tree/master/Brute%20Force/Bubble%20Selection%20Sort)
23 |  [4.2 顺序查找与字符串匹配(BF)](https://github.com/infinityglow/Algorithm-and-Complexity/tree/master/Brute%20Force/String%20Matching)
24 |  [4.3 图的两种遍历](https://github.com/infinityglow/Algorithm-and-Complexity/tree/master/Brute%20Force/Graph%20Traversal)
25 |  [4.4 最近点对与凸包问题(BF)](https://github.com/infinityglow/Algorithm-and-Complexity/tree/master/Brute%20Force/Closest-Pair%20%26%20Convex-Hull%20Problem)
26 |  [4.5 暴力搜索](https://github.com/infinityglow/Algorithm-and-Complexity/tree/master/Brute%20Force/Exhaustive%20Search)
27 | 28 | ### 减治法(Decrease and Conquer) 29 | 30 |  [5.1 插入排序](https://github.com/infinityglow/Algorithm-and-Complexity/tree/master/Decrease%20and%20Conquer/Insertion%20Sort)
31 |  [5.2 拓扑排序](https://github.com/infinityglow/Algorithm-and-Complexity/tree/master/Decrease%20and%20Conquer/Topological%20Sorting)
32 |  [5.3 二分查找与二叉树](https://github.com/infinityglow/Algorithm-and-Complexity/tree/master/Decrease%20and%20Conquer/Binary%20Search%20Tree)
33 |  [5.4 插值查找](https://github.com/infinityglow/Algorithm-and-Complexity/tree/master/Decrease%20and%20Conquer/Interpolation%20Search)
34 | 35 | ### 分治法(Divide and Conquer) 36 | 37 |  [6.1 归并排序](https://github.com/infinityglow/Algorithm-and-Complexity/tree/master/Divide%20and%20Conquer/Merge%20Sort)
38 |  [6.2 快速排序](https://github.com/infinityglow/Algorithm-and-Complexity/tree/master/Divide%20and%20Conquer/Quick%20Sort)
39 |  [6.3 二叉树的遍历](https://github.com/infinityglow/Algorithm-and-Complexity/tree/master/Divide%20and%20Conquer/Binary%20Tree%20Traversal)
40 |  [6.4 最近点对与凸包问题(DC)](https://github.com/infinityglow/Algorithm-and-Complexity/tree/master/Divide%20and%20Conquer/Closest-Pair%20%26%20Convex-Hull%20Problem)
41 | 42 | ### 变治法(Transform and Conquer) 43 | 44 |  [7.1 预排序](https://github.com/infinityglow/Algorithm-and-Complexity/tree/master/Transform%20and%20Conquer/Presorting)
45 |  [7.2 霍纳法则](https://github.com/infinityglow/Algorithm-and-Complexity/tree/master/Transform%20and%20Conquer/Horner's%20Rule)
46 |  [7.3 堆与堆排序](https://github.com/infinityglow/Algorithm-and-Complexity/tree/master/Transform%20and%20Conquer/Heap%20Sort)
47 |  [7.4 AVL树](https://github.com/infinityglow/Algorithm-and-Complexity/tree/master/Transform%20and%20Conquer/AVL%20Tree)
48 |  [7.5 红黑树](https://github.com/infinityglow/Algorithm-and-Complexity/tree/master/Transform%20and%20Conquer/Red-Black%20Tree)
49 |  [7.6 2-3树](https://github.com/infinityglow/Algorithm-and-Complexity/tree/master/Transform%20and%20Conquer/Two-Three%20Tree)
50 | 51 | ### 时空权衡(Time Space Tradeoff) 52 | 53 |  [8.1 计数排序](https://github.com/infinityglow/Algorithm-and-Complexity/tree/master/Time-Space%20Tradeoff/Counting%20Sort)
54 |  [8.2 字符串匹配(TST)](https://github.com/infinityglow/Algorithm-and-Complexity/tree/master/Time-Space%20Tradeoff/String%20Matching)
55 |  [8.3 哈希](https://github.com/infinityglow/Algorithm-and-Complexity/tree/master/Time-Space%20Tradeoff/Hashing/)
56 | 57 | ### 动态规划(Dynamic Programming) 58 | 59 |  [9.1 关于钱的两个经典问题](https://github.com/infinityglow/Algorithm-and-Complexity/tree/master/Dynamic%20Programming/Classic%20Problems)
60 |  [9.2 背包问题(DP)](https://github.com/infinityglow/Algorithm-and-Complexity/tree/master/Dynamic%20Programming/Knapsack%20Problem)
61 |  [9.3 弗洛伊德算法](https://github.com/infinityglow/Algorithm-and-Complexity/tree/master/Dynamic%20Programming/Floyd's%20Algorithm)
62 | 63 | ### 贪心算法(Greedy Algorithm) 64 | 65 |  [10.1 普林姆算法](https://github.com/infinityglow/Algorithm-and-Complexity/tree/master/Greedy%20Algorithm/Prim's%20Algorithm)
66 |  [10.2 克鲁斯卡尔算法](https://github.com/infinityglow/Algorithm-and-Complexity/tree/master/Greedy%20Algorithm/Kruskal's%20Algorithm)
67 |  [10.3 迪克斯特拉算法](https://github.com/infinityglow/Algorithm-and-Complexity/tree/master/Greedy%20Algorithm/Dijkstra's%20Algorithm)
68 |  [10.4 哈弗曼树](https://github.com/infinityglow/Algorithm-and-Complexity/tree/master/Greedy%20Algorithm/Huffman%20Tree)
69 | 70 | ### *高阶算法(Advanced Algorithm) 71 | 72 |  [*回溯](https://github.com/infinityglow/Algorithm-and-Complexity/tree/master/Advanced%20Algorithm/Backtracking)
73 |  [*分支限界](https://github.com/infinityglow/Algorithm-and-Complexity/tree/master/Advanced%20Algorithm/Branch%20and%20Bound)
74 |  *千禧问题:P = NP ?
75 | -------------------------------------------------------------------------------- /Time-Space Tradeoff/Counting Sort/counting sort.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # implementation of counting sort 7 | # to sort a random array with 10,000 elements 8 | # two metrics ("number of comparison" and "consumed time") for efficiency evaluation 9 | # time complexity: Θ(n+k) in all cases, 10 | # where n is the number of elements in an array and k is the number of buckets for counting 11 | # space complexity Θ(k) 12 | # stability: unstable 13 | 14 | import time 15 | import random 16 | 17 | def counting_sort(array): 18 | largest = max(array); smallest = min(array) # get the largest and smallest value 19 | counter = [0 for i in range(largest-smallest+1)] # empty counter array for counting 20 | idx = 0 # index for rearranging array 21 | cnt = 0 22 | for i in range(len(array)): 23 | counter[array[i]-smallest] += 1 24 | for j in range(len(counter)): 25 | while counter[j] > 0: 26 | array[idx] = j + smallest 27 | idx += 1 28 | counter[j] -= 1 29 | return cnt 30 | 31 | 32 | time_in_total = 0 33 | epoch = 5 # num of iteration 34 | total_comparison = 0 35 | 36 | for i in range(epoch): 37 | time_start = time.time() 38 | array = [random.randint(0,10000) for i in range(10000)] 39 | comparison = counting_sort(array) 40 | time_finish = time.time() 41 | total_comparison += comparison 42 | time_in_total += time_finish - time_start 43 | print("Epoch {}: \n number of comparison: {}\n time consumed: {:.4f} s".format(i+1, comparison, time_finish-time_start)) 44 | print("average number of comparison: %d" % (total_comparison/epoch)) 45 | print('average time consumed: {:.4f} s'.format(time_in_total/epoch)) 46 | 47 | -------------------------------------------------------------------------------- /Time-Space Tradeoff/Hashing/double hashing.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # implementation of hashing 7 | # the purpose is to split items into hash table evenly, 8 | # and minimize collisions as fewer as possible 9 | # table size is typically 1.5 larger than the number of items 10 | # modulo operation is chosen as a hash function 11 | # collision resolution mechanism: double hashing 12 | # average attempts for (un)successful search is calculated to evaluate efficiency of each mechanism 13 | 14 | class HashTable(object): 15 | def __init__(self, size): 16 | self.size = size 17 | self.table = [None for i in range(size)] 18 | def function(self, key): 19 | return key % self.size 20 | def rehash(self, key): 21 | m = self.size 22 | return m - 2 - key % (m - 2) 23 | def put(self, key): 24 | address = self.function(key) 25 | # table value is None 26 | while self.table[address]: 27 | address = self.function(address+self.rehash(key)) 28 | self.table[address] = key 29 | def get(self, key): 30 | value = self.function(key) 31 | cnt = 1 # one attempt in a minimum 32 | # table value is None 33 | while self.table[value] is not None: 34 | if self.table[value] == key: 35 | return True, cnt 36 | value = self.function(value+self.rehash(key)) 37 | cnt += 1 38 | return False, cnt 39 | 40 | 41 | keys = [58, 19, 75, 38, 29, 4, 60, 94, 84] 42 | table_size = 13 # prime number is recommended 43 | hashtable = HashTable(table_size) 44 | 45 | # build hash table 46 | print("Put items into hash table:\n", keys) 47 | for key in keys: 48 | hashtable.put(key) 49 | 50 | # search 51 | print("Get items from hash table:") 52 | search_keys = [60, 84, 22] 53 | for key in search_keys: 54 | status, attempt = hashtable.get(key) 55 | if status: 56 | print("\tKey {} has been found, with {} attempt(s).".format(key, attempt)) 57 | else: 58 | print("\tKey {} has not been found.".format(key)) 59 | 60 | # average case: success 61 | counter_success = 0 62 | for key in keys: 63 | _, attempt = hashtable.get(key) 64 | counter_success += attempt 65 | print("Average attempts for successful search is %.2f." % (counter_success/len(keys))) 66 | 67 | # average case: fail 68 | counter_fail = 0 69 | failure_keys = [26, 1, 67, 42, 82, 31, 97, 59, 21, 87, 75, 63, 116] # failure keys for each position of hash table 70 | for key in failure_keys: 71 | _, attempt = hashtable.get(key) 72 | counter_fail += attempt 73 | print("Average attempts for unsuccessful search is %.2f." % (counter_fail/len(failure_keys))) -------------------------------------------------------------------------------- /Time-Space Tradeoff/Hashing/linear probing.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # implementation of hashing 7 | # the purpose is to split items into hash table evenly, 8 | # and minimize collisions as fewer as possible 9 | # table size is typically 1.5 larger than the number of items 10 | # modulo operation is chosen as a hash function 11 | # collision resolution mechanism: linear probing 12 | # average attempts for (un)successful search is calculated to evaluate efficiency of each mechanism 13 | 14 | class HashTable(object): 15 | def __init__(self, size): 16 | self.size = size 17 | self.table = [None for i in range(size)] 18 | def function(self, key): 19 | return key % self.size 20 | def put(self, key): 21 | address = self.function(key) 22 | # table value is None 23 | while self.table[address]: 24 | address = (address + 1) % self.size 25 | self.table[address] = key 26 | def get(self, key): 27 | address = self.function(key) 28 | cnt = 1 # one attempt in a minimum 29 | # table value is None 30 | while self.table[address] is not None: 31 | if self.table[address] == key: 32 | return True, cnt 33 | address = (address + 1) % self.size 34 | cnt += 1 35 | return False, cnt 36 | 37 | 38 | keys = [58, 19, 75, 38, 29, 4, 60, 94, 84] 39 | table_size = 13 # prime number is recommended 40 | hashtable = HashTable(table_size) 41 | 42 | # build hash table 43 | print("Put items into hash table:\n", keys) 44 | for key in keys: 45 | hashtable.put(key) 46 | 47 | 48 | # search 49 | print("Get items from hash table:") 50 | search_keys = [60, 84, 22] 51 | for key in search_keys: 52 | status, attempt = hashtable.get(key) 53 | if status: 54 | print("\tKey {} has been found, with {} attempt(s).".format(key, attempt)) 55 | else: 56 | print("\tKey {} has not been found.".format(key)) 57 | 58 | # average case: success 59 | counter_success = 0 60 | for key in keys: 61 | _, attempt = hashtable.get(key) 62 | counter_success += attempt 63 | print("Average attempts for successful search is %.2f." % (counter_success/len(keys))) 64 | 65 | # average case: fail 66 | counter_fail = 0 67 | failure_keys = [26, 1, 67, 42, 82, 31, 97, 59, 21, 87, 75, 63, 116] # failure keys for each position of hash table 68 | for key in failure_keys: 69 | _, attempt = hashtable.get(key) 70 | counter_fail += attempt 71 | print("Average attempts for unsuccessful search is %.2f." % (counter_fail/len(failure_keys))) -------------------------------------------------------------------------------- /Time-Space Tradeoff/Hashing/separate chaining.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # implementation of hashing 7 | # the purpose is to split items into hash table evenly, 8 | # and minimize collisions as fewer as possible 9 | # table size is typically 1.5 larger than the number of items 10 | # modulo operation is chosen as a hash function 11 | # collision resolution mechanism: separate chaining 12 | # average attempts for (un)successful search is calculated to evaluate efficiency of each mechanism 13 | 14 | class Node(object): 15 | def __init__(self, value): 16 | self.value = value 17 | self.next = None 18 | 19 | class HashTable(object): 20 | def __init__(self, size): 21 | self.size = size 22 | self.table = [None for i in range(size)] 23 | def function(self, key): 24 | return key % self.size 25 | def put(self, key): 26 | address = self.function(key) 27 | node = Node(key) 28 | # table value is None 29 | if not self.table[address]: 30 | self.table[address] = node 31 | else: 32 | p = self.table[address] 33 | while p.next: 34 | p = p.next 35 | p.next = node 36 | def get(self, key): 37 | value = self.function(key) 38 | cnt = 1 # one attempt in a minimum 39 | p = self.table[value] 40 | while p: 41 | if p.value == key: 42 | return True, cnt 43 | p = p.next 44 | cnt += 1 45 | return False, cnt 46 | 47 | 48 | keys = [58, 19, 75, 38, 29, 4, 60, 94, 84] 49 | table_size = 13 # prime number is recommended 50 | hashtable = HashTable(table_size) 51 | 52 | # build hash table 53 | print("Put items into hash table:\n", keys) 54 | for key in keys: 55 | hashtable.put(key) 56 | 57 | # search 58 | print("Get items from hash table:") 59 | search_keys = [60, 84, 22] 60 | for key in search_keys: 61 | status, attempt = hashtable.get(key) 62 | if status: 63 | print("\tKey {} has been found, with {} attempt(s).".format(key, attempt)) 64 | else: 65 | print("\tKey {} has not been found.".format(key)) 66 | 67 | # average case: success 68 | counter_success = 0 69 | for key in keys: 70 | _, attempt = hashtable.get(key) 71 | counter_success += attempt 72 | print("Average attempts for successful search is %.2f." % (counter_success/len(keys))) 73 | 74 | # average case: fail 75 | counter_fail = 0 76 | failure_keys = [26, 1, 67, 42, 82, 31, 97, 59, 21, 87, 75, 63, 116] # failure keys for each position of hash table 77 | for key in failure_keys: 78 | _, attempt = hashtable.get(key) 79 | counter_fail += attempt 80 | print("Average attempts for unsuccessful search is %.2f." % (counter_fail/len(failure_keys))) 81 | -------------------------------------------------------------------------------- /Time-Space Tradeoff/String Matching/string matching.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # implementation of string matching solved by Horspool's algorithm 7 | # complementarity of DNA base pair, which has great similarity to string matching 8 | # method `shift_table` builds a shift table from a given pattern 9 | # method `convert` is for converting A to T, T to A, C to G and G to C 10 | # time complexity: Θ(mn) for the worst case, even worse than brute-force approach, 11 | # e.g. text = 000000000000000000, pattern = 10000 12 | # Θ(n) for the average case 13 | 14 | def shift_table(p, m): 15 | table = {} 16 | for i in range(m): 17 | table[p[i]] = m 18 | for j in range(m-1): 19 | table[p[j]] = m-1-j 20 | return table 21 | 22 | def Horspool(text, pattern): 23 | n = len(text); m = len(pattern) 24 | text = convert(text) 25 | table = shift_table(pattern, m) 26 | i = m - 1 27 | while i < n: 28 | k = 0 29 | while k < m and pattern[m-1-k] == text[i-k]: 30 | k += 1 31 | if k == m: 32 | return i - m + 1 33 | else: 34 | i += table[text[i]] 35 | return -1 36 | 37 | # to convert A-T and C-G pairs 38 | def convert(ori_text): 39 | dic = {'A': 'T', 'T': 'A', 'C': 'G', 'G': 'C'} 40 | temp = [] 41 | for char in ori_text: 42 | conv_char = dic[char] 43 | temp.append(conv_char) 44 | conv_text = ''.join(temp) 45 | return conv_text 46 | 47 | 48 | plain_text = "TCGAGAATTCCTA" 49 | pattern = "CTTAAG" 50 | print("String matching solved by Horspool's algorithm:\n") 51 | print("The plain text is %s" % plain_text) 52 | print("The pattern string is %s\n" % pattern) 53 | status = Horspool(plain_text, pattern) 54 | if status != -1: 55 | print("String matching is successful, the index of first occurrence is %d." % status) 56 | else: 57 | print("String matching in unsuccessful.") -------------------------------------------------------------------------------- /Transform and Conquer/AVL Tree/AVL tree.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # implementation of AVL tree 7 | # three operations: search, insert and remove 8 | # for each insertion and deletion, 9 | # balance factor is calculated for each node it passes through 10 | # if the absolute value of balance factor is greater than 1, 11 | # the corresponding rotation operation will be done to balance the tree 12 | # so the time complexity for all three operations are Θ(nlog n) 13 | 14 | class Node(object): 15 | def __init__(self, value): 16 | self.value = value 17 | self.left = None 18 | self.right = None 19 | self.height = 0 # initialize the height to 0 20 | class AVL_Tree(object): 21 | def __init__(self): 22 | self.root = None 23 | # calculate height 24 | def calc_height(self, node): 25 | if not node: 26 | return -1 27 | return node.height 28 | # calculate balance factor 29 | def calc_balance_factor(self, node): 30 | if not node: 31 | return 0 32 | return self.calc_height(node.left) - self.calc_height(node.right) 33 | # left_rotation, symmetric to right rotation 34 | def left_rotation(self, node): 35 | """ 36 | A C 37 | / \ / \ 38 | B C ------> A E 39 | / \ / \ 40 | D E B D 41 | """ 42 | t1 = node.right 43 | t2 = t1.left 44 | # reconstruction 45 | t1.left = node 46 | node.right = t2 47 | # update the height of node and t1 48 | node.height = max(self.calc_height(node.left), self.calc_height(node.right)) + 1 49 | t1.height = max(self.calc_height(t1.left), self.calc_height(t1.right)) + 1 50 | return t1 51 | # right rotation, symmetric to left rotation 52 | def right_rotation(self, node): 53 | """ 54 | A B 55 | / \ / \ 56 | B C ------> D A 57 | / \ / \ 58 | D E E C 59 | """ 60 | t1 = node.left 61 | t2 = t1.right 62 | # reconstruction 63 | t1.right = node 64 | node.left = t2 65 | # update the height of node and t1 66 | node.height = max(self.calc_height(node.left), self.calc_height(node.right)) + 1 67 | t1.height = max(self.calc_height(t1.left), self.calc_height(t1.right)) + 1 68 | return t1 69 | def search(self, value): 70 | # throw an exception if root is null 71 | if not self.root: 72 | raise ValueError("The tree is null") 73 | return self.search_node(self.root, value) 74 | def search_node(self, root, value): 75 | if not root: # return None if the node is null 76 | return None 77 | if value < root.value: # search from the left sub-tree 78 | return self.search_node(root.left, value) 79 | elif value > root.value: # search from the right sub-tree 80 | return self.search_node(root.right, value) 81 | else: 82 | return root 83 | def insert(self, value): 84 | node = Node(value) # create a new node 85 | self.root = self.insert_node(self.root, node) 86 | def insert_node(self, root, node): 87 | if not root: 88 | return node 89 | if node.value < root.value: 90 | root.left = self.insert_node(root.left, node) 91 | else: 92 | root.right = self.insert_node(root.right, node) 93 | root.height = max(self.calc_height(root.left), self.calc_height(root.right)) + 1 # update height 94 | return self.settle_violation(root) 95 | def settle_violation(self, root): 96 | balance = self.calc_balance_factor(root) 97 | if balance > 1: 98 | # case 1: double-right rotation 99 | if self.calc_balance_factor(root.left) >= 0: 100 | return self.right_rotation(root) 101 | # case 2: left-right rotation 102 | else: 103 | root.left = self.left_rotation(root.left) 104 | return self.right_rotation(root) 105 | elif balance < -1: 106 | # case 3: double-left rotation 107 | if self.calc_balance_factor(root.right) <= 0: 108 | return self.left_rotation(root) 109 | # case 4: right-left rotation 110 | else: 111 | root.right = self.right_rotation(root.right) 112 | return self.left_rotation(root) 113 | return root 114 | def remove(self, value): 115 | # throw an exception if root is null 116 | if not self.root: 117 | raise ValueError("The tree is null") 118 | self.root = self.remove_node(self.root, value) 119 | def remove_node(self, root, value): 120 | if not root: 121 | return root 122 | # search 123 | if value < root.value: 124 | root.left = self.remove_node(root.left, value) 125 | elif value > root.value: 126 | root.right = self.remove_node(root.right, value) 127 | else: 128 | # no children 129 | if not root.left and not root.right: 130 | del root 131 | return None 132 | # either left child or right child 133 | elif not root.left: 134 | temp = root.right 135 | del root 136 | return temp 137 | elif not root.right: 138 | temp = root.left 139 | del root 140 | return temp 141 | # both left and right child 142 | else: 143 | temp = self.get_max(root.left) 144 | root.value = temp.value 145 | root.left = self.remove_node(root.left, temp.value) 146 | root.height = max(self.calc_height(root.left), self.calc_height(root.right)) + 1 # update height 147 | return self.settle_violation(root) 148 | def get_max(self, root): 149 | if root.right: 150 | return self.get_max(root.right) 151 | return root 152 | 153 | def traversal(self): 154 | if not self.root: 155 | raise ValueError("The tree is null!") 156 | return self.in_order(self.root) 157 | def in_order(self, root): 158 | if root.left: 159 | self.in_order(root.left) 160 | print(root.value, end=' ') 161 | if root.right: 162 | self.in_order(root.right) 163 | 164 | # instantiate an AVL tree 165 | avl = AVL_Tree() 166 | 167 | array = [1, 3, 2, 4, 5, 7, 6] 168 | 169 | # insertion 170 | # binary search tree 171 | """ 172 | 1 173 | \ 174 | 3 175 | / \ 176 | 2 4 177 | \ 178 | 5 179 | \ 180 | 7 181 | / 182 | 6 183 | """ 184 | 185 | for element in array: 186 | avl.insert(element) 187 | 188 | # AVL tree 189 | """ 190 | 4 191 | / \ 192 | 2 6 193 | / \ / \ 194 | 1 3 5 7 195 | """ 196 | print("After insertion:") 197 | avl.traversal() 198 | 199 | # search 200 | key1 = 6; key2 = 8 201 | print("\nSearch: ") 202 | 203 | # success 204 | print("search key = %d" % key1) 205 | if avl.search(key1): 206 | print("Search successful.") 207 | else: 208 | print("Search unsuccessful.") 209 | 210 | # fail 211 | print("search key = %d" % key2) 212 | if avl.search(key2): 213 | print("Search successful.") 214 | else: 215 | print("Search unsuccessful.") 216 | 217 | # deletion 218 | avl.remove(6) 219 | avl.remove(5) 220 | avl.remove(7) 221 | 222 | 223 | # After deletion 224 | """ 225 | 2 226 | / \ 227 | 1 4 228 | / 229 | 3 230 | """ 231 | 232 | print("After deleting 6, 5 and 7:") 233 | avl.traversal() 234 | 235 | -------------------------------------------------------------------------------- /Transform and Conquer/Heap Sort/heap sort.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | import random 7 | import time 8 | 9 | # implementation of heap sort 10 | # to sort a random array with 10,000 elements 11 | # two metrics ("number of comparison" and "consumed time") for efficiency evaluation 12 | # time complexity: Θ(nlog n) in all cases 13 | # space complexity: Θ(1) 14 | # stability: unstable 15 | 16 | random.seed(1) # for reproducibility 17 | 18 | def heaplify(array, temp_idx, size): 19 | cnt = 0 20 | temp_val = array[temp_idx] # store value temporarily 21 | heap = False 22 | while not heap and 2 * temp_idx + 1 < size: 23 | j = 2 * temp_idx + 1 # index of left child 24 | # right child exists 25 | if j < size - 1: 26 | # compare two children 27 | if array[j] < array[j+1]: 28 | j = j + 1 29 | cnt += 1 30 | # whether violate heap property or not 31 | if array[j] <= temp_val: 32 | heap = True 33 | else: 34 | array[temp_idx] = array[j] 35 | temp_idx = j # update temp_idx 36 | cnt += 1 37 | array[temp_idx] = temp_val 38 | return cnt 39 | 40 | def heap_sort(array): 41 | cnt = 0 42 | for i in range((len(array)-2)//2, -1, -1): 43 | cnt += heaplify(array, i, len(array)) 44 | for i in range(len(array)-1, -1, -1): 45 | array[0], array[i] = array[i], array[0] # swap the first element with the last one 46 | cnt += heaplify(array, 0, i) 47 | return cnt 48 | 49 | 50 | time_in_total = 0 51 | epoch = 5 # num of iteration 52 | total_comparison = 0 53 | 54 | for i in range(epoch): 55 | time_start = time.time() 56 | array = [random.randint(0,10000) for i in range(10000)] 57 | comparison = heap_sort(array) 58 | time_finish = time.time() 59 | total_comparison += comparison 60 | time_in_total += time_finish - time_start 61 | print("Epoch {}: \n number of comparison: {}\n time consumed: {:.4f} s".format(i+1, comparison, time_finish-time_start)) 62 | print("average number of comparison: %d" % (total_comparison/epoch)) 63 | print('average time consumed: {:.4f} s'.format(time_in_total/epoch)) 64 | 65 | -------------------------------------------------------------------------------- /Transform and Conquer/Horner's Rule/horner's rule.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # implementation of polynomial computation followed by Horner's rule 7 | # time complexity: Θ(n) 8 | 9 | """ 10 | # brute-force approach 11 | # time complexity: Θ(n^2) 12 | def poly_bf(coeffi_list, x): 13 | degree = len(coeffi_list) - 1 # highest degree 14 | result = 0 15 | for i in range(degree+1): 16 | coeffi = coeffi_list[i]; poly = 1 17 | for j in range(degree-i-1, -1, -1): 18 | poly *= x # compute x^i 19 | result += coeffi * poly 20 | return result 21 | """ 22 | 23 | def poly_horner(coeffi_list, x): 24 | degree = len(coeffi_list) - 1 # highest degree 25 | result = coeffi_list[0] 26 | for i in range(1, degree+1): 27 | result = result * x + coeffi_list[i] 28 | return result 29 | 30 | 31 | # compute the value of 2x^4 - 3x^3 + 5x^2 + x -7 at x = 4. 32 | coefficient = [2, -3, 5, 1, -7] 33 | x = 4 34 | print(poly_horner(coefficient, x)) # should be 397 35 | 36 | -------------------------------------------------------------------------------- /Transform and Conquer/Presorting/check element uniqueness.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # implementation of checking element uniqueness with the idea of presorting 7 | # time complexity: Θ(nlog n) + Θ(n) = Θ(nlog n) 8 | # space complexity: Θ(1) 9 | 10 | """ 11 | # brute-force approach 12 | # time complexity: Θ(n^2) 13 | # space complexity: Θ(1) 14 | def is_unique_bf(array): 15 | length = len(array) 16 | for i in range(length-1): 17 | element = array[i] 18 | for j in range(i+1, length): 19 | if array[j] == element: 20 | return False 21 | return True 22 | """ 23 | 24 | def is_unique_presort(array): 25 | length = len(array) 26 | array.sort() # sort array 27 | for i in range(length-1): 28 | if array[i] == array[i+1]: 29 | return False 30 | return True 31 | 32 | 33 | array = [3, 1, 6, 2, 7, 9] 34 | # array = [2, 4, 1, 6, 2, 5] 35 | if is_unique_presort(array): 36 | print("Elements in the array is unique.") 37 | else: 38 | print("Elements in the array is not unique.") -------------------------------------------------------------------------------- /Transform and Conquer/Presorting/compute mode.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # implementation of finding a mode in an array with the idea of presorting 7 | # time complexity: Θ(nlog n) + Θ(n) = Θ(nlog n) 8 | # space complexity: Θ(1) 9 | 10 | """ 11 | # brute-force approach 12 | # time complexity: Θ(n^2) 13 | # space complexity: Θ(n) 14 | def compute_mode_bf(array): 15 | counter = {} 16 | for i in range(len(array)): 17 | counter[array[i]] = counter.get(array[i], 0) + 1 18 | freq = 0 19 | for key, value in counter.items(): 20 | if value > freq: 21 | freq = value 22 | mode = key 23 | return mode 24 | """ 25 | 26 | def compute_mode_presort(array): 27 | array.sort() # sort array 28 | i = 0; freq = 0 29 | while i < len(array): 30 | temp_freq = 1; temp_mode = array[i] 31 | while i + temp_freq < len(array) and array[i+temp_freq] == temp_mode: 32 | temp_freq += 1 33 | if temp_freq > freq: 34 | freq = temp_freq; mode = temp_mode 35 | i += temp_freq 36 | return mode 37 | 38 | array = [2, 6, 3, 1, 2, 1, 4, 2, 5, 3, 7, 8, 6, 4, 3] 39 | print("The mode of the array is %d." % compute_mode_presort(array)) -------------------------------------------------------------------------------- /Transform and Conquer/Red-Black Tree/red-black tree.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # implementation of red-black tree 7 | # three operations: search, insert and remove 8 | # the following properties must be satisfied for each insertion and deletion: 9 | # 1. each node is either red or black 10 | # 2. the root node must be black 11 | # 3. All leaf nodes(NIL) are black 12 | # 4. if a node is red, both its children are black(including NIL) 13 | # 5. Every path from a given node to its descendent NIL contains the same number of black nodes. 14 | # if the properties are violated, then rotation or color rearranging are performed to balance the tree, 15 | # so the time complexity for all three operations are close to Θ(log n) 16 | 17 | class Node(object): 18 | def __init__(self, value): 19 | self.value = value 20 | self.color = 'r' 21 | self.left = None 22 | self.right = None 23 | self.parent = None 24 | # get the grandparent node 25 | def grandparent(self): 26 | if not self.parent: 27 | return None 28 | return self.parent.parent 29 | # get the uncle node 30 | def uncle(self): 31 | if not self.grandparent(): 32 | return None 33 | if self.parent is self.grandparent().left: 34 | return self.grandparent().right 35 | else: 36 | return self.grandparent().left 37 | # get the brother node 38 | def brother(self): 39 | if self.parent.left is self: 40 | return self.parent.right 41 | else: 42 | return self.parent.left 43 | 44 | class Red_Black_Tree(object): 45 | def __init__(self): 46 | # construct a NIL node 47 | NIL = Node(None) 48 | NIL.color = 'b' 49 | self.NIL = NIL 50 | self.root = None 51 | def left_rotation(self, node): 52 | """ 53 | g g 54 | / \ / \ 55 | ? p ? n 56 | / \ --------> / \ 57 | ? n p r 58 | / \ / \ 59 | l r ? l 60 | g: grandparent, p: parent, n: rotated node, l: left child, r: right child 61 | """ 62 | if not node.parent: 63 | self.root = node 64 | return 65 | grandparent = node.grandparent() 66 | parent = node.parent 67 | t = node.left 68 | parent.right = t 69 | 70 | if t is not self.NIL: 71 | t.parent = parent 72 | node.left = parent 73 | parent.parent = node 74 | 75 | if parent is self.root: 76 | self.root = node 77 | node.parent = grandparent 78 | 79 | if grandparent: 80 | # left sub-tree 81 | if grandparent.left is parent: 82 | grandparent.left = node 83 | # right sub-tree 84 | else: 85 | grandparent.right = node 86 | def right_rotation(self, node): 87 | """ 88 | g g 89 | / \ / \ 90 | p ? n ? 91 | / \ --------> / \ 92 | n ? l p 93 | / \ / \ 94 | l r r ? 95 | g: grandparent, p: parent, n: rotated node, l: left child, r: right child 96 | """ 97 | if not node.parent: 98 | self.root = node 99 | return 100 | grandparent = node.grandparent() 101 | parent = node.parent 102 | t = node.right 103 | parent.left = t 104 | 105 | if t is not self.NIL: 106 | t.parent = parent 107 | node.right = parent 108 | parent.parent = node 109 | 110 | if parent is self.root: 111 | self.root = node 112 | node.parent = grandparent 113 | 114 | if grandparent: 115 | # left sub-tree 116 | if grandparent.left is parent: 117 | grandparent.left = node 118 | # right sub-tree 119 | else: 120 | grandparent.right = node 121 | def search(self, value): 122 | # throw an exception if root is null 123 | if not self.root: 124 | raise ValueError("The tree is null") 125 | return self.search_node(self.root, value) 126 | def search_node(self, root, value): 127 | if root is self.NIL: # return None if the node is NIL 128 | return None 129 | if value < root.value: # search from the left sub-tree 130 | return self.search_node(root.left, value) 131 | elif value > root.value: # search from the right sub-tree 132 | return self.search_node(root.right, value) 133 | else: 134 | return root 135 | def insert(self, value): 136 | new_node = Node(value) 137 | new_node.left = self.NIL 138 | new_node.right = self.NIL 139 | if not self.root: 140 | self.root = new_node 141 | new_node.color = 'b' # set root's color to black 142 | else: 143 | self.insert_node(self.root, new_node) 144 | def insert_node(self, root, node): 145 | if node.value < root.value: 146 | if root.left is not self.NIL: 147 | self.insert_node(root.left, node) 148 | else: 149 | root.left = node 150 | node.parent = root 151 | self.insert_case(node) 152 | else: 153 | if root.right is not self.NIL: 154 | self.insert_node(root.right, node) 155 | else: 156 | root.right = node 157 | node.parent = root 158 | self.insert_case(node) 159 | def insert_case(self, node): 160 | # case 1: the root's color is red 161 | if not node.parent: 162 | self.root = node 163 | node.color = 'b' 164 | return 165 | # case 2: parent and uncle are red, grandparent is black 166 | """ 167 | g(b) g(r) 168 | / \ / \ 169 | p(r) u(r) --------> p(b) u(b) 170 | / / 171 | n(r) n(r) 172 | """ 173 | if node.parent.color == 'r': 174 | if node.uncle().color == 'r': 175 | node.parent.color = 'b'; node.uncle().color = 'b' 176 | node.grandparent().color = 'r' 177 | self.insert_case(node.grandparent()) # call insert_case() recursively 178 | else: 179 | # case 3A: uncle is black or NIL, new node is the left child of parent, parent is the left child of grandparent 180 | if node.parent.left is node and node.parent is node.grandparent().left: 181 | """ 182 | g(b) p(b) 183 | / \ / \ 184 | p(r) u(b) ------> n(r) g(r) 185 | / \ 186 | n(r) u(b) 187 | """ 188 | node.parent.color = 'b' 189 | node.grandparent().color = 'r' 190 | self.right_rotation(node.parent) 191 | # case 3B: uncle is black or NIL, new node is the right child of parent, parent is the left child of grandparent 192 | elif node.parent.right is node and node.parent is node.grandparent().right: 193 | """ 194 | g(b) p(b) 195 | / \ / \ 196 | u(b) p(r) ------> g(r) n(r) 197 | \ / 198 | n(r) u(b) 199 | """ 200 | node.parent.color = 'b' 201 | node.grandparent().color = 'r' 202 | self.left_rotation(node.parent) 203 | # case 4A: uncle is black or NIL, new node is the right child of parent, parent is the left child of grandparent 204 | elif node.parent.right is node and node.parent is node.grandparent().left: 205 | """ 206 | g(b) g(b) n(b) 207 | / \ / \ / \ 208 | p(r) u(b) ------> n(r) u(b) ------> p(r) g(r) 209 | \ / \ 210 | n(r) p(r) u(b) 211 | """ 212 | node.color = 'b' 213 | node.grandparent().color = 'r' 214 | self.left_rotation(node) 215 | self.right_rotation(node) 216 | # case 4B: uncle is black or NIL, new node is the left child of parent, parent is the right child of grandparent 217 | elif node.parent.left is node and node.parent is node.grandparent().right: 218 | """ 219 | g(b) g(b) n(b) 220 | / \ / \ / \ 221 | u(b) p(r) ------> u(b) n(r) ------> g(r) p(r) 222 | / \ / 223 | n(r) p(r) u(b) 224 | """ 225 | node.color = 'b' 226 | node.grandparent().color = 'r' 227 | self.right_rotation(node) 228 | self.left_rotation(node) 229 | def remove(self, value): 230 | # throw an exception if root is null 231 | if not self.root: 232 | raise ValueError("The tree is null") 233 | self.remove_node(self.root, value) 234 | def remove_node(self, root, value): 235 | if root is self.NIL: 236 | return 237 | # search 238 | if value < root.value: 239 | self.remove_node(root.left, value) 240 | elif value > root.value: 241 | self.remove_node(root.right, value) 242 | else: 243 | # no children 244 | if root.left is self.NIL and root.right is self.NIL: 245 | self.remove_leaf(root, True) 246 | # either left child or right child 247 | elif root.left is self.NIL or root.right is self.NIL: 248 | self.remove_one_child(root) 249 | # both left child and right child 250 | else: 251 | temp = self.get_max(root.left) # find the maximum value from the left sub-tree 252 | root.value = temp.value 253 | self.remove_node(root.left, temp.value) 254 | def remove_leaf(self, node, flag): 255 | # remove red node without any change 256 | if node.color == 'r': 257 | self.fix(node, self.NIL) 258 | return 259 | # black node 260 | # case 1: node to be removed is the root 261 | if node is self.root: 262 | if flag: 263 | self.fix(node, None) 264 | return 265 | else: 266 | parent = node.parent 267 | brother = node.brother() 268 | # case 2: brother node is red 269 | if brother.color == 'r': 270 | parent.color = 'r'; brother.color = 'b' 271 | # case 2A: node to be removed is the left child of parent 272 | if node.parent.left is node: 273 | """ 274 | p(r) b(b) 275 | / \ / \ 276 | n(b) b(r) --------> p(r) r(b) 277 | / \ / \ / \ 278 | N N l(b) r(b) n(b) l(b) 279 | / \ 280 | N N 281 | """ 282 | self.left_rotation(brother) 283 | # case 2B: node to be removed is the right child of parent 284 | else: 285 | """ 286 | p(r) b(b) 287 | / \ / \ 288 | b(r) n(b) --------> l(b) p(r) 289 | / \ / \ / \ 290 | l(b) r(b) N N r(b) n(b) 291 | / \ 292 | N N 293 | """ 294 | self.right_rotation(brother) 295 | self.remove_leaf(node, True) 296 | else: 297 | nephew_left = brother.left 298 | nephew_right = brother.right 299 | # case 3A: brother node is black, right nephew node is red, node to be removed is the left child of parent 300 | if node.parent.left is node and nephew_right.color == 'r': 301 | """ 302 | p(?) b(?) b(?) 303 | / \ / \ / \ 304 | n(b) b(b) --------> p(b) r(b) --------> p(b) r(b) 305 | / \ / \ / \ / \ 306 | N N l(?) r(r) n(b) l(?) N l(?) 307 | / \ 308 | N N 309 | """ 310 | brother.color = parent.color; parent.color = 'b'; nephew_right.color = 'b' 311 | self.left_rotation(brother) 312 | # case 3B: brother node is black, left nephew node is red, node to be removed is the right child of parent 313 | elif node.parent.right is node and nephew_left.color == 'r': 314 | """ 315 | p(?) b(?) b(?) 316 | / \ / \ / \ 317 | b(b) n(b) --------> l(b) p(b) --------> l(b) p(b) 318 | / \ / \ / \ / \ 319 | l(r) r(?) N N r(?) n(b) r(?) N 320 | / \ 321 | N N 322 | """ 323 | brother.color = parent.color; parent.color = 'b'; nephew_left.color = 'b' 324 | self.right_rotation(brother) 325 | # case 4A: brother node is black, left nephew node is red, node to be removed is the left child of parent 326 | elif node.parent.left is node and nephew_left.color == 'r': 327 | """ 328 | p(?) p(?) l(?) 329 | / \ / \ / \ 330 | n(b) b(b) --------> n(b) l(b) --------> p(b) b(b) 331 | / \ / \ / \ / \ \ / \ 332 | N N l(r) N N N ? b(r) ? ? N 333 | / \ / \ 334 | ? ? ? N 335 | """ 336 | nephew_left.color = 'b'; brother.color = 'r' 337 | self.right_rotation(nephew_left) 338 | self.remove_leaf(node, True) 339 | # case 4B: brother node is black, right nephew node is red, node to be removed is the right child of parent 340 | elif node.parent.right is node and nephew_right.color == 'r': 341 | """ 342 | p(?) p(?) r(?) 343 | / \ / \ / \ 344 | b(b) n(b) --------> r(b) n(b) --------> b(b) p(b) 345 | / \ / \ / \ / \ / \ / 346 | N r(r) N N b(r) ? N N N ? ? 347 | / \ / \ 348 | ? ? N ? 349 | """ 350 | nephew_right.color = 'b'; brother.color = 'r' 351 | self.left_rotation(nephew_right) 352 | self.remove_leaf(node, True) 353 | # case 5: brother node is black, and both of its children are NIL 354 | elif brother.left.color == 'b' and brother.right.color == 'b': 355 | # case 5A: parent node is red 356 | if parent.color == 'r': 357 | """ 358 | p(r) p(b) p(b) 359 | / \ / \ / \ 360 | n(b) b(b) --------> n(b) b(r) --------> N b(r) 361 | / \ / \ / \ / \ / \ 362 | N N N N N N N N N N 363 | """ 364 | parent.color = 'b'; brother.color = 'r' 365 | # case 5B: parent node is black 366 | else: 367 | """ 368 | p(b) p(b) 369 | / \ / \ 370 | n(b) b(b) --------> n(b) b(r) 371 | / \ / \ / \ / \ 372 | N N N N N N N N 373 | """ 374 | brother.color = 'r' 375 | self.remove_leaf(parent, False) 376 | if flag: 377 | self.fix(node, self.NIL) 378 | def remove_one_child(self, node): 379 | if node.left is not self.NIL: 380 | node.left.color = 'b' 381 | self.fix(node, node.left) 382 | else: 383 | node.right.color = 'b' 384 | self.fix(node, node.right) 385 | def fix(self, p, n): 386 | # p.parent is None 387 | if not p.parent: 388 | self.root = n 389 | elif p.parent.left is p: 390 | p.parent.left = n 391 | elif p.parent.right is p: 392 | p.parent.right = n 393 | if n is not self.NIL and n is not None: 394 | n.parent = p.parent 395 | del p 396 | 397 | def get_max(self, root): 398 | if root.right is not self.NIL: 399 | return self.get_max(root.right) 400 | return root 401 | def traversal(self): 402 | collection = {} 403 | if self.root: 404 | return self.in_order(self.root, collection) 405 | return collection 406 | def in_order(self, root, collection): 407 | if root.left is not self.NIL: 408 | self.in_order(root.left, collection) 409 | collection[root.value] = root.color 410 | if root.right is not self.NIL: 411 | self.in_order(root.right, collection) 412 | return collection 413 | 414 | 415 | # instantiate an empty red-black tree 416 | rbt = Red_Black_Tree() 417 | 418 | array = [1, 3, 4, 2, 5, 7, 6] 419 | 420 | # insertion 421 | """ 422 | 1 423 | \ 424 | 3 425 | / \ 426 | 2 4 427 | \ 428 | 5 429 | \ 430 | 7 431 | / 432 | 6 433 | """ 434 | 435 | for element in array: 436 | rbt.insert(element) 437 | 438 | # red-black tree 439 | """ 440 | 3(b) 441 | / \ 442 | 1(b) 5(r) 443 | \ / \ 444 | 2(r) 4(b) 7(b) 445 | / 446 | 6(r) 447 | """ 448 | print("After insertion:") 449 | print(rbt.traversal()) 450 | 451 | # search 452 | key1 = 6; key2 = 8 453 | print("Search: ") 454 | 455 | # success 456 | print("search key = %d" % key1) 457 | if rbt.search(key1): 458 | print("Search successful.") 459 | else: 460 | print("Search unsuccessful.") 461 | 462 | # fail 463 | print("search key = %d" % key2) 464 | if rbt.search(key2): 465 | print("Search successful.") 466 | else: 467 | print("Search unsuccessful.") 468 | 469 | # deletion 470 | rbt.remove(5) 471 | rbt.remove(1) 472 | rbt.remove(7) 473 | 474 | # After deletion 475 | """ 476 | 3(b) 477 | / \ 478 | 2(b) 6(b) 479 | / 480 | 4(r) 481 | """ 482 | 483 | print("After deleting 5, 1 and 7:\n", rbt.traversal()) 484 | -------------------------------------------------------------------------------- /Transform and Conquer/Two-Three Tree/2-3 tree.py: -------------------------------------------------------------------------------- 1 | """ 2 | # -*- coding: utf-8 -*- 3 | @author: Hongzhi Fu 4 | """ 5 | 6 | # implementation of 2-3 tree 7 | # three operations: search, insert and remove 8 | # each node has either one element or two elements 9 | # insertion operation is always occurred in the leaf node 10 | # when node is not full, put the item into the node, otherwise, split node into two new nodes, 11 | # and the procedure is done recursively until meeting the root node 12 | # deletion is similar to binary search tree, but it has several different cases: 13 | # case 1: delete 3-node, delete without any adjustment 14 | # case 2: brother node is full, borrow one element and balance the tree 15 | # case 3: parent node is full, borrow one element from the parent node, so parent node decreases to 2-node 16 | # case 4: neither brother node nor parent node is not full, merge parent node and brother node, 17 | # and check parent node recursively until encountering the root node 18 | # time complexity: Θ(log n), in the range of [log2_n, log3_n] 19 | 20 | class Node(object): 21 | def __init__(self, value): 22 | self.value1 = value 23 | self.value2 = None 24 | self.left = None 25 | self.mid = None 26 | self.right = None 27 | self.parent = None 28 | def is_leaf(self): 29 | # whether node is leaf node 30 | return not self.left and not self.mid and not self.right 31 | def is_full(self): 32 | # whether node is 3-node 33 | return self.value2 is not None 34 | def get_child(self, value): 35 | # given a value, return its child to be searched 36 | if self.value2: # 3-node 37 | if value < self.value1: 38 | return self.left 39 | elif value < self.value2: 40 | return self.mid 41 | return self.right 42 | else: # 2-node 43 | if value < self.value1: 44 | return self.left 45 | return self.right 46 | # return 3-node brother node 47 | def brother(self): 48 | # only return brother when parent is not null 49 | if self.parent: 50 | # parent node is 2-node 51 | if not self.parent.is_full(): 52 | if self.parent.left is self: 53 | return self.parent.right 54 | return self.parent.left 55 | # parent node is 3-node 56 | else: 57 | if self.parent.mid is self: 58 | if self.parent.right.is_full(): 59 | return self.parent.right 60 | if self.parent.left.is_full(): 61 | return self.parent.left 62 | # choose arbitrarily 63 | return self.parent.right 64 | return self.parent.mid 65 | 66 | class TwoThreeTree(object): 67 | def __init__(self): 68 | self.root = None 69 | def search(self, value): 70 | # throw an exception if root is null 71 | if not self.root: 72 | raise ValueError("The tree is null") 73 | return self.search_node(self.root, value) 74 | def search_node(self, root, value): 75 | if not root: 76 | return None # return None if the node is null 77 | if value == root.value1 or value == root.value2: 78 | return root 79 | return self.search_node(root.get_child(value), value) # search from the expected sub-tree 80 | def insert(self, value): 81 | if not self.root: 82 | self.root = Node(value) 83 | return 84 | self.insert_node(self.root, value) 85 | def insert_node(self, root, value): 86 | # search until `root` is leaf node 87 | while not root.is_leaf(): 88 | root = root.get_child(value) 89 | # node is 2-node 90 | if not root.is_full(): 91 | self.put_item(root, value) 92 | # node is 3-node 93 | else: 94 | self.put_item_full(root, value) 95 | def put_item(self, leaf, value): 96 | if value < leaf.value1: 97 | leaf.value2 = leaf.value1 98 | leaf.value1 = value 99 | else: 100 | leaf.value2 = value 101 | def put_item_full(self, leaf, value): 102 | """ 103 | :param leaf: leaf node which is full 104 | :param value: value to be inserted 105 | :return: value that needs to be pushed upward, and splitting node 106 | """ 107 | pvalue, new_node = self.split(leaf, value) 108 | # iterate from leaf up to root 109 | while leaf.parent: 110 | # insert value into the parent node if parent node is 2-node, then jump out of `while` loop 111 | if not leaf.parent.is_full(): 112 | self.put_item(leaf.parent, pvalue) 113 | # leaf is the parent's left child 114 | if leaf.parent.left is leaf: 115 | leaf.parent.mid = new_node 116 | # leaf is the parent's right child 117 | else: 118 | leaf.parent.mid = leaf; leaf.parent.right = new_node 119 | new_node.parent = leaf.parent 120 | break 121 | # split parent node and rearrange each node's references accordingly 122 | else: 123 | pvalue_p, new_node_p = self.split(leaf.parent, pvalue) 124 | # case 1: splitting node is the parent's left child 125 | if leaf.parent.left is leaf: 126 | """ 127 | 4 128 | 4 6 / \ 129 | / | \ insert 3 2 6 130 | 1 2 5 7 ---------> / \ / \ 131 | 1 3 5 7 132 | """ 133 | new_node_p.left = leaf.parent.mid; leaf.parent.mid.parent = new_node_p 134 | new_node_p.right = leaf.parent.right; leaf.parent.right.parent = new_node_p 135 | leaf.parent.right = new_node; new_node.parent = leaf.parent 136 | # case 2: splitting node is the parent's middle child 137 | elif leaf.parent.mid is leaf: 138 | """ 139 | 4 140 | 2 6 / \ 141 | / | \ insert 5 2 6 142 | 1 3 4 7 ---------> / \ / \ 143 | 1 3 5 7 144 | """ 145 | new_node_p.left = new_node; new_node.parent = new_node_p 146 | new_node_p.right = leaf.parent.right; leaf.parent.right.parent = new_node_p 147 | leaf.parent.right = leaf.parent.mid 148 | # case 3: splitting node is the parent's right child 149 | else: 150 | """ 151 | 4 152 | 2 4 / \ 153 | / | \ insert 7 2 6 154 | 1 3 5 6 ---------> / \ / \ 155 | 1 3 5 7 156 | """ 157 | leaf.parent.right = leaf.parent.mid; temp = leaf.parent.right 158 | new_node_p.left = leaf; leaf.parent = new_node_p 159 | new_node_p.right = new_node; new_node.parent = new_node_p 160 | leaf = temp 161 | leaf.parent.mid = None # convert to 2-node 162 | leaf = leaf.parent; pvalue = pvalue_p; new_node = new_node_p # move `leaf`, `pvalue`, `new_node` upward 163 | # if pushing forward to the root node, a new root node is created 164 | else: 165 | new_root = Node(pvalue) 166 | new_root.left = leaf; new_root.right = new_node 167 | leaf.parent = new_root; new_node.parent = new_root 168 | self.root = new_root 169 | def split(self, leaf, value): 170 | new_node = Node(None) 171 | # `value1` to be pushed upward 172 | if value < leaf.value1: 173 | pvalue = leaf.value1 174 | leaf.value1 = value 175 | new_node.value1 = leaf.value2 176 | # `value` to be pushed upward 177 | elif value < leaf.value2: 178 | pvalue = value 179 | new_node.value1 = leaf.value2 180 | # `value2` to be pushed upward 181 | else: 182 | pvalue = leaf.value2 183 | new_node.value1 = value 184 | leaf.value2 = None 185 | return pvalue, new_node 186 | def remove(self, value): 187 | # throw an exception if root is null 188 | if not self.root: 189 | raise ValueError("The tree is null") 190 | node = self.search(value) # find the node to be removed 191 | # leaf node 192 | if node.is_leaf(): 193 | self.remove_leaf(node, value) 194 | # parent node 195 | else: 196 | # find predecessor from the middle sub-tree 197 | if node.is_full() and node.value2 == value: 198 | predecessor = self.get_predecessor(node.mid) 199 | # swap 200 | if predecessor.is_full(): 201 | predecessor.value2, node.value2 = node.value2, predecessor.value2 202 | else: 203 | predecessor.value1, node.value2 = node.value2, predecessor.value1 204 | # find predecessor from the left sub-tree 205 | else: 206 | predecessor = self.get_predecessor(node.left) 207 | # swap 208 | if predecessor.is_full(): 209 | predecessor.value2, node.value1 = node.value1, predecessor.value2 210 | else: 211 | predecessor.value1, node.value1 = node.value1, predecessor.value1 212 | self.remove_leaf(predecessor, value) 213 | def get_predecessor(self, root): 214 | if root.right: 215 | return self.get_predecessor(root.right) 216 | return root 217 | def remove_leaf(self, node, value): 218 | # delete without any adjustment 219 | if node.is_full(): 220 | self.remove_item(node, value) 221 | else: 222 | brother = node.brother() 223 | while node.parent: 224 | # case 1 225 | if brother.is_full(): 226 | self.leaf_case1(node, brother) 227 | break 228 | else: 229 | # case 2 230 | if node.parent.is_full(): 231 | self.leaf_case2(node, brother) 232 | break 233 | # case 3 234 | else: 235 | node = self.merge(node, brother) 236 | brother = node.brother() 237 | else: 238 | self.root = node.mid 239 | del node 240 | def remove_item(self, node, value): 241 | # suppose node is full 242 | if node.value1 == value: 243 | node.value1 = node.value2 244 | node.value2 = None 245 | def leaf_case1(self, node, brother): 246 | node.value1 = None 247 | if node is node.parent.left: 248 | node.value1 = node.parent.value1 249 | node.parent.value1 = brother.value1 250 | # brother node has children 251 | if brother.left: 252 | """ 253 | 2 5 remove 1 3 5 254 | / | \ ---------> / | \ 255 | 1 3 4 6 2 4 6 256 | """ 257 | node.left = node.mid 258 | node.right = brother.left 259 | brother.left.parent = node 260 | brother.left = brother.mid 261 | self.remove_item(brother, brother.value1) 262 | elif node is node.parent.right: 263 | """ 264 | 2 5 remove 6 2 4 265 | / | \ ---------> / | \ 266 | 1 3 4 6 1 3 5 267 | """ 268 | if node.parent.is_full(): 269 | node.value1 = node.parent.value2 270 | node.parent.value2 = brother.value2 271 | else: 272 | node.value1 = node.parent.value1 273 | node.parent.value1 = brother.value2 274 | # brother node has children 275 | if brother.right: 276 | node.right = node.mid 277 | node.left = brother.right 278 | brother.right.parent = node 279 | brother.right = brother.mid 280 | self.remove_item(brother, brother.value2) 281 | else: 282 | if brother is node.parent.right: 283 | """ 284 | 2 4 remove 3 2 5 285 | / | \ ---------> / | \ 286 | 1 3 5 6 1 4 6 287 | """ 288 | node.value1 = node.parent.value2 289 | node.parent.value2 = brother.value1 290 | # brother node has children 291 | if brother.left: 292 | node.left = node.mid 293 | node.right = brother.left 294 | brother.left.parent = node 295 | brother.left = brother.mid 296 | self.remove_item(brother, brother.value1) 297 | else: 298 | """ 299 | 3 5 remove 4 2 5 300 | / | \ ---------> / | \ 301 | 1 2 4 6 1 3 6 302 | """ 303 | node.value1 = node.parent.value1 304 | node.parent.value1 = brother.value2 305 | # brother node has children 306 | if brother.right: 307 | node.right = node.mid 308 | node.left= brother.right 309 | brother.right.parent = node 310 | brother.right = brother.mid 311 | self.remove_item(brother, brother.value2) 312 | brother.mid = None 313 | node.mid = None 314 | def leaf_case2(self, node, brother): 315 | node.value1 = None 316 | if node is node.parent.left: 317 | """ 318 | 2 4 remove 1 4 319 | / | \ ---------> / \ 320 | 1 3 5 2 3 5 321 | """ 322 | self.put_item(brother, node.parent.value1) 323 | if node.mid: 324 | brother.mid = brother.left 325 | brother.left = node.mid 326 | node.mid.parent = brother 327 | node.parent.left = brother 328 | self.remove_item(node.parent, node.parent.value1) 329 | elif node is node.parent.right: 330 | """ 331 | 2 4 remove 5 2 332 | / | \ ---------> / \ 333 | 1 3 5 1 3 4 334 | """ 335 | self.put_item(brother, node.parent.value2) 336 | if node.mid: 337 | brother.mid = brother.right 338 | brother.right = node.mid 339 | node.mid.parent = brother 340 | node.parent.right = brother 341 | self.remove_item(node.parent, node.parent.value2) 342 | else: 343 | """ 344 | 2 4 remove 3 2 345 | / | \ ---------> / \ 346 | 1 3 5 1 4 5 347 | """ 348 | self.put_item(brother, node.parent.value2) 349 | if node.mid: 350 | brother.mid = brother.left 351 | brother.left = node.mid 352 | node.mid.parent = brother 353 | self.remove_item(node.parent, node.parent.value2) 354 | node.parent.mid = None 355 | del node 356 | def merge(self, node, brother): 357 | self.put_item(brother, node.parent.value1) 358 | if node is node.parent.left: 359 | """ 360 | 4 4 361 | / \ / \ 362 | 2 6 remove 1 ? 6 363 | / \ / \ ---------> | / \ 364 | 1 3 5 7 2 3 5 7 365 | """ 366 | if node.mid: 367 | brother.mid = brother.left 368 | brother.left = node.mid 369 | node.mid.parent = brother 370 | node.parent.right = None 371 | else: 372 | """ 373 | 4 4 374 | / \ / \ 375 | 2 6 remove 7 2 ? 376 | / \ / \ ---------> / \ | 377 | 1 3 5 7 1 3 5 6 378 | """ 379 | if node.mid: 380 | brother.mid = brother.right 381 | brother.right = node.mid 382 | node.mid.parent = brother 383 | node.parent.left = None 384 | node.parent.mid = brother 385 | temp = node 386 | node = node.parent 387 | del temp 388 | return node 389 | def traversal(self): 390 | if not self.root: 391 | raise ValueError("The tree is null!") 392 | return self.in_order(self.root) 393 | def in_order(self, root): 394 | if root.is_full(): 395 | if root.left: 396 | self.in_order(root.left) 397 | print(root.value1, end=' ') 398 | if root.mid: 399 | self.in_order(root.mid) 400 | print(root.value2, end=' ') 401 | if root.right: 402 | self.in_order(root.right) 403 | else: 404 | if root.left: 405 | self.in_order(root.left) 406 | print(root.value1, end=' ') 407 | if root.right: 408 | self.in_order(root.right) 409 | 410 | 411 | # instantiate an AVL tree 412 | tttree = TwoThreeTree() 413 | 414 | array = [1, 3, 2, 4, 5, 7, 6] 415 | 416 | # insertion 417 | # binary search tree 418 | """ 419 | 1 420 | \ 421 | 3 422 | / \ 423 | 2 4 424 | \ 425 | 5 426 | \ 427 | 7 428 | / 429 | 6 430 | """ 431 | 432 | for element in array: 433 | tttree.insert(element) 434 | 435 | # two-three tree 436 | """ 437 | 4 438 | / \ 439 | 2 6 440 | / \ / \ 441 | 1 3 5 7 442 | """ 443 | print("After insertion:") 444 | tttree.traversal() 445 | 446 | # search 447 | key1 = 6; key2 = 8 448 | print("\nSearch: ") 449 | 450 | # success 451 | print("search key = %d" % key1) 452 | if tttree.search(key1): 453 | print("Search successful.") 454 | else: 455 | print("Search unsuccessful.") 456 | 457 | # fail 458 | print("search key = %d" % key2) 459 | if tttree.search(key2): 460 | print("Search successful.") 461 | else: 462 | print("Search unsuccessful.") 463 | 464 | # deletion 465 | tttree.remove(1) 466 | tttree.remove(7) 467 | tttree.remove(5) 468 | tttree.remove(6) 469 | 470 | # After deletion 471 | """ 472 | 3 473 | / \ 474 | 2 4 475 | """ 476 | 477 | print("After deleting 1, 7, 5, 6:") 478 | tttree.traversal() 479 | --------------------------------------------------------------------------------