├── .idea ├── .gitignore ├── dsa.iml ├── inspectionProfiles │ └── profiles_settings.xml ├── misc.xml ├── modules.xml └── vcs.xml ├── algorithm_design └── problems │ ├── blackforest.py │ ├── company_party.py │ ├── cut_rod.py │ ├── drivers.py │ ├── fib.py │ ├── knapsack.py │ ├── knapsack2d.py │ ├── magic.py │ ├── maximin.py │ ├── maze.py │ ├── min_cost.py │ ├── plan.py │ └── wired.py ├── data_structures ├── bst.py ├── disjoint_set.py ├── labs │ ├── zad1.py │ ├── zad2.py │ └── zad3.py ├── queue.py └── queue_list.py ├── graphs ├── algorithms │ ├── bellman-ford.py │ ├── bfs.py │ ├── dag_shortest_path.py │ ├── dfs.py │ ├── dijkstra.py │ ├── find_articulation_points.py │ ├── find_bridges.py │ ├── floyd_warshall.py │ ├── kruskal.py │ ├── maximum_bipartite_matching.py │ ├── scc.py │ ├── topological_sort.py │ ├── topological_sort_kahan.py │ └── transitive_closure.py ├── labs │ ├── lab1 │ │ ├── zad1.py │ │ ├── zad2.py │ │ ├── zad3.py │ │ ├── zad4.py │ │ ├── zad5.py │ │ ├── zad6.py │ │ └── zad7.py │ ├── lab2 │ │ ├── zad2.py │ │ ├── zad3.py │ │ ├── zad4.py │ │ └── zad5.py │ ├── lab3 │ │ ├── zad1.py │ │ ├── zad2.py │ │ ├── zad3.py │ │ ├── zad4.py │ │ ├── zad5.py │ │ └── zad6.py │ └── lab4 │ │ ├── zad1.py │ │ ├── zad2.py │ │ └── zad3.py └── problems │ ├── airports.py │ ├── armstrong.py │ ├── beautree.py │ ├── beautree_v2.py │ ├── binworker.py │ ├── flight.py │ ├── gold.py │ ├── has_cycle.py │ ├── is_bipartite.py │ ├── is_connected.py │ ├── is_eulerian.py │ ├── jumper.py │ ├── keep_distance.py │ ├── koleje.py │ ├── lufthansa.py │ ├── mykoryza.py │ ├── offline │ ├── .gitignore │ ├── offline3 │ │ └── zad3.py │ ├── offline4 │ │ └── zad4.py │ └── offline5 │ │ ├── zad.py │ │ └── zadv2.py │ ├── robot.py │ ├── turysta.py │ ├── warrior.py │ └── warrior_v2.py ├── random ├── min_max.py └── shift_left.py └── sorting ├── algorithms ├── binary_search.py ├── bucket_sort.py ├── counting_sort.py ├── heap_sort.py ├── insertion_sort.py ├── merge_sort.py ├── quickselect.py ├── quicksort.py └── selection_sort.py ├── labs ├── lab1 │ ├── zad1.py │ ├── zad2.py │ └── zad3.py ├── lab2 │ ├── zad1.py │ ├── zad2.py │ ├── zad3.py │ ├── zad4.py │ ├── zad5.py │ └── zad6.py └── lab3 │ ├── zad1.py │ ├── zad3.py │ ├── zad4.py │ ├── zad5.py │ ├── zad6.py │ └── zad7.py └── problems ├── .gitignore ├── algorithm_design_manual ├── 4.31.py ├── 4.5.py ├── 4.6.py ├── 4.9.py └── REDME.md ├── cesar.py ├── depth.py ├── dominance.py ├── kolokwia └── kol1.py ├── ksum.py ├── maxrank.py ├── median.py ├── offline ├── .gitignore ├── offline1 │ ├── zad1_v1.py │ ├── zad1_v2.py │ └── zad1_v3.py └── offline2 │ └── zad2.py ├── snow.py ├── sort_chaotic.py ├── staircase_step_count.py ├── three_sum.py └── two_sum.py /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/dsa.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /algorithm_design/problems/blackforest.py: -------------------------------------------------------------------------------- 1 | from math import inf 2 | 3 | def cut_tree(T): 4 | n = len(T) 5 | dp = [-inf] * n 6 | 7 | for i in range(n): 8 | dp[i] = max( 9 | dp[i - 2] + T[i] if i - 2 >= 0 else T[i], 10 | dp[i - 1] if i - 1 >= 0 else -inf 11 | ) 12 | 13 | return dp[n - 1] 14 | 15 | 16 | if __name__ == "__main__": 17 | print(cut_tree([2, 8, 3, 6])) 18 | -------------------------------------------------------------------------------- /algorithm_design/problems/company_party.py: -------------------------------------------------------------------------------- 1 | class Employee: 2 | def __init__(self, fun): 3 | self.employees = [] 4 | self.fun = fun 5 | 6 | # Dynamic programming happens here. 7 | self.going = -1 8 | self.not_going = -1 9 | 10 | 11 | def not_going(v: Employee): 12 | # Check for cached result. 13 | if v.not_going >= 0: 14 | return v.not_going 15 | 16 | v.not_going = 0 17 | 18 | for e in v.employees: 19 | v.not_going += going(e) 20 | 21 | return v.not_going 22 | 23 | 24 | def going(v: Employee): 25 | # Check for cached result. 26 | if v.going >= 0: 27 | return v.going 28 | 29 | # Assume v goes to the party. 30 | result = v.fun 31 | 32 | for e in v.employees: 33 | result += not_going(e) 34 | 35 | # Make sure, it's better than 36 | # if v didn't go to the party. 37 | v.going = max(result, not_going(v)) 38 | 39 | return v.going 40 | 41 | 42 | if __name__ == "__main__": 43 | # 10 44 | # / \ 45 | # 1 5 46 | # / \ 47 | # 2 1 48 | 49 | boss = Employee(10) 50 | emp1 = Employee(1) 51 | emp2 = Employee(5) 52 | emp3 = Employee(2) 53 | emp4 = Employee(1) 54 | 55 | boss.employees = [emp1, emp2] 56 | emp1.employees = [emp3, emp4] 57 | 58 | print(going(boss)) 59 | -------------------------------------------------------------------------------- /algorithm_design/problems/cut_rod.py: -------------------------------------------------------------------------------- 1 | """ 2 | The Rod Cutting Problem 3 | 4 | Given a rod of length n inches and a table of prices p(i) for i = 1, ..., n, determine 5 | the maximum revenue r(n) obtained by cutting up the rod and selling the pieces. 6 | 7 | If the price p(n) for a rod of length n is large enough, an optimal solution might 8 | require no cutting at all. 9 | 10 | For detailed explanation see "Introduction To Algorithms" p. 365 11 | """ 12 | 13 | from math import inf 14 | 15 | 16 | def naive_cut_rod(n, prices): 17 | if n == 0: 18 | return 0 19 | 20 | revenue = -inf 21 | 22 | # `i` represents the length of the segment we'll cut 23 | # from the left of the rod. 24 | for i in range(1, n + 1): 25 | revenue = max(revenue, prices[i] + naive_cut_rod(n - i, prices)) 26 | 27 | return revenue 28 | 29 | 30 | def top_down_memoization_cut_rod(n, prices, cache): 31 | if n == 0: 32 | return 0 33 | 34 | if cache[n] >= 0: 35 | return cache[n] 36 | 37 | r = -inf 38 | 39 | for i in range(1, n + 1): 40 | r = max(r, prices[i] + top_down_memoization_cut_rod(n - i, prices, cache)) 41 | 42 | cache[n] = r 43 | return r 44 | 45 | 46 | def bottom_up_cut_rod(n, prices): 47 | r = [-inf] * (n + 1) 48 | r[0] = 0 49 | 50 | cuts = [-1] * (n + 1) 51 | 52 | for j in range(1, n + 1): 53 | for i in range(1, j + 1): 54 | alt = prices[i] + r[j - i] 55 | 56 | if r[j] < alt: 57 | # Record that we've made 58 | # cut of length i. 59 | r[j] = alt 60 | cuts[j] = i 61 | 62 | return r[n], cuts 63 | 64 | 65 | if __name__ == "__main__": 66 | N = 10 67 | prices = [0, 1, 5, 8, 9, 10, 17, 17, 20, 24, 30] 68 | 69 | bottom_up, cuts = bottom_up_cut_rod(N, prices) 70 | 71 | assert \ 72 | naive_cut_rod(N, prices) == \ 73 | top_down_memoization_cut_rod(N, prices, [-inf] * (N + 1)) == \ 74 | bottom_up 75 | 76 | while N > 0: 77 | print(cuts[N]) 78 | N -= cuts[N] 79 | -------------------------------------------------------------------------------- /algorithm_design/problems/drivers.py: -------------------------------------------------------------------------------- 1 | """ 2 | C(i, j) = liczba punktów kontrolnych między przesiadkami numer `i` i `j` 3 | 4 | T(i, who) - najmniejza liczba punktów kontrolnych jaką przejedzie Marian 5 | jeżeli z punktu i wyruszył `who`. 6 | 7 | T(i, who) = { 8 | if who == "Marian": for k in range(1, 4) min(T(i + k, "Jacek") + C(i, i + k)) 9 | if who == "Jacek": for k in range (1, 4) min(T(i + k, "Marian") 10 | } 11 | """ 12 | 13 | 14 | from math import inf 15 | 16 | 17 | M = 0 18 | J = 1 19 | 20 | 21 | def process_input(P): 22 | n = len(P) 23 | 24 | P = [(P[i][0], P[i][1], i) for i in range(n)] 25 | P.sort() 26 | 27 | checkpoints = [0] # Liczba punktów kontrolnych do punktu przesiadki o indeksie `i` 28 | transfers_indexes_map = [-1] # Mapa indeksów z naszych indeksów, na oryginalne. 29 | controls_counter = 0 30 | 31 | for x, is_transfer, i in P: 32 | if is_transfer: 33 | checkpoints.append(controls_counter) 34 | transfers_indexes_map.append(i) 35 | else: 36 | controls_counter += 1 37 | 38 | checkpoints.append(controls_counter) 39 | transfers_indexes_map.append(-1) 40 | 41 | return checkpoints, transfers_indexes_map 42 | 43 | 44 | def drivers(P, _): 45 | # Przetworzenie danych 46 | 47 | checkpoints, transfers_indexes_map = process_input(P) 48 | n = len(checkpoints) 49 | 50 | # Dynamic Programming 51 | 52 | dp = [[inf, inf] for _ in range(n)] 53 | sol = [[-1, -1] for _ in range(n)] 54 | 55 | for i in range(n - 1, -1, -1): 56 | for k in range(1, 4): 57 | alt_m = dp[i + k][J] + (checkpoints[i + k] - checkpoints[i]) if i + k < n \ 58 | else checkpoints[n - 1] - checkpoints[i] 59 | 60 | if alt_m < dp[i][M]: 61 | dp[i][M] = alt_m 62 | sol[i][M] = i + k 63 | 64 | alt_j = dp[i + k][M] if i + k < n \ 65 | else 0 66 | 67 | if alt_j < dp[i][J]: 68 | dp[i][J] = alt_j 69 | sol[i][J] = i + k 70 | 71 | # Odczytanie wyniku 72 | 73 | ans = [] 74 | driver = J 75 | stop = sol[0][driver] 76 | 77 | while stop < n and transfers_indexes_map[stop] > -1: 78 | ans.append(transfers_indexes_map[stop]) 79 | driver = M if driver == J else J 80 | stop = sol[stop][driver] 81 | 82 | return ans 83 | -------------------------------------------------------------------------------- /algorithm_design/problems/fib.py: -------------------------------------------------------------------------------- 1 | def fib_top_down(n, cache): 2 | if n == 1: 3 | return 0 4 | 5 | if n == 2: 6 | return 1 7 | 8 | if cache[n] > 0: 9 | return cache[n] 10 | 11 | res = fib_top_down(n - 1, cache) + fib_top_down(n - 2, cache) 12 | cache[n] = res 13 | 14 | return res 15 | 16 | 17 | def fib_bottom_up(n): 18 | dp = [-1] * (max(n + 1, 3)) 19 | 20 | dp[1] = 0 21 | dp[2] = 1 22 | 23 | for i in range(3, n + 1): 24 | dp[i] = dp[i - 1] + dp[i - 2] 25 | 26 | return dp[n] 27 | 28 | 29 | if __name__ == "__main__": 30 | n = 4 31 | result = fib_top_down(n, [-1] * (n + 1)) 32 | 33 | print(result) 34 | 35 | assert result == fib_bottom_up(n) 36 | -------------------------------------------------------------------------------- /algorithm_design/problems/knapsack.py: -------------------------------------------------------------------------------- 1 | type Items = list[tuple[int, int]] 2 | type Cache = list[list[int]] 3 | 4 | 5 | def knapsack_top_down_aux(items: Items, i: int, capacity: int, cache: Cache): 6 | # What's the maximum value in knapsack, if we pack nonexistent item 7 | # at index 1? 8 | 9 | if i == len(items): 10 | return 0 11 | 12 | # Have we computed the solution to this subproblem before? 13 | 14 | if cache[i][capacity] > -1: 15 | return cache[i][capacity] 16 | 17 | result = knapsack_top_down_aux(items, i + 1, capacity, cache) 18 | value, size = items[i] 19 | 20 | if size <= capacity: 21 | result = max(result, knapsack_top_down_aux(items, i + 1, capacity - size, cache) + value) 22 | 23 | # Cache the result for this subproblem. 24 | 25 | cache[i][capacity] = result 26 | 27 | return result 28 | 29 | 30 | def knapsack_top_down(items: Items, capacity: int): 31 | n = len(items) 32 | cache = [[-1] * (capacity + 1) for _ in range(n)] 33 | return knapsack_top_down_aux(items, 0, capacity, cache) 34 | 35 | 36 | def knapsack_bottom_up(items: Items, capacity: int): 37 | n = len(items) 38 | 39 | # Translate recursive approach directly. 40 | # Create a 2d array that will store results for our subproblems. 41 | 42 | dp = [[0] * (capacity + 1) for _ in range(n + 1)] 43 | 44 | for i in range(n - 1, -1, -1): 45 | value, size = items[i] 46 | 47 | for s in range(size, capacity + 1): 48 | dp[i][s] = max(dp[i + 1][s], dp[i + 1][s - size] + value) 49 | 50 | return dp[0][capacity] 51 | 52 | 53 | def knapsack_bottom_up_optimised(items: Items, capacity: int): 54 | # We can notice, that single dimension dp array, 55 | # just for capacities, is enough. 56 | 57 | dp = [0] * (capacity + 1) 58 | 59 | for value, size in items: 60 | 61 | # By computing subproblems in order of capacity -> size, 62 | # we know that referring to dp[s - size] doesn't contain 63 | # result which considered current item. 64 | 65 | for s in range(capacity, size - 1, -1): 66 | dp[s] = max(dp[s], dp[s - size] + value) 67 | 68 | return dp[capacity] 69 | 70 | 71 | if __name__ == "__main__": 72 | items = [(60, 10), (100, 20), (120, 30)] # (value, size) 73 | capacity = 50 74 | expected = 220 75 | 76 | assert knapsack_top_down(items, capacity) == knapsack_bottom_up(items, capacity) \ 77 | == knapsack_bottom_up_optimised(items, capacity) == expected 78 | 79 | print(f"ok, expected: {expected}") 80 | -------------------------------------------------------------------------------- /algorithm_design/problems/knapsack2d.py: -------------------------------------------------------------------------------- 1 | type Items = list[tuple[int, int, int]] 2 | 3 | def knapsack2d(items: Items, max_height, max_weight): 4 | n = len(items) 5 | dp = [[[0] * (max_height + 1) for _ in range(max_weight + 1)] for _ in range(n + 1)] 6 | 7 | for w in range(max_weight + 1): 8 | for h in range(max_height + 1): 9 | for i in range(n): 10 | value, weight, height = items[i] 11 | dp[i][w][h] = dp[i-1][w][h] 12 | 13 | if weight <= w and height <= h: 14 | dp[i][w][h] = max(dp[i][w][h], dp[i - 1][w - weight][h - height] + value) 15 | 16 | return dp[n - 1][max_weight][max_height] 17 | -------------------------------------------------------------------------------- /algorithm_design/problems/magic.py: -------------------------------------------------------------------------------- 1 | """ 2 | T(i) = największa liczba sztabek przy wejściu do komnaty i 3 | 4 | T(i) = max( 5 | for k in parents(i): 6 | T(k) + gold_diff 7 | ) 8 | """ 9 | 10 | 11 | def magic( C ): 12 | n = len(C) 13 | gold = [-1] * n 14 | gold[0] = 0 15 | 16 | for i in range(n): 17 | if gold[i] == -1: 18 | continue 19 | 20 | g = C[i][0] 21 | caves = C[i][1:] 22 | 23 | picked_gold = min(g, 10) 24 | g -= picked_gold 25 | 26 | for k, w in caves: 27 | if w == -1 or k < g: 28 | continue 29 | 30 | current_gold = gold[i] + picked_gold - (k - g) 31 | gold[w] = max(gold[w], current_gold) 32 | 33 | return gold[n - 1] 34 | -------------------------------------------------------------------------------- /algorithm_design/problems/maximin.py: -------------------------------------------------------------------------------- 1 | """ 2 | Zależność rekurencyjna: 3 | 4 | 5 | T(i, k) = Najlepsza wartość podział przedziału [:i] po wykonaniu k podziałów. 6 | 7 | 8 | T(i, k) = max( 9 | // Wybieramy punkt kolejnego podziału (ostatni element, który będzie do niego należał) 10 | for s in range(i, n): 11 | min( 12 | sum(A[i:s + 1]), 13 | T(s + 1, k - 1) 14 | ) 15 | ) 16 | 17 | 18 | Złożoność: 19 | - O(nk) pod-problemów. 20 | - Rozwiązanie każdego podproblemu zajmuje O(n). 21 | - Zakładamy że mamy sumy prefiksowe, więc sumowanie działa w O(1) 22 | 23 | Finalna złożoność: O(n^2 * k) 24 | """ 25 | 26 | 27 | from math import inf 28 | 29 | 30 | def maximin_top_down_aux(a, i, k, cache): 31 | # If we have used all value, or we 32 | # can't create more partitions. 33 | if i == len(a) or k == 0: 34 | return -inf 35 | 36 | # This is the last partition, so it's 37 | # best value is just it's sum. 38 | if k == 1: 39 | return sum(a[i:]) 40 | 41 | # The result might be already cached. 42 | if cache[i][k] > -1: 43 | return cache[i][k] 44 | 45 | n = len(a) 46 | result = -inf 47 | 48 | for s in range(i, n): 49 | result = max( 50 | result, 51 | min( 52 | sum(a[i:s + 1]), 53 | maximin_top_down_aux(a, s + 1, k - 1, cache) 54 | ) 55 | ) 56 | 57 | cache[i][k] = result 58 | 59 | return result 60 | 61 | 62 | def maximin_top_down(a, k): 63 | cache = [[-inf] * (k + 1) for _ in range(len(a))] 64 | return maximin_top_down_aux(a, 0, k, cache) 65 | 66 | 67 | def maximin_bottom_down(a, k): 68 | n = len(a) 69 | 70 | dp = [[-inf] * (k + 1) for _ in range(n + 1)] 71 | prefix_sum = [0] * (n + 1) 72 | 73 | for i in range(n): 74 | prefix_sum[i] = prefix_sum[i - 1] + a[i] 75 | 76 | for i in range(n): 77 | dp[i][1] = sum(a[i:]) 78 | 79 | for i in range(n - 1, -1, -1): 80 | for p in range(2, k + 1): 81 | result = -inf 82 | 83 | for s in range(n - 1, i - 1, -1): 84 | result = max( 85 | result, 86 | min( 87 | prefix_sum[s] - prefix_sum[i - 1], 88 | dp[s + 1][p - 1] 89 | ) 90 | ) 91 | 92 | dp[i][p] = result 93 | 94 | return dp[0][k] 95 | 96 | 97 | if __name__ == "__main__": 98 | a = [4, 2, 7, 1, 3, 5, 9] 99 | k = 3 100 | result = maximin_top_down(a, k) 101 | assert result == maximin_bottom_down(a, k) 102 | print(f"OK, result={result}") 103 | -------------------------------------------------------------------------------- /algorithm_design/problems/maze.py: -------------------------------------------------------------------------------- 1 | from math import inf 2 | 3 | 4 | UP = 0 5 | DOWN = 1 6 | ARBITRARY = 2 7 | 8 | 9 | def in_bounds(i, j, n): 10 | return 0 <= i < n and 0 <= j < n 11 | 12 | 13 | def maze_aux(L, i, j, next_move, cache): 14 | n = len(L) 15 | 16 | if not in_bounds(i, j, n) or L[i][j] == "#": 17 | return -inf 18 | 19 | if i == 0 and j == 0: 20 | return 0 21 | 22 | if cache[i][j][next_move]: 23 | return cache[i][j][next_move] 24 | 25 | cache[i][j][next_move] = max( 26 | maze_aux(L, i - 1, j, DOWN, cache) if next_move != UP else -inf, 27 | maze_aux(L, i + 1, j, UP, cache) if next_move != DOWN else -inf, 28 | maze_aux(L, i, j - 1, ARBITRARY, cache) 29 | ) + 1 30 | 31 | return cache[i][j][next_move] 32 | 33 | 34 | def maze(L): 35 | n = len(L) 36 | cache = [[[None] * 3 for _ in range(n)] for _ in range(n)] 37 | return max(maze_aux(L, n - 1, n - 1, ARBITRARY, cache), -1) 38 | -------------------------------------------------------------------------------- /algorithm_design/problems/min_cost.py: -------------------------------------------------------------------------------- 1 | """ 2 | Liczymy funkcję 3 | - T(i) = minimalny zapłacona kwota w momencie dojazdu do parkingu i. 4 | 5 | Żeby to zadziałało, wstawiamy sztuczny parking w mieście B. 6 | """ 7 | 8 | 9 | from math import inf 10 | 11 | 12 | def min_cost_aux(i, T, O, C, cache, cheated=False): 13 | if cache[i][cheated]: 14 | return cache[i][cheated] 15 | 16 | if O[i] <= T or not cheated and O[i] <= 2 * T: 17 | return 0 18 | 19 | n = len(O) 20 | cost = inf 21 | 22 | for j in range(n): 23 | dist = O[i] - O[j] 24 | 25 | if dist > 0 and \ 26 | dist <= T or \ 27 | not cheated and dist <= 2 * T: 28 | cheated = cheated or dist > T 29 | cost = min(cost, C[j] + min_cost_aux(j, T, O, C, cache, cheated)) 30 | 31 | cache[i][cheated] = cost 32 | return cost 33 | 34 | 35 | def min_cost_rec(O, C, T, L): 36 | O = O + [L] 37 | C = C + [0] 38 | 39 | n = len(O) 40 | cache = [[None, None] for _ in range(n)] 41 | 42 | return min_cost_aux(n - 1, T, O, C, cache) 43 | 44 | 45 | def min_cost(O, C, T, L): 46 | O, C = zip(*sorted(list(zip(O + [L], C + [0])))) 47 | 48 | n = len(O) 49 | fair = [inf] * n 50 | unfair = [inf] * n 51 | 52 | for i in range(n): 53 | if O[i] <= T: 54 | fair[i] = unfair[i] = 0 55 | continue 56 | 57 | if O[i] <= 2 * T: 58 | unfair[i] = 0 59 | 60 | for k in range(i): 61 | dist = O[i] - O[k] 62 | 63 | if dist <= T: 64 | fair[i] = min(fair[i], C[k] + fair[k]) 65 | unfair[i] = min(unfair[i], C[k] + unfair[k]) 66 | elif dist <= 2*T: 67 | unfair[i] = min(unfair[i], C[k] + fair[k]) 68 | 69 | return unfair[n - 1] 70 | -------------------------------------------------------------------------------- /algorithm_design/problems/plan.py: -------------------------------------------------------------------------------- 1 | from zad8testy import runtests 2 | from queue import PriorityQueue 3 | 4 | 5 | MOVES = [(-1, 0), (1, 0), (0, -1), (0, 1)] 6 | 7 | 8 | def in_bounds(i, j, n, m): 9 | return 0 <= i < n and 0 <= j < m 10 | 11 | 12 | def gather_oil(T, i, j, visited): 13 | n = len(T) 14 | m = len(T[0]) 15 | 16 | if not in_bounds(i, j, n, m) or T[i][j] == 0 or visited[i][j]: 17 | return 0 18 | 19 | visited[i][j] = True 20 | total = T[i][j] 21 | 22 | for di, dj in MOVES: 23 | total += gather_oil(T, i + di, j + dj, visited) 24 | 25 | return total 26 | 27 | 28 | def plan(T): 29 | n = len(T) 30 | m = len(T[0]) 31 | 32 | visited = [[False] * m for _ in range(n)] 33 | oil = [0] * m 34 | 35 | for k in range(m): 36 | if T[0][k] != 0 and not visited[0][k]: 37 | oil[k] = gather_oil(T, 0, k, visited) 38 | 39 | reach = 0 40 | stops = 0 41 | 42 | while reach < m - 1: 43 | pq = PriorityQueue() 44 | 45 | for k in range(reach + 1): 46 | if oil[k] > 0: 47 | pq.put((-oil[k], k)) 48 | 49 | tanked_oil, k = pq.get() 50 | tanked_oil *= -1 51 | 52 | oil[k] = 0 53 | reach += tanked_oil 54 | stops += 1 55 | 56 | return stops 57 | 58 | 59 | # zmien all_tests na True zeby uruchomic wszystkie testy 60 | runtests(plan, all_tests=True) 61 | -------------------------------------------------------------------------------- /algorithm_design/problems/wired.py: -------------------------------------------------------------------------------- 1 | """ 2 | Obliczamy funkcję f(i, j), która mówi jaki jest najmniejszy koszt połączenia wejść 3 | na przedziale [i, j + 1]. W każdym takim przedziale, szukamy k-tego wejścia w przedziale, 4 | które połączymy z i-tym wejściem, i bierzemy koszt tego połączenia. Następnie wykorzystując 5 | funkcję f, dodajemy od tego koszt wnętrza przedziału [i + 1 : k] oraz koszt pozostałego przedziału [k + 1 : j + 1]. 6 | 7 | f(i, j) = min for k in range(i + 1, j + 1): { 8 | 1 + abs(T[i] - T[k]) + 9 | + f(i + 1, k - 1) + 10 | + f(k + 1, j) + 11 | } 12 | """ 13 | 14 | 15 | from math import inf 16 | 17 | 18 | def wired(T): 19 | n = len(T) 20 | dp = [[inf] * n for _ in range(n)] 21 | 22 | for l in range(2, n + 1, 2): 23 | for i in range(0, n - l + 1): 24 | j = i + l - 1 25 | 26 | for k in range(i + 1, j + 1, 2): 27 | dp[i][j] = min( 28 | dp[i][j], 29 | 1 + abs(T[i] - T[k]) + \ 30 | # Zauważmy, że w momencie kiedy przedziały nie istnieją, 31 | # dodajemy 0, żeby uniknąć wyjścia indeksami poza tablicę. 32 | (dp[i + 1][k - 1] if i + 1 < k - 1 else 0) + \ 33 | (dp[k + 1][j] if k + 1 < j else 0) 34 | ) 35 | 36 | return dp[0][n - 1] 37 | -------------------------------------------------------------------------------- /data_structures/bst.py: -------------------------------------------------------------------------------- 1 | """ 2 | Implementacja BST oparta na Cormen'ie 3 | """ 4 | 5 | 6 | class Node: 7 | def __init__(self, value): 8 | self.value = value 9 | self.parent = None 10 | self.left = None 11 | self.right = None 12 | 13 | 14 | class BST: 15 | def __init__(self): 16 | self.root = None 17 | 18 | # Visits nodes in order. 19 | def inorder_walk(self): 20 | self._inorder_walk(self.root) 21 | 22 | def _inorder_walk(self, node): 23 | if node is not None: 24 | self._inorder_walk(node.left) 25 | print(node.value) 26 | self._inorder_walk(node.right) 27 | 28 | # Inserts the value to the BST. 29 | # Runs in O(h) time. 30 | def insert(self, value): 31 | # Store pointers to current node we'll compare to 32 | # and current parent of inserted value. 33 | cmp = self.root 34 | parent = None 35 | 36 | # While there is a node we can compare to 37 | while cmp: 38 | # Set it to parent. 39 | parent = cmp 40 | 41 | # Float down the tree according to 42 | # standard BST property. 43 | if value < cmp.value: 44 | # This new cmp potentially is our new parent 45 | # in the next iteration. 46 | cmp = cmp.left 47 | else: 48 | cmp = cmp.right 49 | 50 | # Create new node and assign found parent. 51 | new = Node(value) 52 | new.parent = parent 53 | 54 | # If the parent is None, we are the first node in the tree (root) 55 | # Otherwise, attach to left or right leaf of parent. 56 | 57 | # Notice that the wile above guarantees that the side we'll 58 | # choose must be empty. 59 | 60 | if parent is None: 61 | self.root = new 62 | elif value < parent.value: 63 | parent.left = new 64 | else: 65 | parent.right = new 66 | 67 | # Search for value recursively. 68 | # Runs in O(h) time. 69 | def search(self, value): 70 | self._search(self.root, value) 71 | 72 | def _search(self, current, value): 73 | if current is None or current.value == value: 74 | return current 75 | 76 | if value < current.value: 77 | return self._search(current.left, value) 78 | else: 79 | return self._search(current.right, value) 80 | 81 | # Search for value iteratively 82 | # Runs in O(h) time. 83 | def search_iter(self, value): 84 | current = self.root 85 | 86 | while current and current.value != value: 87 | if value < current.value: 88 | current = current.left 89 | else: 90 | current = current.right 91 | 92 | return current 93 | 94 | # Find min from the given root. 95 | # Runs in O(h) time. 96 | def min(self, root=None): 97 | curr = root if root else self.root 98 | 99 | while curr.left: 100 | curr = curr.left 101 | 102 | return curr 103 | 104 | # Find max node from the given root. 105 | # Runs in O(h) time. 106 | def max(self, root=None): 107 | curr = root if root else self.root 108 | 109 | while curr.right: 110 | curr = curr.right 111 | 112 | return curr 113 | 114 | # Find next node in in-order walk order. 115 | # Runs in O(h) time. 116 | def successor(self, node): 117 | # If node has right child, find min in right subtree. 118 | if node.right: 119 | return self.min(node.right) 120 | 121 | # Otherwise find first parent in whose left subtree we are. 122 | parent = node.parent 123 | 124 | # While parent exists and current node is it's right child 125 | # (so parent isn't next in in-order walk) 126 | while parent is not None and node is parent.right: 127 | node = parent 128 | parent = parent.parent 129 | 130 | return parent 131 | 132 | # Transplant replaces subtree rooted at u with subtree rooted at v. 133 | # Visually detach the subtree u, and replace it with v. 134 | # This operation is just a util and DOES NOT maintain BST property. 135 | def transplant(self, u, v): 136 | if u is None: 137 | self.root = v 138 | elif u is u.parent.left: 139 | u.parent.left = v 140 | else: 141 | u.parent.right = v 142 | 143 | if v is not None: 144 | v.p = u.p 145 | 146 | def remove(self, x): 147 | if x.left is None: 148 | self.transplant(x, x.right) 149 | return 150 | 151 | if x.right is None: 152 | self.transplant(x, x.left) 153 | return 154 | 155 | y = self.min(x.right) 156 | 157 | if y is not x.right: 158 | self.transplant(y, y.right) 159 | y.right = x 160 | y.right.p = y 161 | 162 | self.transplant(x, y) 163 | y.left = x.left 164 | y.left.parent = y 165 | -------------------------------------------------------------------------------- /data_structures/disjoint_set.py: -------------------------------------------------------------------------------- 1 | """ 2 | Disjoint set with Path Compression and Rank heuristics. 3 | 4 | Rank Heuristic: 5 | When we union two sets (represented as trees) we 6 | attach smaller tree to the root of the taller tree. 7 | This makes our trees shorter, and improves efficiency of find(x). 8 | 9 | Path Compression: 10 | find(x) returns the root of the x's set. 11 | During find operation, we set x's parent to the root of its set. 12 | Thanks to this, we don't have to traverse all the intermediate 13 | nodes again in another call to find(x). 14 | """ 15 | 16 | 17 | class Node: 18 | def __init__(self): 19 | self.x = None 20 | self.parent = None 21 | self.rank = None 22 | 23 | 24 | # Creates a new root node of a tree set. 25 | def make_set(x): 26 | root = Node() 27 | root.x = x 28 | root.parent = root 29 | root.rank = 0 30 | return root 31 | 32 | 33 | # Union joins two sets using rank heuristic. 34 | def union(x, y): 35 | # Replace x and y with root's 36 | # of their sets. 37 | x = find(x) 38 | y = find(y) 39 | 40 | # If both x and y belong to the 41 | # same set, do nothing. 42 | if x == y: 43 | return 44 | 45 | if x.rank > y.rank: 46 | y.parent = x 47 | else: 48 | x.parent = y 49 | # If both sets had the same rank, increment 50 | # the height estimate. 51 | if x.rank == y.rank: 52 | y.rank = y.rank + 1 53 | 54 | 55 | # Follows the path to the root of 56 | # the current set. 57 | def find(x): 58 | # If x is not the root of the set 59 | # apply path compression. 60 | if x is not x.parent: 61 | x.parent = find(x.parent) 62 | return x.parent 63 | -------------------------------------------------------------------------------- /data_structures/labs/zad1.py: -------------------------------------------------------------------------------- 1 | class Queue: 2 | def __init__(self): 3 | self.incoming = [] 4 | self.outgoing = [] 5 | 6 | def enqueue(self, value): 7 | self.incoming.append(value) 8 | 9 | def dequeue(self): 10 | if len(self.outgoing) == 0: 11 | while len(self.incoming) > 0: 12 | self.outgoing.append(self.incoming.pop(-1)) 13 | 14 | return self.outgoing.pop(-1) if len(self.outgoing) > 0 else None 15 | 16 | 17 | if __name__ == "__main__": 18 | queue = Queue() 19 | 20 | queue.enqueue(1) 21 | queue.enqueue(2) 22 | queue.enqueue(3) 23 | 24 | print(queue.dequeue()) 25 | print(queue.dequeue()) 26 | print(queue.dequeue()) 27 | print(queue.dequeue()) 28 | -------------------------------------------------------------------------------- /data_structures/labs/zad2.py: -------------------------------------------------------------------------------- 1 | # Proszę zaimplementować następujące operacje na drzewie BST 2 | # 3 | # 1) Wstawianie do drzewa BST 4 | # 2) następnik / poprzednik 5 | # 3) znalezienie minimum 6 | 7 | class Node: 8 | def __init__(self, value): 9 | self.value = value 10 | self.left = None 11 | self.right = None 12 | 13 | 14 | class BST: 15 | def __init__(self): 16 | self.root = None 17 | 18 | def insert(self, value): 19 | cmp = self.root 20 | parent = None 21 | 22 | while cmp: 23 | parent = cmp 24 | 25 | if value < cmp.value: 26 | cmp = cmp.left 27 | else: 28 | cmp = cmp.right 29 | 30 | new = Node(value) 31 | new.parent = parent 32 | 33 | if parent is None: 34 | self.root = new 35 | elif value < parent.value: 36 | parent.left = new 37 | else: 38 | parent.right = new 39 | 40 | def min(self, root=None): 41 | curr = root if root is not None else None 42 | 43 | while curr.left: 44 | curr = curr.left 45 | 46 | return curr 47 | 48 | def successor(self, node): 49 | if node.right: 50 | return self.min(node.right) 51 | 52 | parent = node.parent 53 | 54 | while parent is not None and node is parent.right: 55 | node = parent 56 | parent = node.parent 57 | 58 | return parent 59 | -------------------------------------------------------------------------------- /data_structures/labs/zad3.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/senicko/dsa/34c15e1037e66f180dc48f34dc51eedb27121137/data_structures/labs/zad3.py -------------------------------------------------------------------------------- /data_structures/queue.py: -------------------------------------------------------------------------------- 1 | class Queue: 2 | def __init__(self, default_size=16): 3 | self.queue = [None] * default_size 4 | self.max_size = default_size 5 | self.size = 0 6 | self.head = 0 7 | 8 | # Enqueues the item to the queue. 9 | def enqueue(self, item): 10 | if self.size == self.max_size: 11 | self.double_size() 12 | self.enqueue(item) 13 | return 14 | 15 | self.queue[self.head + self.size] = item 16 | self.size += 1 17 | 18 | # Dequeues an item from the queue array. 19 | def dequeue(self): 20 | if self.size == 0: 21 | raise Exception("queue underflow") 22 | 23 | item = self.queue[self.head] 24 | self.head = (self.head + 1) % self.max_size 25 | self.size -= 1 26 | 27 | return item 28 | 29 | # Doubles in size the queue array. 30 | def double_size(self): 31 | self.max_size *= 2 32 | new = [None] * self.max_size 33 | 34 | for i in range(self.size): 35 | new[i] = self.queue[(self.head + i) % self.size] 36 | 37 | self.head = 0 38 | 39 | 40 | if __name__ == "__main__": 41 | queue = Queue() 42 | 43 | queue.enqueue(1) 44 | queue.enqueue(2) 45 | queue.enqueue(3) 46 | 47 | print(queue.dequeue()) 48 | print(queue.dequeue()) 49 | print(queue.dequeue()) 50 | print(queue.dequeue()) 51 | -------------------------------------------------------------------------------- /data_structures/queue_list.py: -------------------------------------------------------------------------------- 1 | class Node: 2 | def __init__(self, value=None): 3 | self.value = value 4 | self.next = None 5 | 6 | 7 | class Queue: 8 | def __init__(self): 9 | self.front = None 10 | self.rear = None 11 | 12 | # Checks if there are any nodes in the queue. 13 | def is_empty(self): 14 | return self.front is None 15 | 16 | def enqueue(self, item): 17 | new_node = Node(item) 18 | 19 | # If our queue is empty, set both 20 | # front and rear to new_node. 21 | if self.is_empty(): 22 | self.front = self.rear = new_node 23 | return 24 | 25 | self.rear.next = new_node 26 | self.rear = new_node 27 | 28 | def dequeue(self): 29 | if self.is_empty(): 30 | return None 31 | 32 | # Store removed node 33 | removed = self.front 34 | 35 | # Advance front node 36 | self.front = self.front.next 37 | 38 | # If front node advanced to None 39 | # we don't have any nodes left. 40 | if self.front is None: 41 | self.rear = None 42 | 43 | return removed.value 44 | 45 | 46 | if __name__ == "__main__": 47 | queue = Queue() 48 | 49 | queue.enqueue(1) 50 | queue.enqueue(2) 51 | queue.enqueue(3) 52 | 53 | print(queue.dequeue()) 54 | print(queue.dequeue()) 55 | print(queue.dequeue()) 56 | -------------------------------------------------------------------------------- /graphs/algorithms/bellman-ford.py: -------------------------------------------------------------------------------- 1 | from math import inf 2 | 3 | 4 | def bellman_ford(G, s): 5 | n = len(G) 6 | 7 | dist = [inf] * n 8 | parent = [None] * n 9 | dist[s] = 0 10 | 11 | # Examine all of |E| edges |V| - 1 times. 12 | for _ in range(n - 1): 13 | for u in range(n): 14 | for v, w in G[u]: 15 | # Relax. 16 | alt = dist[u] + w 17 | 18 | if dist[v] > alt: 19 | dist[v] = alt 20 | parent[v] = u 21 | 22 | # Examine all edges. 23 | # Note that dist[v] <= dist[u] + w(u, v) (For All u that have an edge to v) 24 | # So if this condition is met everything is fine. 25 | for u in range(n): 26 | for v, w in G[u]: 27 | if dist[v] > dist[u] + w: 28 | return False 29 | 30 | return True 31 | -------------------------------------------------------------------------------- /graphs/algorithms/bfs.py: -------------------------------------------------------------------------------- 1 | from math import inf 2 | from collections import deque 3 | 4 | 5 | def bfs_list(graph, s): 6 | n = len(graph) 7 | queue = deque() 8 | 9 | # Initialize state arrays. 10 | visited = [False] * n 11 | parent = [None] * n 12 | distance = [inf] * n 13 | 14 | # Mark starting node as visited. 15 | visited[s] = True 16 | distance[s] = 0 17 | queue.appendleft(s) 18 | 19 | while queue: 20 | v = queue.pop() 21 | 22 | # For every neighbour of v. 23 | for u in graph[v]: 24 | # Skip it if it's already visited. 25 | if visited[u]: 26 | continue 27 | 28 | # Mark it as visited. 29 | visited[u] = True 30 | parent[u] = v 31 | distance[u] = distance[v] + 1 32 | queue.appendleft(u) 33 | 34 | return visited, distance, parent 35 | 36 | 37 | def bfs_matrix(graph, s): 38 | n = len(graph) 39 | queue = deque() 40 | 41 | # Initialize state arrays. 42 | visited = [False] * n 43 | parent = [None] * n 44 | distance = [inf] * n 45 | 46 | # Mark starting node as visited. 47 | visited[s] = True 48 | distance[s] = 0 49 | queue.appendleft(s) 50 | 51 | while queue: 52 | v = queue.pop() 53 | 54 | # This for always makes O(V) iterations 55 | # making the algorithm run in O(V^2) 56 | # time regardless of graph's density. 57 | for u in range(n): 58 | if graph[v][u] != 1 or visited[u]: 59 | continue 60 | 61 | visited[u] = True 62 | parent[u] = v 63 | distance[u] = distance[v] + 1 64 | queue.appendleft(u) 65 | 66 | return visited, distance, parent 67 | 68 | 69 | if __name__ == "__main__": 70 | graph_list = [ 71 | [1, 4], 72 | [2], 73 | [3], 74 | [], 75 | [2] 76 | ] 77 | 78 | graph_matrix = [ 79 | [0, 1, 0, 0, 1], 80 | [0, 0, 1, 0, 0], 81 | [0, 0, 0, 1, 0], 82 | [0, 0, 0, 0, 0], 83 | [0, 0, 1, 0, 0] 84 | ] 85 | 86 | s = 0 87 | result_list = bfs_list(graph_list, s) 88 | result_matrix = bfs_matrix(graph_matrix, s) 89 | 90 | assert result_list == result_matrix 91 | print(result_list) 92 | -------------------------------------------------------------------------------- /graphs/algorithms/dag_shortest_path.py: -------------------------------------------------------------------------------- 1 | """ 2 | Cool application: PERT chart 3 | """ 4 | 5 | from math import inf 6 | 7 | 8 | def topological_sort(G): 9 | n = len(G) 10 | visited = [False] * n 11 | order = [] 12 | 13 | def dfs_visit(v): 14 | visited[v] = True 15 | 16 | for u, _ in G[v]: 17 | if not visited[u]: 18 | dfs_visit(u) 19 | 20 | order.append(v) 21 | 22 | for v in range(n): 23 | if not visited[v]: 24 | dfs_visit(v) 25 | 26 | order.reverse() 27 | return order 28 | 29 | 30 | def dag_shortest_path(G, s): 31 | n = len(G) 32 | 33 | distances = [inf] * n 34 | distances[s] = 0 35 | parents = [None] * n 36 | 37 | order = topological_sort(G) 38 | 39 | def relax(v, u, w): 40 | if distances[u] > distances[v] + w: 41 | distances[u] = distances[v] + w 42 | parents[u] = v 43 | 44 | # We're making use of the fact that our graph is 45 | # a DAG by relaxing edges in topological order. 46 | 47 | for v in order: 48 | for u, w in G[v]: 49 | relax(v, u, w) 50 | 51 | return distances, parents 52 | 53 | 54 | if __name__ == "__main__": 55 | graph = [ 56 | [(1, 2), (2, 4), (3, 2)], 57 | [], 58 | [(3, 4)], 59 | [] 60 | ] 61 | 62 | print(dag_shortest_path(graph, 0)) 63 | -------------------------------------------------------------------------------- /graphs/algorithms/dfs.py: -------------------------------------------------------------------------------- 1 | from math import inf 2 | 3 | 4 | def dfs_list(graph): 5 | n = len(graph) 6 | 7 | time = 0 8 | parent = [None] * n 9 | visited = [False] * n 10 | discovery = [inf] * n 11 | processed = [inf] * n 12 | 13 | def dfs_visit(v): 14 | nonlocal time 15 | 16 | # Increment time at visit 17 | time += 1 18 | discovery[v] = time 19 | visited[v] = True 20 | 21 | for u in graph[v]: 22 | if not visited[u]: 23 | parent[u] = v 24 | dfs_visit(u) 25 | 26 | # Increment time at backtrack 27 | time += 1 28 | processed[v] = time 29 | 30 | for v in range(n): 31 | if not visited[v]: 32 | dfs_visit(v) 33 | 34 | return visited, parent, discovery, processed 35 | 36 | 37 | def dfs_matrix(graph): 38 | n = len(graph) 39 | 40 | time = 0 41 | visited = [False] * n 42 | parent = [None] * n 43 | discovery = [inf] * n 44 | processed = [inf] * n 45 | 46 | def dfs_visit(v): 47 | nonlocal time 48 | 49 | # Increment time at visit 50 | time += 1 51 | visited[v] = True 52 | discovery[v] = time 53 | 54 | for u in range(n): 55 | if graph[v][u] == 1 and not visited[u]: 56 | parent[u] = v 57 | dfs_visit(u) 58 | 59 | # Increment time at backtrack 60 | time += 1 61 | processed[v] = time 62 | 63 | for v in range(n): 64 | if not visited[v]: 65 | dfs_visit(v) 66 | 67 | return visited, parent, discovery, processed 68 | 69 | 70 | if __name__ == "__main__": 71 | graph_list = [ 72 | [1, 4], 73 | [2], 74 | [3], 75 | [], 76 | [2] 77 | ] 78 | 79 | graph_matrix = [ 80 | [0, 1, 0, 0, 1], 81 | [0, 0, 1, 0, 0], 82 | [0, 0, 0, 1, 0], 83 | [0, 0, 0, 0, 0], 84 | [0, 0, 1, 0, 0] 85 | ] 86 | 87 | result_list = dfs_list(graph_list) 88 | result_matrix = dfs_matrix(graph_matrix) 89 | assert result_list == result_matrix 90 | print(result_list) 91 | -------------------------------------------------------------------------------- /graphs/algorithms/dijkstra.py: -------------------------------------------------------------------------------- 1 | from math import inf 2 | from queue import PriorityQueue 3 | 4 | 5 | def dijkstra(G, s): 6 | n = len(G) 7 | parents = [None] * n 8 | 9 | dist = [inf] * n 10 | dist[s] = 0 11 | 12 | queue = PriorityQueue() 13 | queue.put((dist[s], s)) 14 | 15 | while not queue.empty(): 16 | v_dist, v = queue.get() 17 | 18 | # If we have already found a shorter path 19 | # to v, continue. 20 | if v_dist > dist[v]: 21 | continue 22 | 23 | for u, w in G[v]: 24 | if dist[u] > dist[v] + w: 25 | dist[u] = dist[v] + w 26 | parents[u] = v 27 | queue.put((dist[u], u)) 28 | 29 | return dist, parents 30 | 31 | 32 | if __name__ == "__main__": 33 | graph = [ 34 | [(1, 10), (5, 9999)], 35 | [(0, 10), (2, 10)], 36 | [(1, 10), (3, 10)], 37 | [(2, 10), (4, 10)], 38 | [(3, 10), (5, 9999)], 39 | [(0, 9999), (4, 9999)] 40 | ] 41 | 42 | print(dijkstra(graph, 0)) 43 | -------------------------------------------------------------------------------- /graphs/algorithms/find_articulation_points.py: -------------------------------------------------------------------------------- 1 | from math import inf 2 | 3 | 4 | def find_articulation_points(graph): 5 | n = len(graph) 6 | 7 | time = 0 8 | low = [inf] * n 9 | discovery = [inf] * n 10 | visited = [False] * n 11 | points = [False] * n 12 | 13 | def dfs_visit(u, parent=None): 14 | nonlocal time 15 | 16 | time += 1 17 | visited[u] = True 18 | low[u] = discovery[u] = time 19 | children = 0 20 | 21 | for v in graph[u]: 22 | if not visited[v]: 23 | children += 1 24 | dfs_visit(v, u) 25 | low[u] = min(low[u], low[v]) 26 | 27 | if parent is not None and low[v] >= discovery[u]: 28 | points[u] = True 29 | elif parent != v: 30 | low[u] = min(low[u], discovery[v]) 31 | 32 | if parent is None and children >= 2: 33 | points[u] = True 34 | 35 | for u in range(n): 36 | if not visited[u]: 37 | dfs_visit(u) 38 | 39 | return points 40 | 41 | 42 | if __name__ == "__main__": 43 | graph = [ 44 | [1, 2], 45 | [0, 2, 3], 46 | [0, 1], 47 | [1, 4], 48 | [3] 49 | ] 50 | 51 | print(find_articulation_points(graph)) 52 | -------------------------------------------------------------------------------- /graphs/algorithms/find_bridges.py: -------------------------------------------------------------------------------- 1 | from math import inf 2 | 3 | 4 | def find_bridges(graph): 5 | n = len(graph) 6 | bridges = [] 7 | 8 | time = 0 9 | visited = [False] * n 10 | discovery = [0] * n 11 | low = [inf] * n 12 | 13 | def dfs_visit(u, parent=-1): 14 | nonlocal time 15 | 16 | visited[u] = True 17 | time += 1 18 | low[u] = discovery[u] = time 19 | 20 | for v in graph[u]: 21 | if not visited[v]: 22 | dfs_visit(v, u) 23 | 24 | # Can v reach vertex with discovery 25 | # time lower than low[v]? 26 | 27 | low[u] = min(low[u], low[v]) 28 | 29 | # If lowest discovery time reachable by v is 30 | # greater than discovery[v], {v, v} is a bridge. 31 | 32 | if low[v] > discovery[u]: 33 | bridges.append((u, v)) 34 | elif v != parent: 35 | # Does the vertex to which we have found 36 | # a back edge has lower discovery time? 37 | low[u] = min(low[u], discovery[v]) 38 | 39 | for v in range(n): 40 | if not visited[v]: 41 | dfs_visit(v) 42 | 43 | return bridges 44 | 45 | 46 | if __name__ == "__main__": 47 | graph = [ 48 | [1], 49 | [0, 2], 50 | [1, 3, 4], 51 | [2, 4], 52 | [3, 2] 53 | ] 54 | 55 | print(find_bridges(graph)) 56 | -------------------------------------------------------------------------------- /graphs/algorithms/floyd_warshall.py: -------------------------------------------------------------------------------- 1 | """ 2 | Straight Facts: 3 | 4 | In the main loop (*) 5 | 6 | 1) dist[u][v], dist[u][k] and dist[k][u] are 7 | lengths of paths between nodes, which intermediate 8 | nodes are from set V = { 1, ..., k - 1 }. 9 | 10 | 2) We can check if dist[u][v] > dist[u][k] + dist[k][v] 11 | and if so, replace the shortest path between u and v 12 | with one that contains k as intermediate node. 13 | """ 14 | 15 | from math import inf 16 | 17 | 18 | def floyd_warshall(graph): 19 | n = len(graph) 20 | 21 | dist = [[inf] * n for _ in range(n)] 22 | 23 | for u in range(n): 24 | dist[u][u] = 0 25 | 26 | for v, w in graph[u]: 27 | dist[u][v] = w 28 | 29 | for k in range(n): 30 | for u in range(n): 31 | for v in range(n): 32 | # (*) 33 | alt = dist[u][k] + dist[k][v] 34 | if dist[u][v] > alt: 35 | dist[u][v] = alt 36 | 37 | return dist 38 | -------------------------------------------------------------------------------- /graphs/algorithms/kruskal.py: -------------------------------------------------------------------------------- 1 | class Node: 2 | def __init__(self, v): 3 | self.parent = None 4 | self.v = v 5 | self.rank = 0 6 | 7 | 8 | def make_set(v): 9 | root = Node(v) 10 | root.parent = root 11 | return root 12 | 13 | 14 | def find(v): 15 | if v is not v.parent: 16 | v.parent = find(v.parent) 17 | return v.parent 18 | 19 | 20 | def union(u, v): 21 | u = find(u) 22 | v = find(v) 23 | 24 | if u == v: 25 | return 26 | 27 | if u.rank < v.rank: 28 | u.parent = v 29 | else: 30 | v.parent = u 31 | 32 | if v.rank == u.rank: 33 | u.rank += 1 34 | 35 | 36 | def kruskal(graph): 37 | n = len(graph) 38 | mst = [] 39 | 40 | # Create a list of the edges in graph. 41 | 42 | edges = [] 43 | 44 | for u in range(n): 45 | for v, w in graph[u]: 46 | edges.append((u, v, w)) 47 | 48 | # Sort edges in ascending order. 49 | 50 | edges.sort(key=lambda x: x[2]) 51 | 52 | # Create a set from every vertex. 53 | 54 | sets = [None] * n 55 | 56 | for u in range(n): 57 | sets[u] = make_set(u) 58 | 59 | # Process edges in ascending order 60 | # of weights. 61 | 62 | for e in edges: 63 | u, v, _ = e 64 | u = sets[u] 65 | v = sets[v] 66 | 67 | if find(u) is not find(v): 68 | mst.append(e) 69 | union(u, v) 70 | 71 | return mst 72 | 73 | 74 | if __name__ == "__main__": 75 | nodes = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'] 76 | 77 | graph = [ 78 | [(1, 4), (7, 8)], # 0: a 79 | [(0, 4), (2, 8), (7, 11)], # 1: b 80 | [(1, 8), (3, 7), (5, 4), (8, 2)], # 2: c 81 | [(2, 7), (4, 9), (5, 14)], # 3: d 82 | [(3, 9), (5, 10)], # 4: e 83 | [(2, 4), (3, 14), (4, 10), (6, 2)], # 5: f 84 | [(5, 2), (7, 1), (8, 6)], # 6: g 85 | [(0, 8), (1, 11), (6, 1), (8, 7)], # 7: h 86 | [(2, 2), (6, 6), (7, 7)] # 8: i 87 | ] 88 | 89 | mst = kruskal(graph) 90 | 91 | print("Found MST: ") 92 | for u, v, w in mst: 93 | print(f"{nodes[u]} -({w})- {nodes[v]}") 94 | -------------------------------------------------------------------------------- /graphs/algorithms/maximum_bipartite_matching.py: -------------------------------------------------------------------------------- 1 | """ 2 | Wyszukiwanie maksymalnego skojarzenia w grafie korzystając z DFS 3 | 4 | Bardzo spoko artykuł na Geeks For Geeks 5 | https://www.geeksforgeeks.org/maximum-bipartite-matching/ 6 | """ 7 | 8 | 9 | def maximum_bipartite_matching(graph, jobs): 10 | n = len(graph) 11 | job_worker = [None] * jobs 12 | 13 | def bpm(worker, visited): 14 | for job in graph[worker]: 15 | if not visited[job]: 16 | visited[job] = True 17 | 18 | if job_worker[job] is None or bpm(job_worker[job], visited): 19 | job_worker[job] = worker 20 | return True 21 | 22 | return False 23 | 24 | result = 0 25 | 26 | for worker in range(n): 27 | visited = [False] * jobs 28 | 29 | if bpm(worker, visited): 30 | result += 1 31 | 32 | return result 33 | 34 | 35 | if __name__ == "__main__": 36 | # Input graph is represented as Edmonds Matrix. 37 | # Each row can be understood as worker, 38 | # and each array as a list of machines (jobs) they can use. 39 | 40 | jobs = 5 41 | 42 | graph = [ 43 | [0, 1], # Worker 0 can do Job 0 and Job 1 44 | [1, 2], 45 | [0, 3], 46 | [3, 4] 47 | ] 48 | 49 | print(maximum_bipartite_matching(graph, jobs)) 50 | -------------------------------------------------------------------------------- /graphs/algorithms/scc.py: -------------------------------------------------------------------------------- 1 | def build_transpose(graph): 2 | n = len(graph) 3 | new = [[] for _ in range(n)] 4 | 5 | for u in range(n): 6 | for v in graph[u]: 7 | new[v].append(u) 8 | 9 | return new 10 | 11 | 12 | def get_order(graph): 13 | n = len(graph) 14 | visited = [False] * n 15 | order = [] 16 | 17 | for u in range(n): 18 | if not visited[u]: 19 | visited[u] = True 20 | dfs_visit(graph, visited, u, order) 21 | 22 | order.reverse() 23 | return order 24 | 25 | 26 | def dfs_visit(graph, visited, v, out): 27 | for u in graph[v]: 28 | if not visited[u]: 29 | visited[u] = True 30 | dfs_visit(graph, visited, u, out) 31 | 32 | out.append(v) 33 | 34 | 35 | def extract_components(graph, order): 36 | n = len(graph) 37 | visited = [False] * n 38 | 39 | components = [] 40 | 41 | for u in order: 42 | if not visited[u]: 43 | visited[u] = True 44 | component = [] 45 | dfs_visit(graph, visited, u, component) 46 | components.append(component) 47 | 48 | return components 49 | 50 | 51 | def scc(graph): 52 | order = get_order(graph) 53 | transpose = build_transpose(graph) 54 | return extract_components(transpose, order) 55 | 56 | 57 | if __name__ == "__main__": 58 | graph = [ 59 | [1], 60 | [2, 6, 5], 61 | [3], 62 | [4, 2], 63 | [4], 64 | [0, 2], 65 | [3, 7], 66 | [4, 6] 67 | ] 68 | 69 | print(scc(graph)) 70 | -------------------------------------------------------------------------------- /graphs/algorithms/topological_sort.py: -------------------------------------------------------------------------------- 1 | def topological_sort(graph): 2 | n = len(graph) 3 | result = [] 4 | visited = [False] * n 5 | 6 | def dfs_visit(v): 7 | visited[v] = True 8 | 9 | for u in graph[v]: 10 | if not visited[u]: 11 | dfs_visit(u) 12 | 13 | # Regular DFS. We are just adding 14 | # nodes to the result array. 15 | 16 | result.append(v) 17 | 18 | for v in range(n): 19 | if not visited[v]: 20 | dfs_visit(v) 21 | 22 | # At the end we have to reverse the result 23 | # array as in line 17, we actually want 24 | # to append to the beginning of the list. 25 | 26 | result.reverse() 27 | return result 28 | 29 | 30 | if __name__ == "__main__": 31 | names = { 32 | 0: "undershorts", 33 | 1: "pants", 34 | 2: "belt", 35 | 3: "shirt", 36 | 4: "tie", 37 | 5: "jacket", 38 | 6: "shoes", 39 | 7: "socks", 40 | 8: "watch" 41 | } 42 | 43 | graph = [ 44 | [1, 6], 45 | [2, 6], 46 | [5], 47 | [2, 4], 48 | [5], 49 | [], 50 | [], 51 | [6], 52 | [] 53 | ] 54 | 55 | sorted_events = topological_sort(graph) 56 | print([names[e] for e in sorted_events]) 57 | -------------------------------------------------------------------------------- /graphs/algorithms/topological_sort_kahan.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | 3 | 4 | def topological_sort_kahan(graph): 5 | n = len(graph) 6 | result = [] 7 | 8 | # Count in-degree of every vertex 9 | # in the graph. 10 | 11 | in_deg = [0] * n 12 | 13 | for v in range(n): 14 | for u in graph[v]: 15 | in_deg[u] += 1 16 | 17 | # Add vertices with in-degree of 0 18 | # to a queue. 19 | 20 | queue = deque([v for v in range(n) if in_deg[v] == 0]) 21 | 22 | while queue: 23 | v = queue.pop() 24 | result.append(v) 25 | 26 | # Update in-degree of v's neighbours 27 | # by decrementing them by one. 28 | 29 | for u in graph[v]: 30 | in_deg[u] -= 1 31 | 32 | # If u's in-degree reached 0, 33 | # add it to the queue. 34 | 35 | if in_deg[u] == 0: 36 | queue.appendleft(u) 37 | 38 | return result 39 | 40 | 41 | if __name__ == "__main__": 42 | names = { 43 | 0: "undershorts", 44 | 1: "pants", 45 | 2: "belt", 46 | 3: "shirt", 47 | 4: "tie", 48 | 5: "jacket", 49 | 6: "shoes", 50 | 7: "socks", 51 | 8: "watch" 52 | } 53 | 54 | graph = [ 55 | [1, 6], 56 | [2, 6], 57 | [5], 58 | [2, 4], 59 | [5], 60 | [], 61 | [], 62 | [6], 63 | [] 64 | ] 65 | 66 | sorted_events = topological_sort_kahan(graph) 67 | print([names[e] for e in sorted_events]) 68 | -------------------------------------------------------------------------------- /graphs/algorithms/transitive_closure.py: -------------------------------------------------------------------------------- 1 | def transitive_closure(graph): 2 | n = len(graph) 3 | 4 | result = [[0] * n for _ in range(n)] 5 | 6 | for u in range(n): 7 | for v in range(n): 8 | if graph[u][v]: 9 | result[u][v] = 1 10 | 11 | for k in range(n): 12 | # k is the end vertex in set of intermediate vertices. 13 | # So At every iteration V = { 1, ..., k - 1 } is a set 14 | # of vertices that were examined to construct paths 15 | # between every two vertices. 16 | 17 | for u in range(n): 18 | for v in range(n): 19 | if u == v: 20 | continue 21 | 22 | # Here, we check if it's possible to reach 23 | # v from u using k as an intermediate vertex. 24 | if result[u][k] == 1 and result[k][v] == 1: 25 | result[u][v] = 1 26 | 27 | return result 28 | 29 | 30 | if __name__ == "__main__": 31 | graph = [ 32 | [0, 1, 0, 0, 1, 0], # edges: 0-1, 0-4 33 | [1, 0, 1, 0, 1, 0], # edges: 1-0, 1-2, 1-4 34 | [0, 1, 0, 1, 0, 0], # edges: 2-1, 2-3 35 | [0, 0, 1, 0, 0, 1], # edges: 3-2, 3-5 36 | [1, 1, 0, 0, 0, 0], # edges: 4-0, 4-1 37 | [0, 0, 0, 1, 0, 0], # edge: 5-3 38 | ] 39 | 40 | print(*transitive_closure(graph), sep="\n") 41 | -------------------------------------------------------------------------------- /graphs/labs/lab1/zad1.py: -------------------------------------------------------------------------------- 1 | # Sprawdzenie, czy graf jest dwudzielny. 2 | 3 | from collections import deque 4 | 5 | 6 | def is_bipartite_list(graph, s): 7 | n = len(graph) 8 | queue = deque() 9 | 10 | # State 11 | # We will color nodes in 1 and -1 12 | colors = [None] * n 13 | 14 | # Init start vertex 15 | # Color starting node 16 | colors[s] = 1 17 | queue.appendleft(s) 18 | 19 | while queue: 20 | v = queue.pop() 21 | 22 | for u in graph[v]: 23 | if colors[u] is None: 24 | colors[u] = colors[v] * -1 25 | queue.appendleft(u) 26 | # Two connected nodes have the same color. 27 | # The graph is not bipartite. 28 | elif colors[u] == colors[v]: 29 | return False 30 | 31 | # If we don't meet any of the above conditions everything is fine. 32 | # It means that node u was already visited, but it has the color we 33 | # wanted to assign. 34 | 35 | return True 36 | 37 | 38 | # The same thing but with adjacency matrix representation. 39 | def is_bipartite_matrix(graph, s): 40 | n = len(graph) 41 | queue = deque() 42 | 43 | # State 44 | colors = [None] * n 45 | 46 | # Init start vertex 47 | colors[s] = 1 48 | queue.appendleft(s) 49 | 50 | while queue: 51 | v = queue.pop() 52 | 53 | for u in range(n): 54 | if graph[v][u] == 0: 55 | continue 56 | 57 | if colors[u] is None: 58 | colors[u] = colors[v] * -1 59 | queue.appendleft(u) 60 | elif colors[v] == colors[u]: 61 | return False 62 | 63 | return True 64 | 65 | 66 | if __name__ == "__main__": 67 | graph_list = [ 68 | [1, 4], # 0 69 | [0, 2], # 1 70 | [1, 3], # 2 71 | [2], # 3 72 | [0, 2] # 4 73 | ] 74 | 75 | graph_matrix = [ 76 | # 0 1 2 3 4 77 | [0, 1, 0, 0, 1], 78 | [1, 0, 1, 0, 0], 79 | [0, 1, 0, 1, 0], 80 | [0, 0, 1, 0, 0], 81 | [1, 0, 1, 0, 0], 82 | ] 83 | 84 | list_result = is_bipartite_list(graph_list, 0) 85 | matrix_result = is_bipartite_matrix(graph_matrix, 0) 86 | 87 | assert list_result == matrix_result 88 | print(list_result) 89 | -------------------------------------------------------------------------------- /graphs/labs/lab1/zad2.py: -------------------------------------------------------------------------------- 1 | # Policzyć liczbę spójnych składowych. 2 | 3 | 4 | def count_components(graph): 5 | n = len(graph) 6 | visited = [False] * n 7 | 8 | def dfs_visit(v): 9 | visited[v] = True 10 | 11 | for u in graph[v]: 12 | if not visited[u]: 13 | dfs_visit(u) 14 | 15 | counter = 0 16 | 17 | for v in range(n): 18 | if not visited[v]: 19 | # Every time we backtrack to this loop, we have discovered 20 | # a new component. 21 | counter += 1 22 | dfs_visit(v) 23 | 24 | return counter 25 | 26 | 27 | if __name__ == "__main__": 28 | graph = [ 29 | [1, 4], # 0 30 | [0, 2], # 1 31 | [1, 3], # 2 32 | [2], # 3 33 | [0, 2] # 4 34 | ] 35 | 36 | print(count_components(graph)) 37 | -------------------------------------------------------------------------------- /graphs/labs/lab1/zad3.py: -------------------------------------------------------------------------------- 1 | # Jakaś tam historyjka o sieci komórkowej ... 2 | # Chcemy usuwać wierzchołki z grafu w taki sposób, aby do samego końca był spójny. 3 | 4 | 5 | from collections import deque 6 | 7 | 8 | def get_shutdown_order(network, s): 9 | n = len(network) 10 | queue = deque() 11 | 12 | visited = [False] * n 13 | 14 | # To keep the graph connected, we want to shut down 15 | # stations in order, from the furthest one to the nearest one. 16 | shutdown_order = [] 17 | 18 | visited[s] = True 19 | shutdown_order.append(s) 20 | queue.appendleft(s) 21 | 22 | while queue: 23 | v = queue.pop() 24 | 25 | for u in network[v]: 26 | if not visited[u]: 27 | shutdown_order.append(s) 28 | visited[u] = True 29 | queue.appendleft(s) 30 | 31 | shutdown_order.reverse() 32 | return shutdown_order 33 | -------------------------------------------------------------------------------- /graphs/labs/lab1/zad4.py: -------------------------------------------------------------------------------- 1 | """ 2 | Pomysł: 3 | Dla każdego wierzchołka uruchamiamy DFS. Odwiedzamy n wierzchołków. 4 | Przy n-tym wierzchołku sprawdzamy, czy istnieje połączenie pomiędzy nim a wierzchołkiem 5 | startowym. Jak tak to znaleźliśmy cykl, jak nie to oznaczamy wierzchołek startowy jako odwiedzony 6 | i idziemy do następnego. 7 | 8 | (*) Musimy zadbać o to, żeby wszystkie wierzchołki, które oznaczyliśmy jako odwiedzone 9 | podczas sprawdzania cyklu dla jakiegoś wierzchołka, z powrotem stały się nieodwiedzone. 10 | """ 11 | 12 | 13 | def has_n_cycle(graph, n): 14 | n = len(graph) 15 | visited = [False] * n 16 | 17 | def dfs_visit(v, start, left): 18 | visited[v] = True 19 | 20 | # If we've used enough vertices to 21 | # create a cycle. 22 | if left == 0: 23 | # Check if there is a connection between start and v 24 | if graph[v][start] == 1 and graph[start][v] == 1: 25 | print("B") 26 | return True 27 | else: 28 | # (*) 29 | visited[v] = False 30 | return False 31 | 32 | # Continue as in regular DFS 33 | for u in range(n): 34 | if graph[v][u] == 0: 35 | continue 36 | 37 | if not visited[u]: 38 | if dfs_visit(u, start, left - 1): 39 | return True 40 | 41 | # (*) 42 | visited[v] = False 43 | 44 | for v in range(n): 45 | if not visited[v]: 46 | if dfs_visit(v, v, n - 1): 47 | print("C") 48 | return True 49 | visited[v] = True 50 | 51 | return False 52 | 53 | 54 | if __name__ == "__main__": 55 | graph = [ 56 | [0, 1, 1, 0, 0, 0, 1], 57 | [1, 0, 1, 1, 0, 0, 0], 58 | [1, 1, 0, 1, 0, 0, 0], 59 | [0, 1, 1, 0, 1, 0, 0], 60 | [0, 0, 0, 1, 0, 1, 0], 61 | [0, 0, 0, 0, 1, 0, 1], 62 | [1, 0, 0, 0, 0, 1, 0] 63 | ] 64 | 65 | print(has_n_cycle(graph, 4)) 66 | -------------------------------------------------------------------------------- /graphs/labs/lab1/zad5.py: -------------------------------------------------------------------------------- 1 | def find_universal_brute(graph): 2 | n = len(graph) 3 | zero_counts = [0] * n 4 | one_counts = [0] * n 5 | 6 | for i in range(n): 7 | for j in range(n): 8 | if graph[i][j] == 0: 9 | zero_counts[i] += 1 10 | else: 11 | one_counts[j] += 1 12 | 13 | for i in range(n): 14 | if zero_counts[i] == n and one_counts[i] == n - 1: 15 | return i 16 | 17 | return -1 18 | 19 | 20 | def find_universal(graph): 21 | n = len(graph) 22 | i = 0 23 | j = 0 24 | 25 | while i < n and j < n: 26 | if graph[i][j] == 0: 27 | j += 1 28 | else: 29 | i += 1 30 | 31 | if j == n: 32 | j -= 1 33 | count = 0 34 | 35 | for k in range(n): 36 | if graph[k][i] == 1: 37 | count += 1 38 | 39 | if count == n - 1: 40 | return i 41 | 42 | return -1 43 | 44 | 45 | if __name__ == "__main__": 46 | graph = [ 47 | [0, 1, 0, 1], 48 | [0, 0, 0, 0], 49 | [0, 1, 0, 1], 50 | [1, 1, 1, 0], 51 | ] 52 | 53 | print(find_universal(graph)) 54 | print(find_universal_brute(graph)) 55 | -------------------------------------------------------------------------------- /graphs/labs/lab1/zad6.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | from math import inf 3 | 4 | 5 | # Builds distance array to other vertices for vertex s. 6 | def shortest_path_from(graph, s): 7 | n = len(graph) 8 | queue = deque() 9 | 10 | # State 11 | distance = [inf] * n 12 | visited = [False] * n 13 | parents = [None] * n 14 | 15 | # Process starting vertex 16 | distance[s] = 0 17 | visited[s] = True 18 | queue.appendleft(s) 19 | 20 | while queue: 21 | v = queue.pop() 22 | 23 | for u in graph[v]: 24 | if not visited[u]: 25 | parents[u] = v 26 | visited[u] = True 27 | distance[u] = distance[v] + 1 28 | queue.appendleft(u) 29 | 30 | return parents, distance 31 | 32 | 33 | if __name__ == "__main__": 34 | graph = [ 35 | [1, 6], 36 | [0, 2], 37 | [1, 3], 38 | [2, 4, 6], 39 | [3, 5], 40 | [4, 6], 41 | [5, 0, 3] 42 | ] 43 | 44 | parents, distances = shortest_path_from(graph, 0) 45 | 46 | # Print shortest path to vertex 4 from vertex 0 47 | end = 4 48 | curr = end 49 | 50 | while curr is not None: 51 | print(curr, end=" -> ") 52 | curr = parents[curr] 53 | print("*") 54 | -------------------------------------------------------------------------------- /graphs/labs/lab1/zad7.py: -------------------------------------------------------------------------------- 1 | """ 2 | Takie zadania raczej są pod programowanie dynamiczne. 3 | 4 | W tym przypadku wiemy, że wagi pól są z zakresu {1, ..., 5} przez 5 | co możemy efektywnie wykorzystać algorytm BFS do wyznaczenia optymalnej ścieżki 6 | (symulując wagi krawędzi sztucznymi wierzchołkami). 7 | 8 | Musimy sobie wyobrazić, że mamy taki graf siatkę, gdzie do danego pola 9 | wchodzą krawędzie o jego wadze. 10 | """ 11 | 12 | from collections import deque 13 | 14 | MOVES = [[1, 0], [0, 1]] 15 | 16 | 17 | def in_bounds(y, x, n): 18 | return 0 <= y < n and 0 <= x < n 19 | 20 | 21 | def kings_path(A): 22 | n = len(A) 23 | queue = deque() 24 | 25 | # State 26 | 27 | visited = [[False] * n] * n 28 | 29 | # Process first position 30 | 31 | visited[0][0] = True 32 | queue.appendleft((0, 0, A[0][0])) # (y, x, cost) 33 | A[0][0] = 0 34 | 35 | while queue: 36 | y, x, cost = queue.pop() 37 | 38 | if A[y][x] > 0: 39 | A[y][x] -= 1 40 | queue.appendleft((y, x, cost)) 41 | continue 42 | 43 | # We can stop if we've arrived at index (n-1, n-1). 44 | # Because we've used BFS for board traversal, 45 | # we know we have found the shortest path. 46 | 47 | if y == n - 1 and x == n - 1: 48 | return cost 49 | 50 | # If none of the above conditions were met, 51 | # continue regular DFS. 52 | 53 | for my, mx in MOVES: 54 | ny, nx = y + my, x + mx 55 | 56 | if in_bounds(ny, nx, n): 57 | visited[ny][nx] = True 58 | queue.appendleft((ny, nx, cost + A[ny][nx])) 59 | 60 | 61 | if __name__ == "__main__": 62 | board = [ 63 | [1, 4, 7, 1, 3], 64 | [5, 8, 2, 9, 6], 65 | [1, 3, 4, 7, 2], 66 | [6, 2, 5, 1, 8], 67 | [9, 4, 3, 6, 7], 68 | ] 69 | 70 | print(kings_path(board)) 71 | -------------------------------------------------------------------------------- /graphs/labs/lab2/zad2.py: -------------------------------------------------------------------------------- 1 | from math import inf 2 | 3 | 4 | def find_path(x, y, graph): 5 | n = len(graph) 6 | visited = [False] * n 7 | 8 | def dfs_visit(v, parent): 9 | # If we've found the destination node. 10 | if v == y: 11 | return True 12 | 13 | for u in range(n): 14 | # If edge to neighbour has lower 15 | # weight than parent edge. 16 | if graph[v][u] != 0 and graph[v][u] < parent: 17 | visited[u] = True 18 | if dfs_visit(u, graph[v][u]): 19 | return True 20 | 21 | visited[v] = False 22 | return False 23 | 24 | visited[x] = True 25 | return dfs_visit(x, inf) 26 | 27 | 28 | if __name__ == "__main__": 29 | graph = [ 30 | [0, 2, 0, 0, 0], 31 | [2, 0, 3, 0, 0], 32 | [0, 3, 0, 4, 0], 33 | [0, 0, 4, 0, 5], 34 | [0, 0, 0, 5, 0] 35 | ] 36 | 37 | print(find_path(0, 3, graph)) 38 | -------------------------------------------------------------------------------- /graphs/labs/lab2/zad3.py: -------------------------------------------------------------------------------- 1 | def topological_sort(graph): 2 | n = len(graph) 3 | order = [] 4 | visited = [False] * n 5 | 6 | def dfs_visit(v): 7 | for u in graph[v]: 8 | if not visited[u]: 9 | visited[u] = True 10 | dfs_visit(u) 11 | order.append(v) 12 | 13 | for v in range(n): 14 | if not visited[v]: 15 | visited[v] = True 16 | dfs_visit(v) 17 | 18 | order.reverse() 19 | return order 20 | 21 | 22 | def is_hamiltonian(graph): 23 | n = len(graph) 24 | topological_order = topological_sort(graph) 25 | 26 | for i in range(n - 1): 27 | if not topological_order[i + 1] in graph[topological_order[i]]: 28 | return False 29 | 30 | return True 31 | 32 | 33 | if __name__ == "__main__": 34 | graph = [[1], [2], [3], [4], []] 35 | print(is_hamiltonian(graph)) 36 | -------------------------------------------------------------------------------- /graphs/labs/lab2/zad4.py: -------------------------------------------------------------------------------- 1 | def transpose(graph): 2 | n = len(graph) 3 | new_graph = [[] for _ in range(n)] 4 | 5 | for u in range(n): 6 | for v in graph[u]: 7 | new_graph[v].append(u) 8 | 9 | return new_graph 10 | 11 | 12 | def dfs_visit(u, graph, visited, out): 13 | for v in graph[u]: 14 | if not visited[v]: 15 | visited[v] = True 16 | dfs_visit(v, graph, visited, out) 17 | 18 | out.append(u) 19 | 20 | 21 | def get_finish_order(graph): 22 | n = len(graph) 23 | visited = [False] * n 24 | finish_order = [] 25 | 26 | for u in range(n): 27 | if not visited[u]: 28 | visited[u] = True 29 | dfs_visit(u, graph, visited, finish_order) 30 | 31 | return reversed(finish_order) 32 | 33 | 34 | def extract_components(graph, finish_order): 35 | n = len(graph) 36 | visited = [False] * n 37 | components = [] 38 | 39 | for u in finish_order: 40 | if not visited[u]: 41 | visited[u] = True 42 | components.append([]) 43 | dfs_visit(u, graph, visited, components[-1]) 44 | 45 | return components 46 | 47 | 48 | def scc(graph): 49 | finish_order = get_finish_order(graph) 50 | transposed = transpose(graph) 51 | return extract_components(transposed, finish_order) 52 | -------------------------------------------------------------------------------- /graphs/labs/lab2/zad5.py: -------------------------------------------------------------------------------- 1 | def eulerian_cycle_matrix(G): 2 | n = len(G) 3 | next_edge_to = [0] * n 4 | cycle = [] 5 | 6 | # Verify that eulerian cycle exists. 7 | for i in range(n): 8 | if sum(G[i]) % 2 != 0: 9 | return None 10 | 11 | def dfs_visit(u): 12 | nonlocal next_edge_to, G 13 | 14 | # Process outgoing edges. 15 | while next_edge_to[u] < n: 16 | v = next_edge_to[u] 17 | next_edge_to[u] += 1 18 | 19 | if G[u][v] == 1: 20 | G[u][v], G[v][u] = 0, 0 21 | dfs_visit(v) 22 | 23 | # After processing a vertex, add it to 24 | # the final cycle. 25 | cycle.append(u) 26 | 27 | # Start at a random node. 28 | # We can do that as we are searching 29 | # for an eulerian cycle, not an eulerian path, 30 | # in which case we need to find the start vertex that 31 | # has uneven number of edges. 32 | 33 | dfs_visit(0) 34 | return cycle 35 | 36 | 37 | if __name__ == "__main__": 38 | graph = [ 39 | [0, 1, 1, 0, 0], 40 | [1, 0, 1, 0, 0], 41 | [1, 1, 0, 1, 1], 42 | [0, 0, 1, 0, 1], 43 | [0, 0, 1, 1, 0] 44 | ] 45 | 46 | print(eulerian_cycle_matrix(graph)) 47 | -------------------------------------------------------------------------------- /graphs/labs/lab3/zad1.py: -------------------------------------------------------------------------------- 1 | from queue import PriorityQueue 2 | from math import inf 3 | 4 | 5 | def dijkstra(graph, s): 6 | n = len(graph) 7 | visited = [False] * n 8 | parents = [None] * n 9 | 10 | dist = [inf] * n 11 | dist[s] = 0 12 | 13 | queue = PriorityQueue() 14 | queue.put((0, s)) 15 | 16 | while not queue.empty(): 17 | d, v = queue.get() 18 | 19 | if visited[v]: 20 | continue 21 | 22 | visited[v] = True 23 | 24 | for u, w in graph[v]: 25 | if dist[u] > dist[v] + w: 26 | dist[u] = dist[v] + w 27 | parents[u] = v 28 | queue.put((dist[u], u)) 29 | 30 | return dist, parents 31 | 32 | 33 | if __name__ == "__main__": 34 | graph = [ 35 | [(1, 10), (5, 9999)], 36 | [(0, 10), (2, 10)], 37 | [(1, 10), (3, 10)], 38 | [(2, 10), (4, 10)], 39 | [(3, 10), (5, 9999)], 40 | [(0, 9999), (4, 9999)] 41 | ] 42 | 43 | print(dijkstra(graph, 0)) 44 | -------------------------------------------------------------------------------- /graphs/labs/lab3/zad2.py: -------------------------------------------------------------------------------- 1 | """ 2 | sortujemy topologicznie + relaksacja po kolei w kolejnosci posortowanych wierzcholkow, co gwarantuje 3 | najkrotsza sciezke 4 | 5 | jeżeli s nie jest pierwszym wierzcholkiem sortowaniu topologicznym to ignorujemy wierzchołki przed nim. 6 | """ 7 | 8 | from math import inf 9 | 10 | 11 | def topological_sort(graph): 12 | n = len(graph) 13 | visited = [False] * n 14 | order = [] 15 | 16 | def dfs_visit(v): 17 | for u, _ in graph[v]: 18 | if not visited[u]: 19 | visited[u] = True 20 | dfs_visit(u) 21 | order.append(v) 22 | 23 | for v in range(n): 24 | if not visited[v]: 25 | visited[v] = True 26 | dfs_visit(v) 27 | 28 | order.reverse() 29 | return order 30 | 31 | 32 | def shortest_dag(graph, s): 33 | n = len(graph) 34 | order = topological_sort(graph) 35 | parents = [None] * n 36 | dist = [inf] * n 37 | dist[s] = 0 38 | 39 | def relax(u, v, w): 40 | if dist[u] > dist[v] + w: 41 | dist[u] = dist[v] + w 42 | parents[u] = v 43 | 44 | for u in order: 45 | for v, w in graph[u]: 46 | relax(u, v, w) 47 | 48 | return dist, parents 49 | -------------------------------------------------------------------------------- /graphs/labs/lab3/zad3.py: -------------------------------------------------------------------------------- 1 | """ 2 | Sortujemy krawedzie po wagach 3 | 4 | potem przechodzimy liniowo. Jak jest krawedz miedzy nieodwiedzonymi wierzcholkami to ja odrzucamy 5 | bo na pewno jej nie uzyjemy (bo musielibysmy odwiedzic wczesniej) 6 | 7 | dystans dla parzystych i nieparzystych odwiedzen, 8 | mnożenie wierzchołków 9 | """ 10 | from math import inf 11 | 12 | 13 | def extract_edges(graph): 14 | n = len(graph) 15 | edges = [] 16 | 17 | for u in range(n): 18 | for v, w in graph[u]: 19 | edges.append((u, v, w)) 20 | 21 | return edges 22 | 23 | 24 | def shortest_desc(graph, x, y): 25 | n = len(graph) 26 | 27 | visited = [False] * n 28 | visited[x] = True 29 | 30 | parents = [None] * n 31 | dist = [inf] * n 32 | 33 | edges = extract_edges(graph) 34 | edges.sort(key=lambda e: e[2], reverse=True) 35 | 36 | for u, v, w in edges: 37 | if not visited[u]: 38 | continue 39 | 40 | visited[v] = True 41 | 42 | if dist[v] > dist[u] + w: 43 | dist[v] = dist[u] + w 44 | parents[v] = u 45 | 46 | # Check if we have found any path. 47 | 48 | if dist[y] == inf: 49 | return None 50 | 51 | # Reconstruct the path. 52 | 53 | path = [] 54 | v = y 55 | 56 | while v: 57 | path.append(v) 58 | v = parents[v] 59 | 60 | return dist[y], path 61 | 62 | 63 | if __name__ == "__main__": 64 | graph = [] 65 | -------------------------------------------------------------------------------- /graphs/labs/lab3/zad4.py: -------------------------------------------------------------------------------- 1 | """ 2 | Korzystamy ze wzoru log(a * b) = log(a) + log(b) 3 | """ 4 | from queue import PriorityQueue 5 | from math import inf, log 6 | 7 | 8 | def dijkstra(graph, s): 9 | n = len(graph) 10 | 11 | parents = [None] * n 12 | visited = [False] * n 13 | dist = [inf] * n 14 | dist[s] = 1 15 | 16 | queue = PriorityQueue() 17 | queue.put((dist[s], s)) 18 | 19 | while not queue.empty(): 20 | _, v = queue.get() 21 | 22 | if visited[v]: 23 | continue 24 | 25 | visited[v] = True 26 | 27 | for u, w in graph[v]: 28 | # We use the fact that log(ab) = log(a) + log(b) 29 | # so adding logarithms of weights together, finds 30 | # the smallest product of weights (log is ascending). 31 | if dist[u] > dist[v] + log(w): 32 | dist[u] = dist[v] + log(w) 33 | parents[u] = v 34 | 35 | return dist, parents 36 | 37 | 38 | if __name__ == "__main__": 39 | graph = [] 40 | -------------------------------------------------------------------------------- /graphs/labs/lab3/zad5.py: -------------------------------------------------------------------------------- 1 | from math import inf 2 | from queue import PriorityQueue 3 | 4 | ALICE = 0 5 | BOB = 1 6 | 7 | 8 | def alice_and_bob(graph, s): 9 | n = len(graph) 10 | 11 | parents = [[None, None] for _ in range(n)] 12 | visited = [[False, False] for _ in range(n)] 13 | 14 | dist = [[inf, inf] for _ in range(n)] 15 | dist[s][ALICE] = dist[s][BOB] = 0 16 | 17 | queue = PriorityQueue() 18 | queue.put((dist[s][ALICE], s, ALICE)) 19 | queue.put((dist[s][BOB], s, BOB)) 20 | 21 | while not queue.empty(): 22 | _, v, prev_driver = queue.get() 23 | 24 | if visited[v][prev_driver]: 25 | continue 26 | 27 | visited[v][prev_driver] = True 28 | 29 | for u, w in graph[v]: 30 | next_driver = (prev_driver + 1) % 2 31 | 32 | if visited[u][next_driver]: 33 | continue 34 | 35 | w = 0 if next_driver == BOB else w 36 | cost = dist[v][prev_driver] + w 37 | 38 | if dist[u][next_driver] > cost: 39 | dist[u][next_driver] = cost 40 | parents[u][next_driver] = v 41 | queue.put((dist[u][next_driver], u, next_driver)) 42 | 43 | return dist, parents 44 | 45 | 46 | if __name__ == "__main__": 47 | drivers = ['alice', 'bob'] 48 | 49 | for graph in [ 50 | [ 51 | [(1, 5), (3, 1)], 52 | [(2, 4)], 53 | [(3, 8)], 54 | [(4, 99), (0, 1)], 55 | [], 56 | ], 57 | [ 58 | [(1, 5), (3, 99)], 59 | [(2, 4), (3, 1)], 60 | [(3, 8)], 61 | [(4, 99), (0, 1)], 62 | [], 63 | ] 64 | ]: 65 | dist, parents = alice_and_bob(graph, 0) 66 | 67 | curr = 4 68 | driver = ALICE if dist[curr][ALICE] < dist[curr][BOB] else BOB 69 | 70 | while curr is not None: 71 | print(f"{drivers[driver]} visits {curr}; distance by alice {dist[curr][driver]}") 72 | curr = parents[curr][driver] 73 | driver = (driver + 1) % 2 74 | 75 | print(min(dist[4])) 76 | -------------------------------------------------------------------------------- /graphs/labs/lab3/zad6.py: -------------------------------------------------------------------------------- 1 | from queue import PriorityQueue 2 | from math import inf 3 | 4 | 5 | def lowest_fuel_price(graph, s, prices, D): 6 | n = len(graph) 7 | 8 | visited = [[False for _ in range(D + 1)] for v in range(n)] 9 | 10 | costs = [[inf] * (D + 1) for _ in range(n)] 11 | costs[s] = [0] * (D + 1) 12 | 13 | queue = PriorityQueue() 14 | queue.put((0, s, D)) 15 | 16 | while not queue.empty(): 17 | _, v, fuel = queue.get() 18 | 19 | if visited[v][fuel]: 20 | continue 21 | 22 | visited[v][fuel] = True 23 | 24 | for u, w in graph[v]: 25 | # Dolewamy od 0 do x litrów paliwa, tak 26 | # żeby fuel + x = D. 27 | 28 | for fill in range(D - fuel + 1): 29 | new_fuel = fuel + fill 30 | 31 | # Sprawdzamy, czy na pewno mamy wystarczająco 32 | # paliwa, żeby dojechać do kolejnego miasta. 33 | if new_fuel < w: 34 | continue 35 | 36 | price = fill * prices[v] 37 | cost = costs[v][fuel] + price 38 | 39 | # Odejmujemy od nowego poziomu paliwa 40 | # ilość paliwa, która zostanie zużyta 41 | # na dojechanie do kolejnego miasta. 42 | new_fuel -= w 43 | 44 | # Relax 45 | if costs[u][new_fuel] > cost: 46 | costs[u][new_fuel] = cost 47 | queue.put((cost, u, new_fuel)) 48 | 49 | return costs 50 | 51 | 52 | if __name__ == "__main__": 53 | graph = [ 54 | [(1, 4), (2, 2)], # Miasto 0 55 | [(0, 4), (2, 1), (3, 5)], # Miasto 1 56 | [(0, 2), (1, 1), (3, 8), (4, 10)], # Miasto 2 57 | [(1, 5), (2, 8), (4, 2)], # Miasto 3 58 | [(2, 10), (3, 2)] # Miasto 4 59 | ] 60 | 61 | prices = [5, 2, 4, 7, 3] 62 | D = 10 63 | 64 | print(lowest_fuel_price(graph, 0, prices, D)) 65 | -------------------------------------------------------------------------------- /graphs/labs/lab4/zad1.py: -------------------------------------------------------------------------------- 1 | """ 2 | Note that we are working with logarithms. 3 | Summing them works like multiplication. 4 | 5 | -loga > 0 for a < 0 6 | -loga < 0 for a > 0 7 | 8 | After replacing weights with those values, 9 | we can search for a negative weight cycle 10 | using standard Bellman-Ford algorithm. 11 | """ 12 | 13 | from math import inf, log 14 | 15 | 16 | def currency_glitch(graph): 17 | n = len(graph) 18 | 19 | # Map weights to -log(w) and add dummy node with 20 | # directed edges to all the other vertices of weight 1. 21 | 22 | for u in range(n): 23 | graph[u].append(0) 24 | for v in range(n): 25 | graph[u][v] = -log(graph[u][v]) 26 | 27 | n += 1 28 | graph.append([0] * n) 29 | 30 | # Work 31 | 32 | dist = [inf] * n 33 | dist[n - 1] = 0 34 | 35 | for _ in range(n - 1): 36 | for u in range(n): 37 | for v in range(n): 38 | alt = dist[u] + graph[u][v] 39 | 40 | if dist[v] > alt: 41 | dist[v] = alt 42 | 43 | # Validate 44 | 45 | for u in range(n): 46 | for v in range(n): 47 | if dist[v] > dist[u] + graph[u][v]: 48 | # Yes, there exists such currency, as we've found 49 | # negative weight cycle. 50 | return True 51 | 52 | # No, there's no such currency. 53 | return False 54 | 55 | 56 | if __name__ == "__main__": 57 | exchange_rates = [ 58 | [1.0, 0.9, 1.1, 1.25, 0.8, 0.95], 59 | [1.1111, 1.0, 1.2222, 1.3889, 0.8889, 1.0556], 60 | [0.9091, 0.8182, 1.0, 1.1364, 0.7273, 0.8636], 61 | [0.8, 0.72, 0.88, 1.0, 0.64, 0.76], 62 | [1.25, 1.125, 1.375, 1.5625, 1.0, 1.1875], 63 | [1.0526, 0.9474, 1.1579, 1.3158, 0.8421, 1.0] 64 | ] 65 | 66 | print(currency_glitch(exchange_rates)) 67 | -------------------------------------------------------------------------------- /graphs/labs/lab4/zad2.py: -------------------------------------------------------------------------------- 1 | """ 2 | Stworzenie domknięcia przechodniego (transitive closure) grafu. 3 | """ 4 | 5 | 6 | def transitive_closure(graph): 7 | n = len(graph) 8 | 9 | result = [[0] * n for _ in range(n)] 10 | 11 | for u in range(n): 12 | for v in range(n): 13 | if graph[u][v]: 14 | result[u][v] = 1 15 | 16 | for k in range(n): 17 | # k is the end vertex in set of intermediate vertices. 18 | # So At every iteration V = { 1, ..., k - 1 } is a set 19 | # of vertices that were examined to construct paths 20 | # between every two vertices. 21 | 22 | for u in range(n): 23 | for v in range(n): 24 | if u == v: 25 | continue 26 | 27 | # Here, we check if it's possible to reach 28 | # v from u using k as an intermediate vertex. 29 | if result[u][k] == 1 and result[k][v] == 1: 30 | result[u][v] = 1 31 | 32 | return result 33 | 34 | 35 | if __name__ == "__main__": 36 | graph = [ 37 | [0, 1, 0, 0, 1, 0], # edges: 0-1, 0-4 38 | [1, 0, 1, 0, 1, 0], # edges: 1-0, 1-2, 1-4 39 | [0, 1, 0, 1, 0, 0], # edges: 2-1, 2-3 40 | [0, 0, 1, 0, 0, 1], # edges: 3-2, 3-5 41 | [1, 1, 0, 0, 0, 0], # edges: 4-0, 4-1 42 | [0, 0, 0, 1, 0, 0], # edge: 5-3 43 | ] 44 | 45 | print(*transitive_closure(graph), sep="\n") 46 | -------------------------------------------------------------------------------- /graphs/labs/lab4/zad3.py: -------------------------------------------------------------------------------- 1 | """ 2 | Floyd Warshal, Dijkstra 3 | 4 | Znajdywanie cyklu o najmniejszej wadze. 5 | 6 | Ustawiamy odległości wierzchołków samych do siebie na inf. 7 | Kiedy u == v, będziemy porównywać inf ze ścieżką idącą z u do u przez k, czyli cyklem. 8 | """ 9 | 10 | from math import inf 11 | 12 | 13 | def find_min_cycle(graph): 14 | n = len(graph) 15 | 16 | dist = [[inf] * n for _ in range(n)] 17 | 18 | for u in range(n): 19 | # We can set distance to a node itself 20 | # to infinity so that we'll count the 21 | # shortest cycle. 22 | dist[u][u] = inf 23 | 24 | for v, w in graph[u]: 25 | dist[u][v] = w 26 | 27 | for k in range(n): 28 | for u in range(n): 29 | for v in range(n): 30 | # If u == v, in standard Floyd-Warshall nothing would happen, 31 | # but if dist[u][v] = inf, we'll replace the distance 32 | # with the shortest cycle. 33 | 34 | if dist[u][v] > dist[u][k] + dist[k][v]: 35 | dist[u][v] = dist[u][k] + dist[k][v] 36 | 37 | min_cycle = inf 38 | 39 | for u in range(n): 40 | min_cycle = min(dist[u][u], min_cycle) 41 | 42 | return min_cycle 43 | 44 | 45 | if __name__ == "__main__": 46 | graph = [ 47 | [(1, 2)], # Node 0 → Node 1 (weight 2) 48 | [(2, 3)], # Node 1 → Node 2 (weight 3) 49 | [(3, 4)], # Node 2 → Node 3 (weight 4) 50 | [(1, 1), (4, 5)], # Node 3 → Node 1 (weight 1), Node 4 (weight 5) 51 | [] # Node 4 → No outgoing edges 52 | ] 53 | 54 | print(find_min_cycle(graph)) 55 | -------------------------------------------------------------------------------- /graphs/problems/airports.py: -------------------------------------------------------------------------------- 1 | from queue import PriorityQueue 2 | from math import inf 3 | 4 | 5 | def airports(G, A, s, t): 6 | n = len(G) 7 | G.append([]) 8 | 9 | # Create "air" vertex, that connects airports. 10 | for u in range(n): 11 | G[u].append((n, A[u])) 12 | G[n].append((u, A[u])) 13 | 14 | n += 1 15 | dist = [inf] * n 16 | queue = PriorityQueue() 17 | 18 | dist[s] = 0 19 | queue.put((dist[s], s)) 20 | 21 | while not queue.empty(): 22 | d, u = queue.get() 23 | 24 | if d > dist[u]: 25 | continue 26 | 27 | for v, w in G[u]: 28 | alternative = d + w 29 | 30 | if dist[v] > alternative: 31 | dist[v] = alternative 32 | queue.put((alternative, v)) 33 | 34 | return dist[t] 35 | -------------------------------------------------------------------------------- /graphs/problems/armstrong.py: -------------------------------------------------------------------------------- 1 | """ 2 | Kiedy przetwarzamy rower, interesuje nas odległość od s do roweru 3 | i odległość z roweru do t, czyli odległość z t do roweru. 4 | """ 5 | from queue import PriorityQueue 6 | from math import inf, floor 7 | 8 | 9 | def build_graph(edges): 10 | n = max(map(lambda x: max(x[0], x[1]), edges)) + 1 11 | graph = [[] for _ in range(n)] 12 | 13 | for u, v, w in edges: 14 | graph[u].append((v, w)) 15 | graph[v].append((u, w)) 16 | 17 | return graph 18 | 19 | 20 | def dijkstra(graph, s): 21 | n = len(graph) 22 | 23 | dist = [inf] * n 24 | queue = PriorityQueue() 25 | 26 | dist[s] = 0 27 | queue.put((0, s)) 28 | 29 | while not queue.empty(): 30 | d, u = queue.get() 31 | 32 | if d > dist[u]: 33 | continue 34 | 35 | for v, w in graph[u]: 36 | alt = dist[u] + w 37 | if dist[v] > alt: 38 | dist[v] = alt 39 | queue.put((alt, v)) 40 | 41 | return dist 42 | 43 | 44 | def armstrong(B, G, s, t): 45 | graph = build_graph(G) 46 | 47 | dist_s_t = dijkstra(graph, s) 48 | dist_t_s = dijkstra(graph, t) 49 | 50 | result = dist_s_t[t] 51 | 52 | for u, p, q in B: 53 | result = min(result, dist_s_t[u] + dist_t_s[u] * (p / q)) 54 | 55 | return floor(result) 56 | -------------------------------------------------------------------------------- /graphs/problems/beautree.py: -------------------------------------------------------------------------------- 1 | """ 2 | Sorting O(ElogE) <= O(ElogV^2) = O(ElogV) 3 | Building MSTs O(VElog*E) 4 | Total: O(EVlog*E) 5 | """ 6 | 7 | from math import inf, isinf 8 | 9 | 10 | class Set: 11 | def __init__(self, v): 12 | self.v = v 13 | self.parent = self 14 | self.rank = 0 15 | 16 | 17 | def find(x): 18 | if x is not x.parent: 19 | # Using "Path Compression" heuristic. 20 | x.parent = find(x.parent) 21 | return x.parent 22 | 23 | 24 | def union(x, y): 25 | x = find(x) 26 | y = find(y) 27 | 28 | if x == y: 29 | return 30 | 31 | # Using "Rank" heuristic. 32 | if x.rank > y.rank: 33 | y.parent = x 34 | else: 35 | x.parent = y 36 | 37 | if x.rank == y.rank: 38 | y.rank += 1 39 | 40 | 41 | # O(Elog*E) ~ O(E), because of "Path Compression" and "Rank" heuristics. 42 | def kruskal(edges, n, i, j): 43 | sets = [Set(v) for v in range(n)] 44 | 45 | weight = 0 46 | mst = 0 47 | 48 | for e in range(i, j): 49 | u, v, w = edges[e] 50 | u = sets[u] 51 | v = sets[v] 52 | 53 | if find(u) != find(v): 54 | union(u, v) 55 | weight += w 56 | mst += 1 57 | 58 | return mst == n - 1, weight 59 | 60 | 61 | def beautree(graph): 62 | n = len(graph) 63 | print(n) 64 | edges = [] 65 | 66 | for u in range(n): 67 | for v, w in graph[u]: 68 | if v > u: 69 | edges.append((u, v, w)) 70 | 71 | # Sorting takes O(ElogE) 72 | edges.sort(key=lambda x: x[2]) 73 | 74 | m = len(edges) 75 | min_weight = inf 76 | 77 | # Here, we estimate the number of iterations by O(E) 78 | for i in range(m - n + 1): 79 | j = i + n - 1 80 | is_mst, weight = kruskal(edges, n, i, j) 81 | 82 | if is_mst: 83 | min_weight = min(min_weight, weight) 84 | 85 | return min_weight if not isinf(min_weight) else None 86 | -------------------------------------------------------------------------------- /graphs/problems/beautree_v2.py: -------------------------------------------------------------------------------- 1 | """ 2 | Total: (V(V + E)) = O(V^2 + EV) ~ O(2EV) = O(VE) 3 | """ 4 | 5 | from collections import deque 6 | from math import inf, isinf 7 | 8 | 9 | def build_graph(edges, n, i, j): 10 | graph = [[] for _ in range(n)] 11 | total_w = 0 12 | 13 | for e in range(i, j): 14 | u, v, w = edges[e] 15 | total_w += w 16 | graph[u].append((v, w)) 17 | graph[v].append((u, w)) 18 | 19 | return graph, total_w 20 | 21 | 22 | def extract_edges(graph): 23 | n = len(graph) 24 | edges = [] 25 | 26 | for u in range(n): 27 | for v, w in graph[u]: 28 | if u < v: 29 | edges.append((u, v, w)) 30 | 31 | return edges, len(edges) 32 | 33 | 34 | def is_connected(edges, n, i, j): 35 | graph, w = build_graph(edges, n, i, j) 36 | 37 | visited = [False] * n 38 | visited_count = 1 39 | queue = deque() 40 | 41 | visited[0] = True 42 | queue.append(0) 43 | 44 | while queue: 45 | u = queue.popleft() 46 | 47 | for v, _ in graph[u]: 48 | if not visited[v]: 49 | visited_count += 1 50 | visited[v] = True 51 | queue.append(v) 52 | 53 | return visited_count == n, w 54 | 55 | 56 | def beautree(G): 57 | n = len(G) 58 | 59 | edges, m = extract_edges(G) 60 | edges.sort(key=lambda x: x[2]) 61 | 62 | min_w = inf 63 | 64 | for i in range(m - n + 1): 65 | j = i + n - 1 66 | ok, w = is_connected(edges, n, i, j) 67 | 68 | if ok and w < min_w: 69 | min_w = w 70 | 71 | return min_w if not isinf(min_w) else None 72 | -------------------------------------------------------------------------------- /graphs/problems/binworker.py: -------------------------------------------------------------------------------- 1 | def binworker(M): 2 | # no. workers 3 | n = len(M) 4 | 5 | # no. machines 6 | m = max(map(max, M)) + 1 7 | 8 | machine_worker = [None] * m 9 | 10 | def bpm(worker, visited): 11 | for machine in M[worker]: 12 | if not visited[machine]: 13 | visited[machine] = True 14 | 15 | if machine_worker[machine] is None or bpm(machine_worker[machine], visited): 16 | machine_worker[machine] = worker 17 | return True 18 | 19 | return False 20 | 21 | result = 0 22 | 23 | for worker in range(n): 24 | visited = [False] * m 25 | 26 | if bpm(worker, visited): 27 | result += 1 28 | 29 | return result 30 | -------------------------------------------------------------------------------- /graphs/problems/flight.py: -------------------------------------------------------------------------------- 1 | def build_graph(edges): 2 | n = max(edges, key=lambda e: max(e[0], e[1])) 3 | n = max(n[0], n[1]) + 1 4 | 5 | graph = [[] for _ in range(n)] 6 | 7 | for u, v, p in edges: 8 | graph[u].append((v, p)) 9 | graph[v].append((u, p)) 10 | 11 | return graph 12 | 13 | 14 | def flight(L, x, y, t): 15 | graph = build_graph(L) 16 | n = len(graph) 17 | 18 | def dfs_visit(u, min_p, max_p, visited): 19 | if u == y: 20 | return 21 | 22 | for v, p in graph[u]: 23 | if not visited[v] and p - t <= max_p and p + t >= min_p: 24 | visited[v] = True 25 | dfs_visit(v, max(min_p, p - t), min(max_p, p + t), visited) 26 | 27 | for u, p in graph[x]: 28 | visited = [False] * n 29 | visited[x] = visited[u] = True 30 | 31 | dfs_visit(u, p - t, p + t, visited) 32 | 33 | if visited[y]: 34 | return True 35 | 36 | return False 37 | -------------------------------------------------------------------------------- /graphs/problems/gold.py: -------------------------------------------------------------------------------- 1 | from queue import PriorityQueue 2 | from math import inf 3 | 4 | 5 | def dijkstra(graph, s, converter=lambda x: x): 6 | n = len(graph) 7 | 8 | dist = [inf] * n 9 | parents = [0] * n 10 | queue = PriorityQueue() 11 | 12 | dist[s] = 0 13 | parents[s] = 0 14 | queue.put((dist[s], s)) 15 | 16 | while not queue.empty(): 17 | d, u = queue.get() 18 | 19 | if d > dist[u]: 20 | continue 21 | 22 | for v, w in graph[u]: 23 | alt = d + converter(w) 24 | 25 | if dist[v] > alt: 26 | dist[v] = alt 27 | parents[v] = parents[u] + 1 28 | queue.put((alt, v)) 29 | 30 | return dist, parents 31 | 32 | 33 | def gold(G, V, s, t, r): 34 | n = len(G) 35 | 36 | # Distance to vertices before robbing any castle. 37 | dist_s, parents_s = dijkstra(G, s) 38 | 39 | # Distance to vertices after robbing a castle. 40 | dist_t, parents_t = dijkstra(G, t, converter=lambda x: 2 * x + r) 41 | 42 | min_dist = dist_s[t] 43 | 44 | for u in range(n): 45 | dist = dist_s[u] + dist_t[u] - V[u] 46 | min_dist = min(min_dist, dist) 47 | 48 | return min_dist 49 | -------------------------------------------------------------------------------- /graphs/problems/has_cycle.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | 3 | 4 | def has_cycle(graph): 5 | n = len(graph) 6 | 7 | visited = [False for _ in range(n)] 8 | visited[0] = True 9 | 10 | queue = deque() 11 | queue.appendleft(0) 12 | 13 | while queue: 14 | v = queue.pop() 15 | 16 | for u in graph[v]: 17 | # If at this point we tried to go to a node 18 | # which was already visited, there must be a cycle. 19 | if visited[u]: 20 | return True 21 | 22 | visited[u] = True 23 | queue.appendleft(u) 24 | 25 | return False 26 | 27 | 28 | if __name__ == "__main__": 29 | graph = [ 30 | [1, 4], 31 | [2], 32 | [3], 33 | [], 34 | [2] 35 | ] 36 | 37 | print(has_cycle(graph)) 38 | -------------------------------------------------------------------------------- /graphs/problems/is_bipartite.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | 3 | 4 | def is_bipartite(graph): 5 | n = len(graph) 6 | queue = deque() 7 | colors = [-1] * n 8 | 9 | for v in range(n): 10 | # If vertex was already visited. 11 | if colors[v] != -1: 12 | continue 13 | 14 | # Put starting vertex of the new component 15 | # to the queue. 16 | colors[v] = 0 17 | queue.appendleft(v) 18 | 19 | while queue: 20 | u = queue.pop() 21 | 22 | for w in graph[u]: 23 | # The vertex doesn't have a color yet. 24 | if colors[w] == -1: 25 | colors[w] = 1 - colors[u] 26 | queue.append(w) 27 | # Adjacent vertex has the same color, 28 | # so our graph is not bipartite. 29 | elif colors[w] == colors[u]: 30 | return False 31 | 32 | # Adjacent vertex was already visited, 33 | # but it has different color, so it's fine. 34 | 35 | return True 36 | 37 | 38 | if __name__ == "__main__": 39 | graph = [ 40 | [1, 4], 41 | [0, 2], 42 | [1, 3], 43 | [2], 44 | [0, 2] 45 | ] 46 | 47 | print(is_bipartite(graph)) 48 | -------------------------------------------------------------------------------- /graphs/problems/is_connected.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | 3 | 4 | def is_connected(graph): 5 | n = len(graph) 6 | 7 | queue = deque() 8 | visited = [False] * n 9 | 10 | # Process first node (arbitrary one) 11 | visits = 1 12 | visited[0] = True 13 | queue.append(0) 14 | 15 | while queue: 16 | v = queue.popleft() 17 | 18 | for u in graph[v]: 19 | if not visited[u]: 20 | visits += 1 21 | visited[u] = True 22 | queue.append(u) 23 | 24 | # If we have visited every node in the graph 25 | # number of visits should be equal to number of nodes. 26 | return visits == n 27 | 28 | 29 | if __name__ == "__main__": 30 | graph = [ 31 | [], 32 | [], 33 | [] 34 | ] 35 | 36 | connected = is_connected(graph) 37 | assert not connected 38 | 39 | print(graph, connected) 40 | 41 | graph = [ 42 | [1], 43 | [2], 44 | [0] 45 | ] 46 | 47 | connected = is_connected(graph) 48 | assert connected 49 | 50 | print(graph, connected) 51 | -------------------------------------------------------------------------------- /graphs/problems/is_eulerian.py: -------------------------------------------------------------------------------- 1 | # In this implementation we check if all NON-ZERO 2 | # degree vertices are connected with each other. 3 | def is_connected(graph): 4 | n = len(graph) 5 | visited = [False] * n 6 | 7 | def dfs_visit(v): 8 | visited[v] = True 9 | 10 | for u in graph[v]: 11 | if not visited[u]: 12 | dfs_visit(u) 13 | 14 | dfs_visit(0) 15 | 16 | for v in range(n): 17 | # If vertex wasn't visited, and it 18 | # is not isolated. 19 | if not visited[v] and len(graph[v]): 20 | return False 21 | 22 | return True 23 | 24 | 25 | def is_eulerian_connected(graph): 26 | n = len(graph) 27 | 28 | # If graph is not connected. 29 | if not is_connected(graph): 30 | return False 31 | 32 | for v in range(n): 33 | if len(graph[v]) % 2 == 1: 34 | return False 35 | 36 | return True 37 | 38 | 39 | if __name__ == "__main__": 40 | graph = [ 41 | [], 42 | [], 43 | [] 44 | ] 45 | -------------------------------------------------------------------------------- /graphs/problems/jumper.py: -------------------------------------------------------------------------------- 1 | from math import inf 2 | from queue import PriorityQueue 3 | 4 | 5 | def jumper(G, s, w): 6 | print(*G, sep='\n') 7 | 8 | n = len(G) 9 | 10 | dist = [[inf] * 2 for _ in range(n)] 11 | queue = PriorityQueue() 12 | 13 | dist[s][False] = 0 14 | queue.put((dist[s][False], s, False)) 15 | 16 | while not queue.empty(): 17 | d, u, used_boots = queue.get() 18 | 19 | # Don't process already visited 20 | # vertices twice. 21 | if d > dist[u][used_boots]: 22 | continue 23 | 24 | for v in range(n): 25 | if G[u][v] == 0: 26 | continue 27 | 28 | new_dist = d + G[u][v] 29 | 30 | if dist[v][False] > new_dist: 31 | dist[v][False] = new_dist 32 | queue.put((dist[v][False], v, False)) 33 | 34 | # If we've used boots to reach u, 35 | # we can't use them again. 36 | if used_boots: 37 | continue 38 | 39 | for k in range(n): 40 | if G[v][k] == 0: 41 | continue 42 | 43 | vk = max(G[u][v], G[v][k]) 44 | new_dist = d + vk 45 | 46 | if dist[k][True] > new_dist: 47 | dist[k][True] = new_dist 48 | queue.put((dist[k][True], k, True)) 49 | 50 | return min(dist[w]) 51 | -------------------------------------------------------------------------------- /graphs/problems/keep_distance.py: -------------------------------------------------------------------------------- 1 | from zad1testy import runtests 2 | from collections import deque 3 | from math import inf 4 | 5 | 6 | def floyd_warshall(graph): 7 | n = len(graph) 8 | dist = [[inf] * n for _ in range(n)] 9 | 10 | for u in range(n): 11 | dist[u][u] = 0 12 | 13 | for v in range(n): 14 | if graph[u][v] == 0: 15 | continue 16 | 17 | dist[u][v] = graph[u][v] 18 | 19 | for k in range(n): 20 | for u in range(n): 21 | for v in range(n): 22 | alt = dist[u][k] + dist[k][v] 23 | if dist[u][v] > alt: 24 | dist[u][v] = alt 25 | 26 | return dist 27 | 28 | 29 | def keep_distance(M, x, y, d): 30 | n = len(M) 31 | dist = floyd_warshall(M) 32 | 33 | if dist[x][y] < d: 34 | return None 35 | 36 | visited = [[False] * n for _ in range(n)] 37 | queue = deque() 38 | queue.append((x, y, [(x, y)])) 39 | visited[x][y] = True 40 | 41 | while queue: 42 | u, v, path = queue.popleft() 43 | 44 | if (u, v) == (y, x): 45 | return path 46 | 47 | # Get all possible next moves (including "wait") 48 | next_u_list = [u] + [i for i in range(n) if M[u][i] > 0] 49 | next_v_list = [v] + [j for j in range(n) if M[v][j] > 0] 50 | 51 | for next_u in next_u_list: 52 | for next_v in next_v_list: 53 | # Nie mogą się minąć na tej samej krawędzi w przeciwnych kierunkach 54 | if next_u == v and next_v == u: 55 | continue 56 | 57 | # Muszą być w odpowiedniej odległości 58 | if dist[next_u][next_v] < d: 59 | continue 60 | 61 | if not visited[next_u][next_v]: 62 | visited[next_u][next_v] = True 63 | queue.append((next_u, next_v, path + [(next_u, next_v)])) 64 | 65 | return None 66 | 67 | 68 | runtests(keep_distance) 69 | -------------------------------------------------------------------------------- /graphs/problems/koleje.py: -------------------------------------------------------------------------------- 1 | from math import inf 2 | 3 | 4 | def articulation_points(graph): 5 | n = len(graph) 6 | 7 | time = 0 8 | visited = [False] * n 9 | low = [inf] * n 10 | discovery = [inf] * n 11 | points = set() 12 | 13 | def dfs_visit(u, parent=None): 14 | nonlocal time 15 | 16 | time += 1 17 | visited[u] = True 18 | low[u] = discovery[u] = time 19 | children = 0 20 | 21 | for v in graph[u]: 22 | if not visited[v]: 23 | children += 1 24 | dfs_visit(v, u) 25 | low[u] = min(low[u], low[v]) 26 | 27 | if parent is not None and low[v] >= discovery[u]: 28 | points.add(u) 29 | elif parent != v: 30 | low[u] = min(low[u], discovery[v]) 31 | 32 | if parent is None and children >= 2: 33 | points.add(u) 34 | 35 | return children 36 | 37 | for u in range(n): 38 | if not visited[u]: 39 | dfs_visit(u) 40 | 41 | return points 42 | 43 | 44 | def koleje(B): 45 | n = max(map(lambda x: max(x[0], x[1]), B)) + 1 46 | graph = [[] for _ in range(n)] 47 | 48 | for u, v in B: 49 | graph[u].append(v) 50 | graph[v].append(u) 51 | 52 | return len(articulation_points(graph)) 53 | -------------------------------------------------------------------------------- /graphs/problems/lufthansa.py: -------------------------------------------------------------------------------- 1 | class Node: 2 | def __init__(self, v): 3 | self.v = v 4 | self.parent = self 5 | self.rank = 0 6 | 7 | 8 | def find(x): 9 | if x is not x.parent: 10 | x.parent = find(x.parent) 11 | return x.parent 12 | 13 | 14 | def union(x, y): 15 | x = find(x) 16 | y = find(y) 17 | 18 | if x == y: 19 | return 20 | 21 | if x.rank < y.rank: 22 | x.parent = y 23 | else: 24 | y.parent = x 25 | 26 | if x.rank == y.rank: 27 | x.rank += 1 28 | 29 | 30 | def extract_edges(G): 31 | n = len(G) 32 | edges = [] 33 | total_weight = 0 34 | 35 | for u in range(n): 36 | for v, w in G[u]: 37 | if v > u: 38 | total_weight += w 39 | edges.append((w, u, v)) 40 | 41 | return edges, total_weight 42 | 43 | 44 | def lufthansa(G): 45 | n = len(G) 46 | 47 | edges, total_weight = extract_edges(G) 48 | edges.sort(reverse=True) 49 | 50 | sets = [Node(v) for v in range(n)] 51 | 52 | mst_weight = 0 53 | has_exception = False 54 | 55 | for w, u, v in edges: 56 | u_set = sets[u] 57 | v_set = sets[v] 58 | 59 | if find(u_set) != find(v_set): 60 | union(u_set, v_set) 61 | mst_weight += w 62 | elif not has_exception: 63 | has_exception = True 64 | mst_weight += w 65 | 66 | return total_weight - mst_weight 67 | -------------------------------------------------------------------------------- /graphs/problems/mykoryza.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | from math import inf, isinf 3 | 4 | 5 | def mykoryza(G, T, d): 6 | n = len(G) 7 | 8 | queue = deque() 9 | dist = [inf] * n 10 | 11 | for m in range(len(T)): 12 | u = T[m] 13 | dist[u] = m 14 | queue.append(u) 15 | 16 | count = 1 17 | 18 | while queue: 19 | u = queue.popleft() 20 | 21 | for v in G[u]: 22 | if isinf(dist[v]): 23 | if dist[u] == d: 24 | count += 1 25 | 26 | dist[v] = dist[u] 27 | queue.append(v) 28 | 29 | return count 30 | -------------------------------------------------------------------------------- /graphs/problems/offline/.gitignore: -------------------------------------------------------------------------------- 1 | testy.py 2 | *test_spec.py 3 | *testy.py -------------------------------------------------------------------------------- /graphs/problems/offline/offline3/zad3.py: -------------------------------------------------------------------------------- 1 | """ 2 | Z reguły mnożenia (kombinatoryka) 3 | 4 | - x = ilość najkrótszych ścieżek z t do u 5 | - y = ilość najkrótszych ścieżek z s do v 6 | - x * y = ilość ścieżek przechodzących przez krawędź u--v 7 | 8 | Jeżeli x * y == ilości najkrótszych ścieżek z t do s, 9 | to najkrótsza ścieżka musi przejść przez u--v. 10 | 11 | Z tego powodu u--v to "most" w grafie najkrótszych ścieżek. 12 | Usunięcie u--v musi spowodować wydłużenie najkrótszej ścieżki. 13 | """ 14 | 15 | from zad3testy import runtests 16 | from collections import deque 17 | from math import inf 18 | 19 | 20 | def bfs(G, start): 21 | n = len(G) 22 | 23 | visited = [False] * n 24 | distances = [inf] * n 25 | parents = [None] * n 26 | counts = [0] * n 27 | 28 | visited[start] = True 29 | distances[start] = 0 30 | counts[start] = 1 31 | 32 | queue = deque([start]) 33 | 34 | while queue: 35 | v = queue.pop() 36 | 37 | for u in G[v]: 38 | if not visited[u]: 39 | visited[u] = True 40 | parents[u] = v 41 | distances[u] = distances[v] + 1 42 | counts[u] = counts[v] 43 | queue.appendleft(u) 44 | elif distances[v] + 1 == distances[u]: 45 | counts[u] += counts[v] 46 | 47 | return counts, parents 48 | 49 | 50 | def longer(G, s, t): 51 | counts_s_t, parents = bfs(G, s) 52 | counts_t_s, _ = bfs(G, t) 53 | 54 | shortest = [] 55 | curr = t 56 | 57 | while curr is not None: 58 | shortest.append(curr) 59 | curr = parents[curr] 60 | 61 | total_shortest_s_t = counts_s_t[t] 62 | 63 | for i in range(len(shortest) - 1): 64 | u = shortest[i] 65 | v = shortest[i + 1] 66 | 67 | if counts_t_s[u] * counts_s_t[v] == total_shortest_s_t: 68 | return shortest[i + 1], shortest[i] 69 | 70 | return None 71 | 72 | 73 | # zmien all_tests na True zeby uruchomic wszystkie testy 74 | runtests(longer, all_tests=True) 75 | -------------------------------------------------------------------------------- /graphs/problems/offline/offline4/zad4.py: -------------------------------------------------------------------------------- 1 | from zad4testy import runtests 2 | from queue import PriorityQueue 3 | from math import inf 4 | 5 | 6 | def dijkstra(n, graph, a): 7 | dist = [inf] * n 8 | visited = [False] * n 9 | queue = PriorityQueue() 10 | 11 | dist[a] = 0 12 | queue.put((0, a)) 13 | 14 | while not queue.empty(): 15 | v_dist, v = queue.get() 16 | 17 | if visited[v]: 18 | continue 19 | 20 | visited[v] = True 21 | 22 | for u, t in graph[v]: 23 | if not visited[u] and dist[u] > dist[v] + t: 24 | dist[u] = dist[v] + t 25 | queue.put((dist[u], u)) 26 | 27 | return dist 28 | 29 | 30 | def spacetravel(n, E, S, a, b): 31 | graph = [[] for _ in range(n + 1)] 32 | 33 | for u, v, t in E: 34 | graph[u].append((v, t)) 35 | graph[v].append((u, t)) 36 | 37 | for v in S: 38 | graph[n].append((v, 0)) 39 | graph[v].append((n, 0)) 40 | 41 | distances = dijkstra(n + 1, graph, a) 42 | 43 | return distances[b] if distances[b] != inf else None 44 | 45 | 46 | # zmien all_tests na True zeby uruchomic wszystkie testy 47 | runtests(spacetravel, all_tests=True) 48 | -------------------------------------------------------------------------------- /graphs/problems/offline/offline5/zad.py: -------------------------------------------------------------------------------- 1 | from zadtesty import runtests 2 | from collections import deque 3 | from math import inf 4 | 5 | 6 | def goodknight(G, s, t): 7 | n = len(G) 8 | 9 | # We need to duplicate every vertex 17 times, so that 10 | # we can visit every vertex with different stamina levels. 11 | dist = [[inf] * 17 for _ in range(n)] 12 | 13 | buckets = [deque() for _ in range(16 * n + 1)] 14 | 15 | buckets[0].append((s, 16)) 16 | dist[s] = [0] * 17 17 | 18 | d = 0 19 | 20 | while True: 21 | # Find next not empty bucket. 22 | # This while does O(C * n) iterations. 23 | while not buckets[d] and d < 16 * n: 24 | d += 1 25 | 26 | # Stop if we have processed all buckets. 27 | if d == 16 * n: 28 | break 29 | 30 | # This pop() can happen O(V) times, so in our case O(n) 31 | # (all of the vertices can be in the same bucket) 32 | u, stamina = buckets[d].pop() 33 | 34 | # Skip stale vertices. 35 | if d > dist[u][stamina]: 36 | continue 37 | 38 | # Relax every edge. 39 | # This while does O(E) iterations, so in our case O(n) 40 | for v in range(n): 41 | if G[u][v] == -1: 42 | continue 43 | 44 | w = G[u][v] 45 | 46 | # Simulate sleeping in a castle. 47 | # We can't go longer than 16 hours. 48 | # Sleeping at a castle resets stamina, 49 | # but adds 8 to the weight. Edge weights 50 | # are from 1 to 8, but sleeping makes it 1 - 16. 51 | 52 | real_w = w 53 | real_stamina = stamina 54 | 55 | if real_w > real_stamina: 56 | real_stamina = 16 57 | real_w += 8 58 | 59 | real_stamina -= w 60 | alternative = d + real_w 61 | 62 | if dist[v][real_stamina] > alternative: 63 | dist[v][real_stamina] = alternative 64 | buckets[alternative].append((v, real_stamina)) 65 | 66 | return min(dist[t]) 67 | 68 | 69 | runtests(goodknight, all_tests=True) 70 | -------------------------------------------------------------------------------- /graphs/problems/offline/offline5/zadv2.py: -------------------------------------------------------------------------------- 1 | from zadtesty import runtests 2 | from collections import deque 3 | from math import inf 4 | 5 | 6 | def goodknight(G, s, t): 7 | n = len(G) 8 | 9 | dist = [[inf] * 17 for _ in range(n)] 10 | queue = deque() 11 | 12 | dist[s] = [0] * 17 13 | # (u, distance at u, steps left to reach u, stamina at u) 14 | queue.append((s, dist[s][0], 0, 16)) 15 | 16 | while queue: 17 | u, d, steps, stamina = queue.popleft() 18 | 19 | # If we've processed u with better 20 | # distance before, we can ignore this case. 21 | if d > dist[u][stamina]: 22 | continue 23 | 24 | # If we didn't reach u yet, continue our journey. 25 | if steps != 0: 26 | queue.append((u, d, steps - 1, stamina)) 27 | continue 28 | 29 | for v in range(n): 30 | # If u and v aren't connected. 31 | if G[u][v] == -1: 32 | continue 33 | 34 | w = G[u][v] 35 | real_w = w 36 | real_stamina = stamina 37 | 38 | # Consider sleeping. 39 | if w > stamina: 40 | real_w += 8 41 | real_stamina = 16 42 | 43 | real_stamina -= w 44 | alternative = d + real_w 45 | 46 | if dist[v][real_stamina] > alternative: 47 | dist[v][real_stamina] = alternative 48 | queue.append((v, alternative, real_w - 1, real_stamina)) 49 | 50 | return min(dist[t]) 51 | 52 | 53 | runtests(goodknight, all_tests=True) 54 | -------------------------------------------------------------------------------- /graphs/problems/robot.py: -------------------------------------------------------------------------------- 1 | from math import inf 2 | 3 | 4 | def floyd_warshall(graph): 5 | n = len(graph) 6 | dist = [[inf] * n for _ in range(n)] 7 | 8 | for u in range(n): 9 | dist[u][u] = 0 10 | 11 | for v, w in graph[u]: 12 | dist[u][v] = w 13 | dist[v][u] = w 14 | 15 | for k in range(n): 16 | for u in range(n): 17 | for v in range(n): 18 | alt = dist[u][k] + dist[k][v] 19 | 20 | if dist[u][v] > alt: 21 | dist[u][v] = alt 22 | 23 | return dist 24 | 25 | 26 | def robot(G, P): 27 | dist = floyd_warshall(G) 28 | 29 | w = 0 30 | for p in range(len(P) - 1): 31 | w += dist[P[p]][P[p + 1]] 32 | 33 | return w 34 | -------------------------------------------------------------------------------- /graphs/problems/turysta.py: -------------------------------------------------------------------------------- 1 | from queue import PriorityQueue 2 | from math import inf 3 | 4 | 5 | def get_adj(G): 6 | n = max(map(lambda x: max(x[0], x[1]), G)) + 1 7 | 8 | adj = [[] for _ in range(n)] 9 | 10 | for u, v, w in G: 11 | adj[u].append((v, w)) 12 | adj[v].append((u, w)) 13 | 14 | return adj, n 15 | 16 | 17 | def turysta(G, D, L): 18 | adj, n = get_adj(G) 19 | 20 | dist = [[inf] * 5 for _ in range(n)] 21 | queue = PriorityQueue() 22 | 23 | dist[D] = [0] * 5 24 | queue.put((dist[D][0], D, 0)) 25 | 26 | while not queue.empty(): 27 | d, u, visits = queue.get() 28 | 29 | # If we have reached the airport, 30 | # we have found the shortest path 31 | # through 3 attractions. 32 | if u == L: 33 | return d 34 | 35 | # If we have found a shorter path 36 | # to an attraction at u, continue. 37 | if d > dist[u][visits]: 38 | continue 39 | 40 | for v, w in adj[u]: 41 | # If we have visited 3 attractions 42 | # already, force choosing the airport. 43 | if visits == 3 and v != L or visits < 3 and v == L: 44 | continue 45 | 46 | alt = d + w 47 | 48 | if dist[v][visits + 1] > alt: 49 | dist[v][visits + 1] = alt 50 | queue.put((alt, v, visits + 1)) 51 | -------------------------------------------------------------------------------- /graphs/problems/warrior.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | from math import inf 3 | 4 | 5 | def process_graph(G): 6 | n = -inf 7 | 8 | for u, v, w in G: 9 | n = max(n, u, v) 10 | 11 | graph = [[] for _ in range(n + 1)] 12 | 13 | for u, v, w in G: 14 | graph[u].append((v, w)) 15 | graph[v].append((u, w)) 16 | 17 | return graph 18 | 19 | 20 | def warrior(graph, s, t): 21 | graph = process_graph(graph) 22 | n = len(graph) 23 | 24 | dist = [[inf] * 17 for _ in range(n)] 25 | 26 | queue = deque() 27 | queue.append((s, 0, 16, 0)) # (v, steps, stamina, journey) 28 | 29 | while queue: 30 | u, steps, stamina, journey = queue.popleft() 31 | 32 | if dist[u][stamina] < journey: 33 | continue 34 | 35 | if steps != 0: 36 | queue.append((u, steps - 1, stamina, journey)) 37 | continue 38 | 39 | for v, w in graph[u]: 40 | real_w = w 41 | real_stamina = stamina 42 | 43 | if w > stamina: 44 | real_w += 8 45 | real_stamina = 16 46 | 47 | real_stamina -= w 48 | new_journey = journey + real_w 49 | 50 | if new_journey < dist[v][real_stamina]: 51 | dist[v][real_stamina] = new_journey 52 | queue.append((v, real_w - 1, real_stamina, new_journey)) 53 | 54 | return min(dist[t]) 55 | -------------------------------------------------------------------------------- /graphs/problems/warrior_v2.py: -------------------------------------------------------------------------------- 1 | from queue import PriorityQueue 2 | from math import inf 3 | 4 | 5 | def get_adj_list(edges): 6 | n = max(map(lambda x: max(x[0], x[1]), edges)) + 1 7 | graph = [[] for _ in range(n)] 8 | 9 | for u, v, w in edges: 10 | graph[u].append((v, w)) 11 | graph[v].append((u, w)) 12 | 13 | return graph, n 14 | 15 | 16 | def warrior(G, s, t): 17 | adj, n = get_adj_list(G) 18 | 19 | dist = [[inf] * 17 for _ in range(n)] 20 | dist[0] = [0] * 17 21 | 22 | queue = PriorityQueue() 23 | queue.put((0, s, 16)) 24 | 25 | while not queue.empty(): 26 | d, u, stamina = queue.get() 27 | 28 | if d > dist[u][stamina]: 29 | continue 30 | 31 | for v, w in adj[u]: 32 | real_w = w 33 | real_stamina = stamina 34 | 35 | if real_w > real_stamina: 36 | real_w += 8 37 | real_stamina = 16 38 | 39 | real_stamina -= w 40 | 41 | if dist[v][real_stamina] > d + real_w: 42 | dist[v][real_stamina] = d + real_w 43 | queue.put((dist[v][real_stamina], v, real_stamina)) 44 | 45 | return min(dist[t]) 46 | -------------------------------------------------------------------------------- /random/min_max.py: -------------------------------------------------------------------------------- 1 | from math import inf 2 | from random import randint 3 | 4 | 5 | # Finds minimum and maximum of an array in O((3/2)*n) time complexity. 6 | def min_max(a): 7 | n = len(a) 8 | 9 | found_min = inf 10 | found_max = -inf 11 | 12 | for i in range(0, n, 2): 13 | j = i + 1 14 | 15 | # In each iteration we make 3 comparisons. 16 | # Because of that we will make ((1/2)*n)*3 comparisons, so (3/2)*n 17 | 18 | if a[i] <= a[j]: 19 | if a[i] < found_min: 20 | found_min = a[i] 21 | if a[j] > found_max: 22 | found_max = a[j] 23 | else: 24 | if a[i] > found_max: 25 | found_max = a[i] 26 | if a[j] < found_min: 27 | found_min = a[j] 28 | 29 | return found_min, found_max 30 | 31 | 32 | for _ in range(20): 33 | # arrange 34 | a = [randint(0, 420) for _ in range(10)] 35 | expected_min, expected_max = min(a), max(a) 36 | 37 | # test 38 | found_min, found_max = min_max(a) 39 | 40 | # assert 41 | assert found_min == expected_min and found_max == expected_max 42 | print("OK") 43 | -------------------------------------------------------------------------------- /random/shift_left.py: -------------------------------------------------------------------------------- 1 | def shift_left(a, k): 2 | n = len(a) 3 | 4 | # It doesn't make sense to shift array by itself 5 | assert k < n 6 | 7 | for i in range(k): 8 | a[i], a[i + k] = a[i + k], a[i] 9 | 10 | left = n - 2 * k 11 | counter = 0 12 | 13 | while counter < left: 14 | for i in range(n - left + counter, k + counter, -1): 15 | a[i], a[i - 1] = a[i - 1], a[i] 16 | 17 | counter += 1 18 | -------------------------------------------------------------------------------- /sorting/algorithms/binary_search.py: -------------------------------------------------------------------------------- 1 | # Searches for a value in O(n * log(n)). 2 | def binary_search(a, k): 3 | n = len(a) 4 | 5 | left = 0 6 | right = n - 1 7 | 8 | while left <= right: 9 | mid = (left + right) // 2 10 | 11 | if a[mid] == k: 12 | return mid 13 | elif a[mid] < k: 14 | left = mid + 1 15 | else: 16 | right = mid - 1 17 | 18 | return -1 19 | 20 | 21 | # Finds the first index equal to k. 22 | def left_bound(a, k): 23 | n = len(a) 24 | 25 | left = 0 26 | right = n - 1 27 | 28 | while left <= right: 29 | mid = (left + right) // 2 30 | 31 | # Because of < operator we'll 32 | # move to the left boundary. 33 | if a[mid] < k: 34 | left = mid + 1 35 | else: 36 | right = mid - 1 37 | 38 | return left 39 | 40 | 41 | # Finds the first index greater than k. 42 | def right_bound(a, k): 43 | n = len(a) 44 | 45 | left = 0 46 | right = n - 1 47 | 48 | while left <= right: 49 | mid = (left + right) // 2 50 | 51 | # Because of <= operator we'll 52 | # move to the right boundary. 53 | if a[mid] <= k: 54 | left = mid + 1 55 | else: 56 | right = mid - 1 57 | 58 | return left 59 | 60 | 61 | # Counts occurrences of an element in O(n * log(n)) 62 | def binary_count(a, k): 63 | return right_bound(a, k) - left_bound(a, k) 64 | 65 | 66 | if __name__ == "__main__": 67 | a = [2, 2, 2, 2, 4, 5, 5] 68 | print(a) 69 | print(binary_count(a, 2)) 70 | print(binary_search(a, 4)) 71 | -------------------------------------------------------------------------------- /sorting/algorithms/bucket_sort.py: -------------------------------------------------------------------------------- 1 | from random import uniform 2 | 3 | 4 | # Doing insertion sort this way (without swapping) 5 | # reduces number of operations performed in the inner 6 | # while loop. 7 | def insertion_sort(a): 8 | n = len(a) 9 | 10 | for i in range(1, n): 11 | key = a[i] 12 | j = i - 1 13 | 14 | while j >= 0 and a[j] >= key: 15 | a[j + 1] = a[j] 16 | j -= 1 17 | 18 | a[j + 1] = key 19 | 20 | 21 | def bucket_sort(a): 22 | n = len(a) 23 | buckets = [[] for _ in range(n)] 24 | 25 | min_v = min(a) 26 | max_v = max(a) 27 | span = max_v - min_v 28 | 29 | # Insert values to their buckets 30 | for v in a: 31 | bucket_index = int((v - min_v) / span * (n - 1)) 32 | buckets[bucket_index].append(v) 33 | 34 | # Sort each bucket with insertion sort. 35 | for bucket in buckets: 36 | insertion_sort(bucket) 37 | 38 | # Insert sorted values back to a 39 | k = 0 40 | for bucket in buckets: 41 | for v in bucket: 42 | a[k] = v 43 | k += 1 44 | 45 | 46 | for _ in range(20): 47 | # arrange 48 | a = [uniform(100, 999) for _ in range(15)] 49 | expected = sorted(a) 50 | 51 | # test 52 | bucket_sort(a) 53 | 54 | # assert 55 | assert a == expected 56 | print(a, "ok") 57 | -------------------------------------------------------------------------------- /sorting/algorithms/counting_sort.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | 3 | ''' 4 | (1) Store number of elements equal to 5 | x at index count[x]. 6 | 7 | (2) Store number of elements less than or equal 8 | to x at count[x]. 9 | 10 | (3) At this point we have an array count which stores number of elements 11 | less than or equal to x, where x is the index. So count[200] = 100 12 | means that there are 100 elements less than or equal to 200. 13 | 14 | 15 | (4) Find how many elements less than or equal to a[i] there are, 16 | and insert a[i] to the index after them (excluding itself). 17 | So if we have count[200] = 100, then we want to insert 200 18 | at index 99 (100'th position) 19 | ''' 20 | 21 | 22 | def counting_sort(a, n, limit): 23 | out = [0 for _ in range(n)] 24 | count = [0 for _ in range(limit + 1)] 25 | 26 | # (1) 27 | for i in range(n): 28 | count[a[i]] += 1 29 | 30 | # (2) 31 | for i in range(1, limit + 1): 32 | count[i] += count[i - 1] 33 | 34 | # (3) 35 | 36 | # (4) 37 | for i in range(n - 1, -1, -1): 38 | count[a[i]] -= 1 39 | out[count[a[i]]] = a[i] 40 | 41 | return out 42 | 43 | 44 | for _ in range(20): 45 | # arrange 46 | a = [randint(100, 999) for _ in range(15)] 47 | expected = sorted(a) 48 | 49 | # test 50 | a = counting_sort(a, len(a), max(a)) 51 | 52 | # assert 53 | assert a == expected 54 | print(a, "ok") 55 | -------------------------------------------------------------------------------- /sorting/algorithms/heap_sort.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | 3 | left = lambda i: 2 * i + 1 4 | right = lambda i: 2 * i + 2 5 | parent = lambda i: (i - 1) // 2 6 | 7 | 8 | def max_heapify(heap, n, i): 9 | l = left(i) 10 | r = right(i) 11 | max_index = i 12 | 13 | if l < n and heap[l] > heap[max_index]: 14 | max_index = l 15 | 16 | if r < n and heap[r] > heap[max_index]: 17 | max_index = r 18 | 19 | # If max_index is not i, then we need to "flow" down 20 | # in the heap with value at index i. 21 | if max_index != i: 22 | heap[max_index], heap[i] = heap[i], heap[max_index] 23 | max_heapify(heap, n, max_index) 24 | 25 | 26 | def build_max_heap(a): 27 | n = len(a) 28 | 29 | # Heapify bottom-up starting at index of last inner node. 30 | for i in range(parent(n - 1), -1, -1): 31 | max_heapify(a, n, i) 32 | 33 | 34 | def heap_sort(a): 35 | n = len(a) 36 | build_max_heap(a) 37 | 38 | # Top contains maximum element from the array. 39 | # Shrink the heap. Now n is index of the last leaf. 40 | # Swap it with the top, so that it's at sorted position in the final array. 41 | # Run heapify for the leaf, now at index 0, to make sure max heap property is met. 42 | 43 | for i in range(n - 1, 0, -1): 44 | a[0], a[i] = a[i], a[0] 45 | max_heapify(a, i, 0) 46 | 47 | 48 | for _ in range(20): 49 | # arrange 50 | a = [randint(100, 999) for _ in range(15)] 51 | expected = sorted(a) 52 | 53 | # test 54 | heap_sort(a) 55 | 56 | # assert 57 | assert a == expected 58 | print(a, "ok") 59 | -------------------------------------------------------------------------------- /sorting/algorithms/insertion_sort.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | 3 | 4 | def insertion_sort(a): 5 | n = len(a) 6 | 7 | for i in range(1, n): 8 | key = a[i] 9 | j = i - 1 10 | 11 | while j >= 0 and a[j] >= key: 12 | a[j + 1] = a[j] 13 | j -= 1 14 | 15 | a[j + 1] = key 16 | 17 | 18 | for _ in range(20): 19 | # arrange 20 | a = [randint(100, 999) for _ in range(15)] 21 | expected = sorted(a) 22 | 23 | # test 24 | insertion_sort(a) 25 | 26 | # assert 27 | assert a == expected 28 | print(a, "OK") 29 | -------------------------------------------------------------------------------- /sorting/algorithms/merge_sort.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | 3 | 4 | def merge(a, l, mid, r): 5 | # Copy values from left and right subarray for merge operation 6 | left = a[l:mid + 1] 7 | right = a[mid + 1:r + 1] 8 | 9 | i = 0 10 | j = 0 11 | k = l # keep tract of insert position in original array 12 | 13 | # While both pointers are not finished 14 | while i < len(left) and j < len(right): 15 | # Choose smaller value and insert it to original array 16 | if left[i] <= right[j]: 17 | a[k] = left[i] 18 | i += 1 19 | else: 20 | a[k] = right[j] 21 | j += 1 22 | k += 1 23 | 24 | # Make sure all values from left and right subarray 25 | # are inserted to original array. 26 | 27 | while i < len(left): 28 | a[k] = left[i] 29 | i += 1 30 | k += 1 31 | 32 | while j < len(right): 33 | a[k] = right[j] 34 | j += 1 35 | k += 1 36 | 37 | 38 | def merge_sort(a, l, r): 39 | # If subarray has more than one element 40 | if l < r: 41 | # Find midpoint 42 | q = (l + r) // 2 43 | 44 | # Sort sub arrays recursively 45 | merge_sort(a, l, q) 46 | merge_sort(a, q + 1, r) 47 | 48 | # Merge sorted sub arrays 49 | merge(a, l, q, r) 50 | 51 | 52 | for _ in range(20): 53 | # arrange 54 | a = [randint(100, 999) for _ in range(15)] 55 | expected = sorted(a) 56 | 57 | # test 58 | merge_sort(a, 0, len(a) - 1) 59 | 60 | # assert 61 | assert a == expected 62 | print(a, "OK") 63 | -------------------------------------------------------------------------------- /sorting/algorithms/quickselect.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | 3 | 4 | # Lomuto's partition scheme. 5 | def partition(a, l, r): 6 | i = l - 1 7 | 8 | for j in range(l, r): 9 | if a[j] <= a[r]: 10 | i += 1 11 | a[i], a[j] = a[j], a[i] 12 | 13 | a[i + 1], a[r] = a[r], a[i + 1] 14 | 15 | return i + 1 16 | 17 | 18 | # Selects the k-th smallest index in unsorted array in O(n) time. 19 | def quickselect(a, l, r, k): 20 | # k-th index should be present in the array. 21 | # We can handle this case in an arbitrary way. 22 | assert l <= k <= r 23 | 24 | # This also won't happen, if sought index exists. 25 | if l == r: 26 | return a[l] 27 | 28 | pivot_index = partition(a, l, r) 29 | 30 | # If pivot index is the sought index, return the found value. 31 | # If k is smaller than pivot index, search in left subarray. 32 | # If k is greater to pivot index, search in right subarray. 33 | 34 | if k == pivot_index: 35 | return a[k] 36 | elif k < pivot_index: 37 | return quickselect(a, l, pivot_index - 1, k) 38 | else: 39 | return quickselect(a, pivot_index + 1, r, k) 40 | 41 | 42 | for _ in range(1): 43 | # arrange 44 | k = randint(0, 19) 45 | a = [randint(1, 9) for _ in range(10)] 46 | 47 | expected = sorted(a)[k] 48 | 49 | # test 50 | result = quickselect(a, 0, len(a) - 1, k) 51 | 52 | # assert 53 | assert expected == result 54 | print(expected, "=", result, "OK") 55 | -------------------------------------------------------------------------------- /sorting/algorithms/quicksort.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | 3 | 4 | # Lomuto's partitioning algorithm. 5 | def partition(a, l, r): 6 | # Pick the rightmost element as a pivot 7 | # and keep track of the highest index in the "low" subarray. 8 | pivot = a[r] 9 | i = l - 1 10 | 11 | # Note that we never reach index r, so pivot doesn't move during partitioning. 12 | for j in range(l, r): 13 | # If value a[j] belongs to the "low" subarray, 14 | # make it wider and move the value into it. 15 | if a[j] <= pivot: 16 | i += 1 17 | a[i], a[j] = a[j], a[i] 18 | 19 | # Move pivot between "low" and "high" sub arrays. 20 | pivot_index = i + 1 21 | a[pivot_index], a[r] = a[r], a[pivot_index] 22 | 23 | return pivot_index 24 | 25 | 26 | def quicksort(a, l, r): 27 | if l < r: 28 | pivot = partition(a, l, r) 29 | quicksort(a, l, pivot - 1) 30 | quicksort(a, pivot + 1, r) 31 | 32 | 33 | def quicksort_tail_recursion(a, l, r): 34 | while l < r: 35 | pivot = partition(a, l, r) 36 | 37 | if pivot - l < r - pivot: 38 | quicksort(a, l, pivot - 1) 39 | l = pivot + 1 40 | else: 41 | quicksort(a, pivot + 1, r) 42 | r = pivot - 1 43 | 44 | 45 | for _ in range(20): 46 | # arrange 47 | a = [randint(100, 999) for _ in range(15)] 48 | expected = sorted(a) 49 | 50 | # test 51 | # quicksort(a, 0, len(a) - 1) 52 | quicksort_tail_recursion(a, 0, len(a) - 1) 53 | 54 | # assert 55 | assert a == expected 56 | print(a, "OK") 57 | -------------------------------------------------------------------------------- /sorting/algorithms/selection_sort.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | 3 | 4 | def selection_sort(a): 5 | n = len(a) 6 | 7 | for i in range(n): 8 | min_j = i 9 | 10 | for j in range(i + 1, n): 11 | if a[j] < a[min_j]: 12 | min_j = j 13 | 14 | a[i], a[min_j] = a[min_j], a[i] 15 | 16 | 17 | for _ in range(20): 18 | # arrange 19 | a = [randint(100, 999) for _ in range(15)] 20 | expected = sorted(a) 21 | 22 | # test 23 | selection_sort(a) 24 | 25 | # assert 26 | assert a == expected 27 | print(a, "OK") 28 | -------------------------------------------------------------------------------- /sorting/labs/lab1/zad1.py: -------------------------------------------------------------------------------- 1 | from typing import Self, cast 2 | 3 | 4 | class Node: 5 | def __init__(self): 6 | self.value: int = cast(int, None) 7 | self.next: Self | None = None 8 | self.prev: Self | None = None 9 | 10 | @staticmethod 11 | def from_list(values: list[int]): 12 | dummy = Node() 13 | head = dummy 14 | 15 | for v in values: 16 | new = Node() 17 | new.value = v 18 | head.next = new 19 | head = new 20 | 21 | return dummy 22 | 23 | 24 | def find_max(p: Node): 25 | max_value = -1 26 | curr_node = p.next 27 | 28 | while curr_node: 29 | if curr_node.value > max_value: 30 | max_value = curr_node.value 31 | curr_node = curr_node.next 32 | 33 | return max_value 34 | 35 | 36 | ll = Node.from_list([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]) 37 | print(find_max(ll)) 38 | -------------------------------------------------------------------------------- /sorting/labs/lab1/zad2.py: -------------------------------------------------------------------------------- 1 | from typing import Self, cast 2 | 3 | 4 | class Node: 5 | def __init__(self): 6 | self.value: int = cast(int, None) 7 | self.next: Self | None = None 8 | 9 | @staticmethod 10 | def from_list(values: list[int]): 11 | dummy = Node() 12 | head = dummy 13 | 14 | for v in values: 15 | new = Node() 16 | new.value = v 17 | head.next = new 18 | head = new 19 | 20 | return dummy 21 | 22 | def __repr__(self): 23 | out = "[" 24 | curr = self.next 25 | while curr: 26 | out += f"{curr.value}->" 27 | curr = curr.next 28 | out += "]" 29 | return out 30 | 31 | 32 | def insert_sorted(p: Node, v: int): 33 | prev = p 34 | curr = p.next 35 | 36 | new = Node() 37 | new.value = v 38 | 39 | while curr: 40 | if curr.value > v: 41 | prev.next = new 42 | new.next = curr 43 | break 44 | 45 | prev = curr 46 | curr = curr.next 47 | 48 | if curr is None: 49 | prev.next = new 50 | 51 | 52 | ll = Node.from_list([1, 2, 3, 4, 5, 7, 8, 9]) 53 | insert_sorted(ll, 6) 54 | print(ll) 55 | -------------------------------------------------------------------------------- /sorting/labs/lab1/zad3.py: -------------------------------------------------------------------------------- 1 | class Node: 2 | def __init__(self, value=None, next=None): 3 | self.value = value 4 | self.next = next 5 | 6 | def __repr__(self): 7 | out = "[" 8 | curr = self.next 9 | while curr: 10 | out += f"{curr.value}->" 11 | curr = curr.next 12 | out += "]" 13 | return out 14 | 15 | @staticmethod 16 | def from_list(values): 17 | dummy = Node() 18 | head = dummy 19 | 20 | for v in values: 21 | new = Node(v) 22 | head.next = new 23 | head = new 24 | 25 | return dummy 26 | 27 | 28 | # Extracts parent of a node with minimum value. 29 | def extract_min(p): 30 | min_p = p 31 | prev = p.next 32 | curr = prev.next 33 | 34 | while curr: 35 | if curr.value < min_p.next.value: 36 | min_p = prev 37 | 38 | prev = curr 39 | curr = curr.next 40 | 41 | return min_p 42 | 43 | 44 | # Inserts a node at sorted position to the list with sentinel. 45 | def insert_sorted(p, new): 46 | prev = p 47 | curr = p.next 48 | 49 | while curr: 50 | if curr.value > new.value: 51 | prev.next = new 52 | new.next = curr 53 | break 54 | 55 | prev = curr 56 | curr = curr.next 57 | 58 | if curr is None: 59 | prev.next = new 60 | 61 | 62 | def selection_sort(p): 63 | # Head points to the last element in the sorted prefix. 64 | head = p.next 65 | 66 | while head and head.next: 67 | if head.value > head.next.value: 68 | # Find parent of the minimum node 69 | # in the remaining list 70 | min_p = extract_min(head) 71 | min_node = min_p.next 72 | 73 | # Detach minimum node 74 | min_p.next = min_p.next.next 75 | min_node.next = None 76 | 77 | # Insert min node into sorted list 78 | insert_sorted(p, min_node) 79 | else: 80 | head = head.next 81 | 82 | 83 | for test in [ 84 | [3, 6, 2, 4, 6, 2, 5, 2, 5], 85 | [], 86 | [1], 87 | [7, 6, 5, 4, 3] 88 | ]: 89 | ll = Node.from_list(test) 90 | selection_sort(ll) 91 | -------------------------------------------------------------------------------- /sorting/labs/lab2/zad1.py: -------------------------------------------------------------------------------- 1 | """ 2 | Proszę zaproponować algorytm, który mając na wejsciu tablicę A zwraca liczbę jej inwersji 3 | (t.j, liczbę par indeksów i < j takich, że A[i] > A[j].) 4 | """ 5 | 6 | 7 | def merge(a, l, mid, r): 8 | inversions = 0 9 | 10 | left = a[l:mid + 1] 11 | right = a[mid + 1: r + 1] 12 | 13 | i = 0 14 | j = 0 15 | k = l 16 | 17 | while i < len(left) and j < len(right): 18 | if left[i] <= right[j]: 19 | a[k] = left[i] 20 | i += 1 21 | else: 22 | inversions += len(left) - i 23 | a[k] = right[j] 24 | j += 1 25 | k += 1 26 | 27 | while i < len(left): 28 | # We don't count any inversions here. 29 | # All inversions for smaller elements from right 30 | # subarray were already counted in the while above. 31 | 32 | a[k] = left[i] 33 | i += 1 34 | k += 1 35 | 36 | while j < len(right): 37 | a[k] = right[j] 38 | j += 1 39 | k += 1 40 | 41 | return inversions 42 | 43 | 44 | def number_of_inversions(a, l, r): 45 | inv = 0 46 | 47 | if l < r: 48 | mid = (l + r) // 2 49 | inv += number_of_inversions(a, l, mid) + \ 50 | number_of_inversions(a, mid + 1, r) + \ 51 | merge(a, l, mid, r) 52 | 53 | return inv 54 | 55 | 56 | if __name__ == "__main__": 57 | a = [1, 2, 3, 4, 5, 1, 2] 58 | print(number_of_inversions(a, 0, len(a) - 1)) 59 | -------------------------------------------------------------------------------- /sorting/labs/lab2/zad2.py: -------------------------------------------------------------------------------- 1 | """ 2 | Proszę zaimplementować operację która mając na wejściu dwie posortowane listy jednokierunkowe- 3 | kowe zwraca posortowaną listę zawierającą wszystkie elementy list wejściowych. 4 | """ 5 | 6 | 7 | class Node: 8 | def __init__(self): 9 | self.value = None 10 | self.next = None 11 | 12 | def __repr__(self): 13 | out = "[" 14 | curr = self 15 | while curr: 16 | out += f"{curr.value}->" 17 | curr = curr.next 18 | out += "]" 19 | return out 20 | 21 | @staticmethod 22 | def from_list(values: list[int]): 23 | dummy = Node() 24 | head = dummy 25 | 26 | for v in values: 27 | new = Node() 28 | new.value = v 29 | head.next = new 30 | head = new 31 | 32 | return dummy.next 33 | 34 | 35 | def merge_lists(p: Node, q: Node): 36 | dummy = Node() 37 | prev = dummy 38 | 39 | # While both p and q are some nodes. 40 | while p and q: 41 | # Pick node with the smallest value. 42 | if p.value < q.value: 43 | prev.next = p # Chain it with prev 44 | prev = p # Set prev to p 45 | p = p.next # Advance p 46 | else: 47 | prev.next = q 48 | prev = q 49 | q = q.next 50 | 51 | # At this point we have finished visiting nodes in p or q. 52 | # We have to attach the rest of the list which wasn't fully visited. 53 | 54 | if p: 55 | prev.next = p 56 | 57 | if q: 58 | prev.next = q 59 | 60 | return dummy.next 61 | 62 | 63 | if __name__ == "__main__": 64 | a = Node.from_list([1, 3, 5, 7, 9, 11]) 65 | b = Node.from_list([2, 4, 6, 8, 10, 12, 14, 16]) 66 | out = merge_lists(a, b) 67 | print(out) 68 | -------------------------------------------------------------------------------- /sorting/labs/lab2/zad3.py: -------------------------------------------------------------------------------- 1 | """ 2 | Proszę zaimplementować MergeSort dla listy jednokierunkowej. 3 | """ 4 | 5 | from zad2 import merge_lists 6 | 7 | 8 | class Node: 9 | def __init__(self): 10 | self.value = None 11 | self.next = None 12 | 13 | def __repr__(self): 14 | out = "[" 15 | curr = self 16 | while curr: 17 | out += f"{curr.value}->" 18 | curr = curr.next 19 | out += "]" 20 | return out 21 | 22 | @staticmethod 23 | def from_list(values: list[int]): 24 | dummy = Node() 25 | head = dummy 26 | 27 | for v in values: 28 | new = Node() 29 | new.value = v 30 | head.next = new 31 | head = new 32 | 33 | return dummy.next 34 | 35 | 36 | def extract_mid(l, n): 37 | # We must to use this function only 38 | # when there are at least 2 nodes. 39 | assert n > 1 40 | 41 | # Find middle node index and subtract one 42 | # to select middle node's parent. 43 | mid = (n // 2) - 1 44 | 45 | while mid > 0: 46 | l = l.next 47 | mid -= 1 48 | 49 | # Keep pointer to middle node 50 | # and detach halves from each other. 51 | mid = l.next 52 | l.next = None 53 | 54 | return mid 55 | 56 | 57 | def merge_sort(l): 58 | # Find number of nodes in list a. 59 | n = 0 60 | curr = l 61 | while curr: 62 | n += 1 63 | curr = curr.next 64 | 65 | # The actual merge sort algorithm. 66 | def sort(l, n): 67 | if not l.next: 68 | return l 69 | 70 | # If our list has at least two elements. 71 | if l.next: 72 | mid = extract_mid(l, n) 73 | l = sort(l, n // 2) 74 | mid = sort(mid, n - (n // 2)) 75 | return merge_lists(l, mid) 76 | 77 | return sort(l, n) 78 | 79 | 80 | if __name__ == "__main__": 81 | l = Node.from_list([13, 5, 31, 4, 212, 5, 6]) 82 | merge_sort(l).print() 83 | -------------------------------------------------------------------------------- /sorting/labs/lab2/zad4.py: -------------------------------------------------------------------------------- 1 | """ 2 | Proszę zaimplementować operację wstawiania elementu do kopca binarnego. 3 | """ 4 | 5 | 6 | left = lambda i: 2 * i + 1 7 | right = lambda i: 2 * i + 2 8 | parent = lambda i: (i - 2) // 2 9 | 10 | 11 | def heapify(heap, n, i): 12 | l = left(i) 13 | r = right(i) 14 | max_index = i 15 | 16 | if l < n and heap[l] > heap[max_index]: 17 | max_index = l 18 | 19 | if r < n and heap[r] > heap[max_index]: 20 | max_index = r 21 | 22 | if max_index != i: 23 | heap[max_index], heap[i] = heap[i], heap[max_index] 24 | heapify(heap, n, max_index) 25 | 26 | 27 | def build_heap(a): 28 | n = len(a) 29 | 30 | for i in range(parent(n - 1), 0, -1): 31 | heapify(a, n, i) 32 | 33 | 34 | def insert_heap(heap, n, value): 35 | # We assume that our heap is dynamically sized. 36 | if n < len(heap): 37 | heap[n] = value 38 | else: 39 | heap.append(value) 40 | 41 | n += 1 42 | i = n - 1 43 | 44 | while i > 0 and heap[parent(i)] < value: 45 | heap[parent(i)], heap[i] = heap[i], heap[parent(i)] 46 | i = parent(i) 47 | 48 | # Return new heap length back to the caller 49 | return n 50 | 51 | 52 | if __name__ == "__main__": 53 | a = [1, 2, 3, 4, 5, 6] 54 | build_heap(a) 55 | print(a) 56 | 57 | insert_heap(a, len(a) - 1, 10) 58 | print(a) 59 | -------------------------------------------------------------------------------- /sorting/labs/lab2/zad5.py: -------------------------------------------------------------------------------- 1 | E_OPEN = "open" 2 | E_CLOSE = "close" 3 | 4 | type Point = tuple[int, int] 5 | 6 | 7 | class Event: 8 | def __init__(self, type, y, width): 9 | self.type = type 10 | self.y = y 11 | self.width = width 12 | 13 | 14 | def merge(a, l, mid, r): 15 | left = a[l:mid + 1] 16 | right = a[mid + 1:r + 1] 17 | 18 | i = 0 19 | j = 0 20 | k = l 21 | 22 | while i < len(left) and j < len(right): 23 | if left[i].y < right[j].y: 24 | a[k] = left[i] 25 | i += 1 26 | else: 27 | a[k] = right[j] 28 | j += 1 29 | k += 1 30 | 31 | while i < len(left): 32 | a[k] = left[i] 33 | i += 1 34 | k += 1 35 | 36 | while j < len(right): 37 | a[k] = right[j] 38 | j += 1 39 | k += 1 40 | 41 | 42 | def merge_sort(a, l, r): 43 | if l < r: 44 | mid = l + (r - l) // 2 45 | merge_sort(a, l, mid) 46 | merge_sort(a, mid + 1, r) 47 | merge(a, l, mid, r) 48 | 49 | 50 | def find_filled_tanks(a: list[tuple[Point, Point]], water): 51 | n = len(a) 52 | events = [] 53 | 54 | # Create events from container 55 | for i in range(n): 56 | top, bottom = a[i] 57 | width = bottom[0] - top[0] 58 | events.append(Event(type=E_OPEN, y=bottom[1], width=width)) 59 | events.append(Event(type=E_CLOSE, y=top[1], width=width)) 60 | 61 | # Sort events by occurrence height. 62 | merge_sort(events, 0, len(events) - 1) 63 | 64 | filled_count = 0 65 | current_width = 0 66 | current_y = 0 67 | 68 | # Process sorted events. 69 | for e in events: 70 | water -= current_width * (e.y - current_y) 71 | 72 | if water < 0: 73 | break 74 | 75 | if e.type is E_OPEN: 76 | current_width += e.width 77 | if e.type is E_CLOSE: 78 | filled_count += 1 79 | current_width -= e.width 80 | 81 | current_y = e.y 82 | 83 | return filled_count 84 | 85 | 86 | if __name__ == "__main__": 87 | tanks = [((10, 4), (14, 2)), ((5, 3), (8, 1)), ((3, 9), (7, 4))] 88 | print(find_filled_tanks(tanks, 20)) 89 | -------------------------------------------------------------------------------- /sorting/labs/lab2/zad6.py: -------------------------------------------------------------------------------- 1 | """ 2 | Proszę zaproponować strukturę danych, która pozwala wykonywać operacje: 3 | 1. Insert 4 | 2. RemoveMedian (wyciągnięcie mediany) tak, żeby operacje te działały w czasie O(logn). 5 | """ 6 | 7 | 8 | class MinHeap: 9 | def __init__(self): 10 | self.size = 0 11 | self.values = [] 12 | 13 | left = lambda i: 2 * i + 1 14 | right = lambda i: 2 * i + 2 15 | parent = lambda i: (i - 1) // 2 16 | 17 | def heapify(self, i): 18 | l = MinHeap.left(i) 19 | r = MinHeap.right(i) 20 | max_index = i 21 | 22 | if l < self.size and self.values[l] < self.values[max_index]: 23 | max_index = l 24 | 25 | if r < self.size and self.values[r] < self.values[max_index]: 26 | max_index = r 27 | 28 | if max_index != i: 29 | self.values[max_index], self.values[i] = self.values[i], self.values[max_index] 30 | self.heapify(max_index) 31 | 32 | def insert(self, value): 33 | if self.size < len(self.values): 34 | self.values[self.size] = value 35 | else: 36 | self.values.append(value) 37 | 38 | self.size += 1 39 | i = self.size - 1 40 | 41 | while i > 0 and self.values[i] < self.values[MinHeap.parent(i)]: 42 | self.values[i], self.values[MinHeap.parent(i)] = self.values[MinHeap.parent(i)], self.values[i] 43 | i = MinHeap.parent(i) 44 | 45 | def pop(self): 46 | self.size -= 1 47 | self.values[0], self.values[self.size] = self.values[self.size], self.values[0] 48 | self.heapify(0) 49 | return self.values[self.size] 50 | 51 | def top(self): 52 | return self.values[0] 53 | 54 | 55 | class Median: 56 | def __init__(self): 57 | self.max_heap = MinHeap() 58 | self.min_heap = MinHeap() 59 | 60 | def insert(self, value): 61 | # If there are no values in both heaps, insert median 62 | # to the max heap. 63 | if self.max_heap.size == 0 and self.min_heap.size == 0: 64 | self.max_heap.insert(value) 65 | return 66 | 67 | # Insert value to the correct heap based on top value in max_heap. 68 | if value <= -self.max_heap.top(): 69 | self.max_heap.insert(-value) 70 | else: 71 | self.min_heap.insert(value) 72 | 73 | # Rebalance heaps. 74 | self._rebalance() 75 | 76 | def remove_median(self): 77 | median = self.get_median() 78 | 79 | if self.max_heap.size == self.min_heap.size: 80 | self.min_heap.pop() 81 | self.max_heap.pop() 82 | else: 83 | self.min_heap.pop() 84 | self._rebalance() 85 | 86 | return median 87 | 88 | def get_median(self): 89 | return -self.max_heap.top() \ 90 | if self.max_heap.size > self.min_heap.size \ 91 | else (-self.max_heap.top() + self.min_heap.top()) / 2.0 92 | 93 | def _rebalance(self): 94 | if self.max_heap.size - 1 > self.min_heap.size: 95 | self.min_heap.insert(-self.max_heap.pop()) 96 | elif self.min_heap.size > self.max_heap.size: 97 | self.max_heap.insert(-self.min_heap.pop()) 98 | 99 | 100 | if __name__ == "__main__": 101 | median = Median() 102 | 103 | for x in [1, 2, 2, 2, 3, 3, 6, 7, 7]: 104 | median.insert(x) 105 | 106 | print("mediana 1:", median.remove_median()) 107 | print("mediana 2:", median.remove_median()) 108 | print("mediana 3:", median.remove_median()) 109 | -------------------------------------------------------------------------------- /sorting/labs/lab3/zad1.py: -------------------------------------------------------------------------------- 1 | """ 2 | Quick sort używając max log(n) dodatkowej pamięci. 3 | """ 4 | 5 | from random import randint 6 | 7 | 8 | # Lomuto's partition scheme. 9 | def partition(A, l, r): 10 | pivot = A[r] 11 | i = l - 1 12 | 13 | for j in range(l, r): 14 | if A[j] <= pivot: 15 | i += 1 16 | A[j], A[i] = A[i], A[j] 17 | 18 | A[r], A[i + 1] = A[i + 1], A[r] 19 | return i + 1 20 | 21 | 22 | def quicksort(A, l, r): 23 | # (1) Usuwamy rekurencję ogonową, wykorzystując pętlę 24 | # zamiast ifa. Dzięki temu quicksort robi 2 razy mniej 25 | # wywołań rekurencyjnych. Ta optymalizacja nie gwarantuje jeszcze pamięci `O(nlogn)`. 26 | while l < r: 27 | pivot = partition(A, l, r) 28 | 29 | # (2) Po wybraniu pivot'a mniejszy przedział chcemy obsłużyć wywołaniem rekurencyjnym, 30 | # a większy przedział iteracyjnie dzięki pętli while. 31 | # To zagwarantuje nam zużycie pamięci `O(logn)`, ponieważ w najgorszym przypadku, kiedy 32 | # oba przedziały będą równej długości, zrobimy `logn` wywołań rekurencyjnych. 33 | 34 | if pivot - l < r - pivot: 35 | # Jeżeli lewy przedział jest mniejszy niż prawy przedział. 36 | quicksort(A, l, pivot - 1) 37 | l = pivot + 1 38 | else: 39 | quicksort(A, pivot + 1, r) 40 | r = pivot - 1 41 | 42 | 43 | for _ in range(20): 44 | # arrange 45 | a = [randint(100, 999) for _ in range(15)] 46 | expected = sorted(a) 47 | 48 | # test 49 | quicksort(a, 0, len(a) - 1) 50 | 51 | # assert 52 | assert a == expected 53 | print(a, "OK") 54 | -------------------------------------------------------------------------------- /sorting/labs/lab3/zad3.py: -------------------------------------------------------------------------------- 1 | """ 2 | `n` elementowa tablica `a` z liczbami ze zbioru 0 ... (n^2 - 1) 3 | Trzeba posortować jak najszybciej. 4 | 5 | Zauważamy, że mamy n elementów z zakresu n^2 - 1. 6 | Możemy zapisać każdą liczbę w systemie N-kowym. 7 | Gwarantuje nam to, że każda cyfra w naszej tablicy w systemie n-kowym ma 8 | co najwyżej 2 cyfry. Korzystamy z Radix Sort'a korzystającego z counting sorta 9 | do posortowania. 10 | """ 11 | from random import randint 12 | 13 | 14 | def sort(a): 15 | n = len(a) 16 | a = counting_sort(a, key=lambda x: x % n) 17 | a = counting_sort(a, key=lambda x: x // 10) 18 | return a 19 | 20 | 21 | def counting_sort(a, key): 22 | n = len(a) 23 | c = [0] * n 24 | b = [0] * n 25 | 26 | for i in range(n): 27 | c[key(a[i])] += 1 28 | 29 | for i in range(1, n): 30 | c[i] += c[i - 1] 31 | 32 | for i in range(n - 1, -1, -1): 33 | c[key(a[i])] -= 1 34 | b[c[key(a[i])]] = a[i] 35 | 36 | return b 37 | 38 | 39 | if __name__ == "__main__": 40 | n = 10 41 | a = [randint(0, n ** 2 - 1) for _ in range(n)] 42 | a = sort(a) 43 | print(a) 44 | -------------------------------------------------------------------------------- /sorting/labs/lab3/zad4.py: -------------------------------------------------------------------------------- 1 | """ 2 | Mamy tablicę T, w której znajduje się `n` elementów. 3 | Szukamy elementów `x` i `y` takich, że różnica między nimi jest największa ze wszystkich par, 4 | oraz nie istnieje element `z` taki, że `x` < `z` < `y`. 5 | 6 | Na przykład 7 | T = [1, 4, 2, 8, 3, 9, 2] 8 | max_diff(T) -> (4, 8) 9 | 10 | _Dlaczego? W tablicy nie ma elementów [5, 6, 7], więc para (4, 8) jest prawidłowa, 11 | i jednocześnie ich różnica jest największa ze wszystkich takich prawidłowych par. 12 | 13 | Para (4, 9) nie jest prawidłowa, ponieważ w tablicy jest 8._ 14 | """ 15 | 16 | 17 | def max_diff(T): 18 | n = len(T) 19 | buckets = [[float("inf"), float("-inf")] for _ in range(n)] 20 | 21 | low, high = min(T), max(T) 22 | span = high - low 23 | 24 | for value in T: 25 | bucket_index = int(((value - low) / span) * (n - 1)) 26 | buckets[bucket_index][0] = min(buckets[bucket_index][0], value) 27 | buckets[bucket_index][1] = max(buckets[bucket_index][1], value) 28 | 29 | result_low = 0 30 | result_high = 0 31 | prev_low = buckets[0][0] 32 | 33 | for i in range(1, n): 34 | # Skip empty spans. 35 | if buckets[i][0] == float("inf"): 36 | continue 37 | 38 | if result_high - result_low < buckets[i][1] - prev_low: 39 | result_low = prev_low 40 | result_high = buckets[i][1] 41 | 42 | prev_low = buckets[i][0] 43 | 44 | return result_low, result_high 45 | 46 | 47 | if __name__ == "__main__": 48 | a = [1, 4, 2, 8, 3, 9, 2] 49 | print(max_diff(a)) 50 | -------------------------------------------------------------------------------- /sorting/labs/lab3/zad5.py: -------------------------------------------------------------------------------- 1 | """ 2 | Pokrycie kolorów. 3 | 4 | Mamy tablicę T, z n elementami. 5 | 6 | Wartości tablicy to kolory od 0 do k - 1. Szukamy najkrótszego przedziału od i do j takiego, że elementy w tym przedziale 7 | zawierają wszystkie kolory, w dowolnej kolejności. 8 | """ 9 | 10 | from random import randint 11 | 12 | 13 | def color_coverage(T, k): 14 | n = len(T) 15 | 16 | counts = [0] * k 17 | unique = 0 18 | 19 | start = 0 20 | end = 0 21 | 22 | min_window = float("inf") 23 | 24 | while end < n: 25 | # Increment count of current color. 26 | counts[T[end]] += 1 27 | 28 | # If it's first color of this type in our window, 29 | # inc unique count. 30 | if counts[T[end]] == 1: 31 | unique += 1 32 | 33 | # Inc end index. 34 | end += 1 35 | 36 | # Wile we have k unique colors. 37 | while unique == k: 38 | # Take min of old min and current window. 39 | min_window = min(min_window, end - start) 40 | 41 | # Dec count of color coming out of current window. 42 | counts[T[start]] -= 1 43 | 44 | # If count of the color fell down to 0, 45 | # dec unique. 46 | if counts[T[start]] == 0: 47 | unique -= 1 48 | 49 | # Inc start index. 50 | start += 1 51 | 52 | return min_window 53 | 54 | 55 | if __name__ == "__main__": 56 | k = 5 57 | T = [randint(0, k - 1) for _ in range(10)] 58 | print(T) 59 | print(color_coverage(T, k)) 60 | -------------------------------------------------------------------------------- /sorting/labs/lab3/zad6.py: -------------------------------------------------------------------------------- 1 | """ 2 | Przedziały jednostajne. 3 | 4 | Chcemy posortować tablicę T. 5 | 6 | Tablica zawiera N liczb. Mamy k przedziałów. [a1, b1), [a2, b2), [ak, bk). 7 | Każdy z tych przedziałów ma prawdopodobieństwo c1 c2 ck. 8 | Granice przedziałów to liczby naturalne. 9 | 10 | Wybieramy przedział, 11 | 12 | Tworzymy tablice: 13 | 1) Losujemy przedzial zgodnie z ich prawdopodobienstwami 14 | 2) Wybieramy element z przedzialu 15 | i tak k razy 16 | 17 | Idea: 18 | Bierzemy kubelek, 19 | kazdy kubelek dane rozlozone jednostajnie 20 | robimy bucket sort 21 | """ 22 | -------------------------------------------------------------------------------- /sorting/labs/lab3/zad7.py: -------------------------------------------------------------------------------- 1 | """ 2 | Znaleźć k-ty element 3 | """ 4 | from random import randint 5 | 6 | 7 | # Lomuto's partition scheme. 8 | def partition(T, l, r): 9 | pivot = T[r] 10 | i = l - 1 11 | 12 | for j in range(l, r): 13 | if T[j] < pivot: 14 | i += 1 15 | T[i], T[j] = T[j], T[i] 16 | 17 | T[i + 1], T[r] = T[r], T[i + 1] 18 | return i + 1 19 | 20 | 21 | def quickselect(T, l, r, k): 22 | if l == r: 23 | # In this case there might not be kth element. 24 | return T[l] 25 | 26 | pivot = partition(T, l, r) 27 | 28 | if pivot == k: 29 | return T[pivot] 30 | elif k > pivot: 31 | return quickselect(T, pivot + 1, r, k) 32 | else: 33 | return quickselect(T, l, pivot - 1, k) 34 | 35 | 36 | for _ in range(20): 37 | # arrange 38 | k = randint(0, 19) 39 | a = [randint(10, 99) for _ in range(20)] 40 | expected = sorted(a)[k] 41 | 42 | # test 43 | result = quickselect(a, 0, len(a) - 1, k) 44 | 45 | # assert 46 | assert expected == result 47 | print(expected, "=", result, "OK") 48 | -------------------------------------------------------------------------------- /sorting/problems/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/senicko/dsa/34c15e1037e66f180dc48f34dc51eedb27121137/sorting/problems/.gitignore -------------------------------------------------------------------------------- /sorting/problems/algorithm_design_manual/4.31.py: -------------------------------------------------------------------------------- 1 | # Binary search for an arbitrary value in array of integers 2 | # shifted by k positions right. 3 | def k_binary_search(a, k, x): 4 | n = len(a) 5 | 6 | l = k 7 | r = n - 1 + k 8 | 9 | while l <= r: 10 | mid = l + (r - l) // 2 11 | 12 | if a[mid % n] == x: 13 | return mid % n 14 | elif a[mid % n] < x: 15 | l = mid + 1 16 | else: 17 | r = mid - 1 18 | 19 | return -1 20 | 21 | 22 | # Searches for max element in sorted array with 23 | # values shifted by k indexes in O(nlogn) time. 24 | def binary_max(a): 25 | n = len(a) 26 | 27 | l = 0 28 | r = n - 1 29 | 30 | while l <= r: 31 | mid = l + (r - l) // 2 32 | 33 | if a[mid] > a[(mid + 1) % n]: 34 | return a[mid] 35 | 36 | elif a[mid] >= a[l]: 37 | l = mid + 1 38 | else: 39 | r = mid - 1 40 | 41 | return None 42 | 43 | 44 | if __name__ == "__main__": 45 | a = [29, 35, 42, 5, 15, 27] 46 | print(binary_max(a)) 47 | -------------------------------------------------------------------------------- /sorting/problems/algorithm_design_manual/4.5.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | 3 | 4 | # Solution with counting sort. 5 | # Works great for small range of values. 6 | def solve_counting_sort(a): 7 | n = len(a) 8 | max_value = max(a) 9 | min_value = min(a) 10 | buckets = [0 for _ in range(max_value)] 11 | 12 | for val in a: 13 | buckets[val - min_value] += 1 14 | 15 | max_value = a[0] 16 | 17 | for val in range(n): 18 | if buckets[max_value - min_value] < buckets[val - min_value]: 19 | max_value = val 20 | 21 | return max_value 22 | 23 | 24 | def partition(a, l, r): 25 | # Select random pivot. 26 | pivot_index = randint(l, r) 27 | a[pivot_index], a[r] = a[r], a[pivot_index] 28 | 29 | # Continue standard Lomuto partition scheme. 30 | pivot = a[r] 31 | i = l - 1 32 | 33 | for j in range(l, r): 34 | if a[j] < pivot: 35 | i += 1 36 | a[i], a[j] = a[j], a[i] 37 | 38 | a[i + 1], a[r] = a[r], a[i + 1] 39 | return i + 1 40 | 41 | 42 | def quicksort(a, l, r): 43 | while l < r: 44 | pivot = partition(a, l, r) 45 | 46 | if pivot - l < r - pivot: 47 | quicksort(a, l, pivot - 1) 48 | l = pivot + 1 49 | else: 50 | quicksort(a, pivot + 1, r) 51 | r = pivot - 1 52 | 53 | 54 | def solve_sorting(a): 55 | n = len(a) 56 | 57 | # Sorting takes O(nlogn) time. 58 | quicksort(a, 0, n - 1) 59 | 60 | mode = 0 61 | max_count = 0 62 | current_count = 1 63 | 64 | # This scan takes O(n) time. 65 | for i in range(1, n): 66 | if a[i] != a[i - 1]: 67 | if current_count > max_count: 68 | mode = a[i - 1] 69 | max_count = current_count 70 | 71 | current_count = 1 72 | else: 73 | current_count += 1 74 | 75 | return mode 76 | 77 | 78 | if __name__ == "__main__": 79 | a = [4, 6, 2, 4, 3, 1] 80 | assert solve_counting_sort(a) == solve_sorting(a) 81 | -------------------------------------------------------------------------------- /sorting/problems/algorithm_design_manual/4.6.py: -------------------------------------------------------------------------------- 1 | left = lambda i: 2 * i + 1 2 | right = lambda i: 2 * i + 2 3 | parent = lambda i: (i - 1) // 2 4 | 5 | 6 | def heapify(a, n, i): 7 | l = left(i) 8 | r = right(i) 9 | max_index = i 10 | 11 | if l < n and a[l] > a[max_index]: 12 | max_index = l 13 | 14 | if r < n and a[r] > a[max_index]: 15 | max_index = r 16 | 17 | if max_index != i: 18 | a[max_index], a[i] = a[i], a[max_index] 19 | heapify(a, n, max_index) 20 | 21 | 22 | def buildheap(a): 23 | n = len(a) 24 | 25 | for i in range(parent(n - 1), -1, -1): 26 | heapify(a, n, i) 27 | 28 | 29 | def heapsort(a): 30 | n = len(a) 31 | buildheap(a) 32 | 33 | for i in range(n - 1, 0, -1): 34 | a[i], a[0] = a[0], a[i] 35 | heapify(a, i, 0) 36 | 37 | 38 | def binary_search(a, x): 39 | n = len(a) 40 | l = 0 41 | r = n - 1 42 | 43 | while l <= r: 44 | mid = l + (r - l) // 2 45 | 46 | if a[mid] == x: 47 | return True 48 | if a[mid] < x: 49 | l = mid + 1 50 | else: 51 | r = mid - 1 52 | 53 | return False 54 | 55 | 56 | def find_pairs(a, b, x): 57 | # Sorting takes O(nlogn) 58 | heapsort(a) 59 | heapsort(b) 60 | 61 | # Linear scan with binary search, O(nlogn) 62 | for v in a: 63 | if binary_search(b, x - v): 64 | return (v, x - v) 65 | 66 | return None 67 | 68 | 69 | if __name__ == "__main__": 70 | print(find_pairs([1, 2, 3, 4], [1, 3, 4, 6], 10)) 71 | -------------------------------------------------------------------------------- /sorting/problems/algorithm_design_manual/4.9.py: -------------------------------------------------------------------------------- 1 | from random import random, randint 2 | 3 | 4 | def merge(a, l, mid, r): 5 | left = a[l:mid + 1] 6 | right = a[mid + 1:r + 1] 7 | 8 | i = 0 9 | j = 0 10 | k = l 11 | 12 | while i < len(left) and j < len(right): 13 | if left[i] < right[j]: 14 | a[k] = left[i] 15 | i += 1 16 | else: 17 | a[k] = right[j] 18 | j += 1 19 | k += 1 20 | 21 | while i < len(left): 22 | a[k] = left[i] 23 | i += 1 24 | k += 1 25 | 26 | while j < len(right): 27 | a[k] = right[j] 28 | j += 1 29 | k += 1 30 | 31 | 32 | def merge_sort(a, l, r): 33 | if l < r: 34 | mid = l + (r - l) // 2 35 | merge_sort(a, l, mid) 36 | merge_sort(a, mid + 1, r) 37 | merge(a, l, mid, r) 38 | 39 | 40 | def right_bound(a, x): 41 | n = len(a) 42 | 43 | l = 0 44 | r = n - 1 45 | 46 | while l <= r: 47 | mid = l + (r - l) // 2 48 | 49 | if a[mid] <= x: 50 | l = mid + 1 51 | else: 52 | r = mid - 1 53 | 54 | return l 55 | 56 | 57 | def union(a, b): 58 | n = len(a) 59 | 60 | merge_sort(a, 0, n - 1) 61 | merge_sort(b, 0, n - 1) 62 | union = [] 63 | 64 | i = 0 65 | j = 0 66 | 67 | while i < n and j < n: 68 | if a[i] == b[j]: 69 | union.append(a[i]) 70 | i = right_bound(a, a[i]) 71 | j = right_bound(b, b[j]) 72 | elif a[i] < b[j]: 73 | union.append(a[i]) 74 | i = right_bound(a, a[i]) 75 | else: 76 | union.append(b[j]) 77 | j = right_bound(b, b[j]) 78 | 79 | while i < n: 80 | union.append(a[i]) 81 | i = right_bound(a, a[i]) 82 | 83 | while j < n: 84 | union.append(b[j]) 85 | j = right_bound(b, b[j]) 86 | 87 | return union 88 | 89 | 90 | if __name__ == "__main__": 91 | a = [randint(1, 10) for _ in range(20)] 92 | b = [randint(30, 40) for _ in range(20)] 93 | print(union(a, b)) 94 | -------------------------------------------------------------------------------- /sorting/problems/algorithm_design_manual/REDME.md: -------------------------------------------------------------------------------- 1 | Solutions to some problems from Steven S. Skiena's "The Algorithm Design Manual" 2 | -------------------------------------------------------------------------------- /sorting/problems/cesar.py: -------------------------------------------------------------------------------- 1 | def cesar(s): 2 | n = len(s) 3 | res = 1 4 | 5 | for i in range(n): 6 | for j in range(n): 7 | l = i - j 8 | r = i + j 9 | 10 | # Expand left and right. 11 | if l < 0 or r >= n or s[l] != s[r]: 12 | break 13 | 14 | res = max(res, r - l + 1) 15 | 16 | return res 17 | -------------------------------------------------------------------------------- /sorting/problems/depth.py: -------------------------------------------------------------------------------- 1 | LEFT = 0 2 | RIGHT = 1 3 | COUNT = 2 4 | 5 | left = lambda i: 2 * i + 1 6 | right = lambda i: 2 * i + 2 7 | parent = lambda i: (i - 1) // 2 8 | 9 | 10 | def merge(L, l, mid, r): 11 | left = L[l:mid + 1] 12 | right = L[mid + 1: r + 1] 13 | 14 | i = 0 15 | j = 0 16 | k = l 17 | 18 | # We want to sort ranges in descending order by range start. 19 | # If two ranges have the same start, we want to sort them ascending by range end. 20 | while i < len(left) and j < len(right): 21 | if left[i][LEFT] > right[j][LEFT] or \ 22 | (left[i][LEFT] == right[j][LEFT] and left[i][RIGHT] < right[j][RIGHT]): 23 | L[k] = left[i] 24 | i += 1 25 | else: 26 | L[k] = right[j] 27 | j += 1 28 | k += 1 29 | 30 | while i < len(left): 31 | L[k] = left[i] 32 | i += 1 33 | k += 1 34 | 35 | while j < len(right): 36 | L[k] = right[j] 37 | j += 1 38 | k += 1 39 | 40 | 41 | def mergesort(L, l, r): 42 | if l < r: 43 | mid = l + (r - l) // 2 44 | mergesort(L, l, mid) 45 | mergesort(L, mid + 1, r) 46 | merge(L, l, mid, r) 47 | 48 | 49 | def max_rank_merge(L, l, mid, r): 50 | left = L[l:mid + 1] 51 | right = L[mid + 1: r + 1] 52 | 53 | i = 0 54 | j = 0 55 | k = l 56 | 57 | counter = 0 58 | 59 | while i < len(left) and j < len(right): 60 | if left[i][RIGHT] <= right[j][RIGHT]: 61 | L[k] = left[i] 62 | counter += 1 63 | i += 1 64 | else: 65 | L[k] = right[j] 66 | right[j][COUNT] += counter 67 | j += 1 68 | k += 1 69 | 70 | while i < len(left): 71 | L[k] = left[i] 72 | i += 1 73 | k += 1 74 | 75 | while j < len(right): 76 | L[k] = right[j] 77 | right[j][COUNT] += counter 78 | j += 1 79 | k += 1 80 | 81 | 82 | def max_rank(L, l, r): 83 | if l < r: 84 | mid = l + (r - l) // 2 85 | max_rank(L, l, mid) 86 | max_rank(L, mid + 1, r) 87 | max_rank_merge(L, l, mid, r) 88 | 89 | 90 | def depth(L): 91 | n = len(L) 92 | 93 | for i in range(n): 94 | L[i] = [L[i][0], L[i][1], 0] 95 | 96 | mergesort(L, 0, n - 1) 97 | max_rank(L, 0, n - 1) 98 | 99 | return max(L, key=lambda r: r[COUNT])[COUNT] 100 | 101 | 102 | if __name__ == "__main__": 103 | L = [[1, 6], 104 | [5, 6], 105 | [2, 5], 106 | [8, 9], 107 | [1, 6]] 108 | 109 | print(depth(L)) 110 | -------------------------------------------------------------------------------- /sorting/problems/dominance.py: -------------------------------------------------------------------------------- 1 | def dominance(P): 2 | n = len(P) 3 | 4 | # Each coordinate will store number of points 5 | # with coordinate greater or equal to it. 6 | x = [0] * (n + 1) 7 | y = [0] * (n + 1) 8 | 9 | for p in P: 10 | x[p[0]] += 1 11 | y[p[1]] += 1 12 | 13 | for i in range(n - 1, -1, -1): 14 | x[i] += x[i + 1] 15 | y[i] += y[i + 1] 16 | 17 | min_not_dominated = float("inf") 18 | 19 | for p in P: 20 | # This counts |x| + |y| + |x ∩ y| Every point with 21 | # both x and y greater equal will be counted twice. This 22 | # is not a problem however as the point which dominates 23 | # most points can't have any point in area |x ∩ y|. 24 | not_dominated = x[p[0]] + y[p[1]] 25 | min_not_dominated = min(min_not_dominated, not_dominated) 26 | 27 | # The point dominating most points counted itself twice, 28 | # because of adding |x ∩ y|, So we need to subtract 1. 29 | return n - (min_not_dominated + 1) 30 | -------------------------------------------------------------------------------- /sorting/problems/kolokwia/kol1.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | from math import inf 3 | 4 | 5 | def min_max(T, l, r): 6 | found_min = inf 7 | found_max = -inf 8 | 9 | for i in range(l, r + 1, 2): 10 | j = i + 1 11 | 12 | if T[i] <= T[j]: 13 | if T[i] < found_min: 14 | found_min = T[i] 15 | if T[j] > found_max: 16 | found_max = T[j] 17 | else: 18 | if T[i] > found_max: 19 | found_max = T[i] 20 | if T[j] < found_min: 21 | found_min = T[j] 22 | 23 | return found_min, found_max 24 | 25 | 26 | # O(n) 27 | def partition(T, l, r): 28 | pivot_index = randint(l, r) 29 | T[pivot_index], T[r] = T[r], T[pivot_index] 30 | 31 | i = l - 1 32 | 33 | for j in range(l, r): 34 | if T[j] <= T[r]: 35 | i += 1 36 | T[i], T[j] = T[j], T[i] 37 | 38 | T[i + 1], T[r] = T[r], T[i + 1] 39 | 40 | return i + 1 41 | 42 | 43 | # O(n) 44 | def quickselect(T, l, r, k): 45 | pivot = partition(T, l, r) 46 | 47 | if pivot == k: 48 | return pivot 49 | if pivot < k: 50 | return quickselect(T, pivot + 1, r, k) 51 | else: 52 | return quickselect(T, l, pivot - 1, k) 53 | 54 | 55 | # O(n^2), ale dla bucket sorta z rozkładem jednostajnym O(1) 56 | def insertion_sort(T): 57 | n = len(T) 58 | 59 | for i in range(n): 60 | key = T[i] 61 | j = i - 1 62 | 63 | while j >= 0 and T[j] > key: 64 | T[j + 1] = T[j] 65 | j -= 1 66 | 67 | T[j + 1] = key 68 | 69 | 70 | # O(n) dla tablicy z wartościami z rozkładu jednostajnego. 71 | def bucket_sort(T, l, r): 72 | n = r - l + 1 73 | buckets = [[] for _ in range(n)] 74 | 75 | min_v, max_v = min_max(T, l, r) 76 | span = max_v - min_v 77 | 78 | for i in range(l, r + 1): 79 | bucket_index = int(((T[i] - min_v) / span) * (n - 1)) 80 | buckets[bucket_index].append(T[i]) 81 | 82 | k = l 83 | 84 | for bucket in buckets: 85 | insertion_sort(bucket) 86 | 87 | for i in range(len(bucket)): 88 | T[k] = bucket[i] 89 | k += 1 90 | 91 | 92 | def ogrodzenie(M, D, T): 93 | n = len(T) 94 | 95 | mid = quickselect(T, 0, n - 1, n // 2) 96 | bucket_sort(T, 0, mid - 1) 97 | bucket_sort(T, mid, n - 1) 98 | 99 | pairs_counter = 0 100 | for i in range(1, n): 101 | if T[i] - T[i - 1] >= D: 102 | pairs_counter += 1 103 | 104 | return pairs_counter 105 | -------------------------------------------------------------------------------- /sorting/problems/ksum.py: -------------------------------------------------------------------------------- 1 | from sorting.algorithms.bucket_sort import bucket_sort 2 | 3 | MAX_HEAP = "max_heap" 4 | MIN_HEAP = "min_heap" 5 | 6 | 7 | # Node is a class that tracks value's heap and index. 8 | # It allows us to find in which heap value resides and 9 | # at what index it is in O(1) time. 10 | class Value: 11 | def __init__(self, value, index=None, heap=None): 12 | self.value = value 13 | self.index = index 14 | self.heap = heap 15 | 16 | def __neg__(self): 17 | self.value *= -1 18 | return self 19 | 20 | def swap_index(self, other): 21 | self.index, other.index = other.index, self.index 22 | 23 | 24 | # MinHeap implementation that manages Nodes and updates their 25 | # indexes during heap operations. 26 | class MinHeap: 27 | def __init__(self, n, id=None): 28 | self.heap = [None] * n 29 | self.size = 0 30 | self.id = id 31 | 32 | left = lambda i: 2 * i + 1 33 | right = lambda i: 2 * i + 2 34 | parent = lambda i: (i - 1) // 2 35 | 36 | def top(self): 37 | return self.heap[0].value 38 | 39 | def heapify_down(self, i): 40 | l = MinHeap.left(i) 41 | r = MinHeap.right(i) 42 | min_index = i 43 | 44 | if l < self.size and self.heap[l].value < self.heap[min_index].value: 45 | min_index = l 46 | 47 | if r < self.size and self.heap[r].value < self.heap[min_index].value: 48 | min_index = r 49 | 50 | if min_index != i: 51 | self.heap[min_index].swap_index(self.heap[i]) 52 | self.heap[min_index], self.heap[i] = self.heap[i], self.heap[min_index] 53 | self.heapify_down(min_index) 54 | 55 | def heapify_up(self, i): 56 | p = MinHeap.parent(i) 57 | 58 | if i > 0 and self.heap[p].value > self.heap[i].value: 59 | self.heap[p].swap_index(self.heap[i]) 60 | self.heap[p], self.heap[i] = self.heap[i], self.heap[p] 61 | self.heapify_up(p) 62 | 63 | def insert(self, value): 64 | value.heap = self.id 65 | value.index = self.size 66 | self.heap[self.size] = value 67 | 68 | # Make sure min heap property is maintained 69 | self.heapify_up(self.size) 70 | 71 | self.size += 1 72 | 73 | def remove(self, i): 74 | self.size -= 1 75 | 76 | removed = self.heap[i] 77 | self.heap[self.size].swap_index(self.heap[i]) 78 | self.heap[self.size], self.heap[i] = self.heap[i], self.heap[self.size] 79 | 80 | if self.size != i: 81 | # Make sure min heap property is maintained 82 | self.heapify_up(i) 83 | self.heapify_down(i) 84 | 85 | return removed 86 | 87 | 88 | def ksum(T, k, p): 89 | n = len(T) 90 | 91 | for i in range(n): 92 | T[i] = Value(T[i]) 93 | 94 | min_heap = MinHeap(n, MIN_HEAP) 95 | max_heap = MinHeap(n, MAX_HEAP) 96 | 97 | # Load first k values to min_heap. 98 | for i in range(k): 99 | min_heap.insert(T[i]) 100 | 101 | # Load the rest of p first values to min_heap or max_heap 102 | # maintaining the property that min_heap keeps the k largest 103 | # values in the window. 104 | for i in range(k, p): 105 | if T[i].value > min_heap.top(): 106 | max_heap.insert(-min_heap.remove(0)) 107 | min_heap.insert(T[i]) 108 | else: 109 | max_heap.insert(-T[i]) 110 | 111 | total = 0 112 | 113 | for i in range(p, n): 114 | # Add the kth largest element from previous window to total sum. 115 | total += min_heap.top() 116 | 117 | # Get outgoing and incoming nodes. 118 | outgoing = T[i - p] 119 | incoming = T[i] 120 | 121 | # Remove outgoing node from its heap. 122 | # Insert incoming node to appropriate heap. 123 | 124 | min_heap.remove(outgoing.index) if outgoing.heap == MIN_HEAP else max_heap.remove(outgoing.index) 125 | min_heap.insert(incoming) 126 | 127 | # It's possible that after removing outgoing node 128 | # max_heap's top belongs to the kth largest ones. 129 | 130 | if max_heap.size > 0: 131 | min_heap.insert(-max_heap.remove(0)) 132 | 133 | # Balance min_heap so that it contains exactly k values. 134 | while min_heap.size > k: 135 | max_heap.insert(-min_heap.remove(0)) 136 | 137 | return total + min_heap.top() 138 | -------------------------------------------------------------------------------- /sorting/problems/maxrank.py: -------------------------------------------------------------------------------- 1 | """ 2 | At every merge level we can keep track of number of "inversions" (defined in problem desc). 3 | Every merge level keeps track of its own inversions. 4 | 5 | At every call to merge inversions in left and right sub arrays were already 6 | counted in the deeper level of. 7 | """ 8 | 9 | 10 | def merge(A, l, mid, r): 11 | left = A[l:mid + 1] 12 | right = A[mid + 1:r + 1] 13 | 14 | i = 0 15 | j = 0 16 | k = l 17 | counter = 0 18 | 19 | while i < len(left) and j < len(right): 20 | if left[i][0] < right[j][0]: 21 | A[k] = left[i] 22 | i += 1 23 | # Inc less elements counter 24 | counter += 1 25 | else: 26 | A[k] = right[j] 27 | # Inc number of inversions 28 | right[j][1] += counter 29 | j += 1 30 | k += 1 31 | 32 | while i < len(left): 33 | A[k] = left[i] 34 | i += 1 35 | k += 1 36 | 37 | while j < len(right): 38 | A[k] = right[j] 39 | # Inc number of inversions 40 | right[j][1] += counter 41 | j += 1 42 | k += 1 43 | 44 | 45 | def merge_sort(A, l, r): 46 | if l < r: 47 | mid = (l + r) // 2 48 | merge_sort(A, l, mid) 49 | merge_sort(A, mid + 1, r) 50 | merge(A, l, mid, r) 51 | 52 | 53 | def maxrank(T): 54 | n = len(T) 55 | 56 | # Keep track of number of inversion for every element. 57 | for i in range(n): 58 | T[i] = [T[i], 0] 59 | 60 | merge_sort(T, 0, n - 1) 61 | return max(T, key=lambda x: x[1])[1] 62 | -------------------------------------------------------------------------------- /sorting/problems/median.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | 3 | N = 0 4 | 5 | 6 | # Lomuto's partition scheme. 7 | def partition(T, l, r): 8 | pivot = T[r] 9 | i = l - 1 10 | 11 | for j in range(l, r): 12 | if T[j] <= pivot: 13 | i += 1 14 | T[j], T[i] = T[i], T[j] 15 | 16 | T[i + 1], T[r] = T[r], T[i + 1] 17 | return i + 1 18 | 19 | 20 | def select(T, l, r, k): 21 | if l >= r: 22 | return -1 23 | 24 | pivot = partition(T, l, r) 25 | 26 | if pivot == k: 27 | return 28 | elif pivot < k: 29 | return select(T, pivot + 1, r, k) 30 | else: 31 | return select(T, l, pivot - 1, k) 32 | 33 | 34 | def median(T): 35 | global N 36 | N = len(T) 37 | 38 | tmp = [0] * (N * N) 39 | 40 | for i in range(N): 41 | for j in range(N): 42 | tmp[N * i + j] = T[i][j] 43 | 44 | start = (N * N - N) // 2 45 | end = start + N 46 | 47 | for i in range(start, end): 48 | select(tmp, 0, N * N - 1, i) 49 | 50 | i = 0 51 | 52 | for x in range(0, N - 1): 53 | for y in range(x + 1, N): 54 | T[y][x] = tmp[i] 55 | T[x][y] = tmp[end + i] 56 | i += 1 57 | 58 | for y in range(N): 59 | T[y][y] = tmp[i] 60 | i += 1 61 | 62 | 63 | if __name__ == "__main__": 64 | T = [ 65 | [12, 25, 38, 41, 54, 67, 72, 89], 66 | [15, 28, 39, 42, 55, 68, 73, 90], 67 | [18, 31, 40, 45, 56, 69, 74, 91], 68 | [21, 34, 43, 46, 57, 70, 75, 92], 69 | [24, 37, 44, 49, 58, 71, 76, 93], 70 | [27, 48, 50, 52, 60, 77, 80, 95], 71 | [30, 51, 53, 62, 78, 81, 85, 97], 72 | [33, 59, 61, 66, 79, 82, 88, 99] 73 | ] 74 | 75 | median(T) 76 | print(*T, sep="\n") 77 | -------------------------------------------------------------------------------- /sorting/problems/offline/.gitignore: -------------------------------------------------------------------------------- 1 | testy.py 2 | *test_spec.py 3 | *testy.py -------------------------------------------------------------------------------- /sorting/problems/offline/offline1/zad1_v1.py: -------------------------------------------------------------------------------- 1 | from zad1testy import runtests 2 | 3 | """ 4 | Złożoność czasowa: O(NlogN) 5 | (1) O(N) 6 | (2) O(NlogN) 7 | (3) O(n) 8 | """ 9 | 10 | left = lambda i: 2 * i + 1 11 | right = lambda i: 2 * i + 2 12 | parent = lambda i: (i - 1) // 2 13 | 14 | 15 | def heapify(a, n, i): 16 | l = left(i) 17 | r = right(i) 18 | max_index = i 19 | 20 | if l < n and a[l] > a[max_index]: 21 | max_index = l 22 | 23 | if r < n and a[r] > a[max_index]: 24 | max_index = r 25 | 26 | if max_index != i: 27 | a[max_index], a[i] = a[i], a[max_index] 28 | heapify(a, n, max_index) 29 | 30 | 31 | def build_heap(a): 32 | n = len(a) 33 | for i in range(parent(n - 1), -1, -1): 34 | heapify(a, n, i) 35 | 36 | 37 | def heapsort(a): 38 | n = len(a) 39 | build_heap(a) 40 | 41 | for i in range(n - 1, 0, -1): 42 | a[i], a[0] = a[0], a[i] 43 | heapify(a, i, 0) 44 | 45 | 46 | def strong_string(T): 47 | n = len(T) 48 | 49 | # (1) 50 | for i in range(n): 51 | rev = T[i][::-1] 52 | if rev < T[i]: 53 | T[i] = rev 54 | 55 | # (2) 56 | heapsort(T) 57 | 58 | # (3) 59 | max_strength = 0 60 | current_strength = 1 61 | prev = T[0] 62 | 63 | for i in range(1, n): 64 | if T[i] == prev: 65 | current_strength += 1 66 | else: 67 | max_strength = max(max_strength, current_strength) 68 | current_strength = 1 69 | prev = T[i] 70 | 71 | return max(max_strength, current_strength) 72 | 73 | 74 | # Odkomentuj by uruchomic duze testy 75 | runtests(strong_string, all_tests=True) 76 | 77 | # Zakomentuj gdy uruchamiasz duze testy 78 | # runtests(strong_string, all_tests=False) 79 | -------------------------------------------------------------------------------- /sorting/problems/offline/offline1/zad1_v2.py: -------------------------------------------------------------------------------- 1 | from zad1testy import runtests 2 | 3 | """ 4 | Złożoność czasowa: O(nk) 5 | 6 | PS. Radix sort działa słabo dla bardzo długich napisów, dlatego 7 | Testy sprawdzające wydajność dla takich przypadków mielą i mają 8 | problem. 9 | """ 10 | 11 | 12 | def bucket_sort(T, pos): 13 | buckets = [[] for _ in range(26)] 14 | 15 | for word in T: 16 | idx = ord(word[pos]) - ord('a') if pos < len(word) else 0 17 | buckets[idx].append(word) 18 | 19 | i = 0 20 | 21 | for bucket in buckets: 22 | for j in range(len(bucket)): 23 | T[i] = bucket[j] 24 | i += 1 25 | 26 | 27 | def radix_sort(T): 28 | k = -1 29 | for word in T: 30 | k = max(k, len(word)) 31 | 32 | for i in range(k): 33 | bucket_sort(T, i) 34 | 35 | 36 | def strong_string(T): 37 | n = len(T) 38 | 39 | for i in range(n): 40 | rev = T[i][::-1] 41 | if rev < T[i]: 42 | T[i] = rev 43 | 44 | radix_sort(T) 45 | 46 | max_strength = 0 47 | current_strength = 1 48 | prev = T[0] 49 | 50 | for i in range(1, n): 51 | if T[i] == prev: 52 | current_strength += 1 53 | else: 54 | max_strength = max(max_strength, current_strength) 55 | current_strength = 1 56 | prev = T[i] 57 | 58 | return max(max_strength, current_strength) 59 | 60 | 61 | # Odkomentuj by uruchomic duze testy 62 | runtests(strong_string, all_tests=True) 63 | 64 | # Zakomentuj gdy uruchamiasz duze testy 65 | # runtests(strong_string, all_tests=False) 66 | -------------------------------------------------------------------------------- /sorting/problems/offline/offline1/zad1_v3.py: -------------------------------------------------------------------------------- 1 | from zad1testy import runtests 2 | 3 | """ 4 | Sebastian Flajszer 5 | 6 | Złożoność czasowa: O(N + nlogn) 7 | (1): O(N) 8 | (2): O(nlogn) 9 | (3): O(n) 10 | 11 | Złożoność pamięciowa: O(logn) - wywołania rekurencyjne w operacjach na heap'ie 12 | 13 | Opis: 14 | 15 | (1) Podczas liniowego przejścia po tablicy dla każdego słowa wybieramy leksykograficznie mniejszą wersję z niego 16 | i jego odwrotności. Ten krok spowoduje, że słowa równoważne będą takimi samymi stringami. 17 | Następnie obliczamy hash wybranej wersji w celu zejścia ze złożoności O(k) do złożoności O(1) 18 | przy porównywaniu stringów w szybkim algorytmie sortującym Heapsort. 19 | 20 | Hash obliczany jest algorytmem "Polynomial Rolling Hash", który działa w czasie O(k). Przechodzi przez litery w przekazanym słowie 21 | i sumuje ze sobą s[i]*p^i, biorąc ostatecznie % m. p i m to stałe często wykorzystywane przy tym algorytmie, m to duża 22 | liczba pierwsza. W zasadzie obliczamy dwa hashe, z różnymi stałymi p i m, w celu zminimalizowania możliwości wystąpienia kolizji. 23 | Elementy w tablicy T zamieniamy na krotki z obliczonymi hashami. 24 | 25 | Złożoność kroku: O(N). 26 | 27 | (2) Wykorzystujemy szybkie sortowanie Heapsort do posortowania obliczonych hashy. Pozwoli nam to na liniowe przejście 28 | po tablicy w celu znalezienia ilości powtórzeń pojedynczych słów. 29 | 30 | Złożoność kroku: O(nlogn), ponieważ dzięki preprocessing'u z kroku (1) porównywanie napisów (ich hashy, którymi są liczby) zajmuje O(1). 31 | 32 | (3) Przechodzimy liniowo po posortowanej tablicy i szukamy najdłuższego podciągu o takich samych wyrazach. Możemy tak 33 | zrobić, ponieważ posortowaliśmy hash'e w podpunkcie (2). Długość najdłuższego podciągu to siła najsilniejszego napisu. 34 | 35 | Złożoność kroku: O(n) 36 | """ 37 | 38 | M1 = 10 ** 9 + 9 39 | P1 = 31 40 | 41 | M2 = 10 ** 9 + 7 42 | P2 = 37 43 | 44 | 45 | def hash(s): 46 | h1 = 0 47 | h2 = 0 48 | 49 | p1 = P1 50 | p2 = P2 51 | 52 | for c in s: 53 | h1 = (h1 + ord(c) * p1) % M1 54 | p1 = (p1 * P1) % M1 55 | 56 | h2 = (h2 + ord(c) * p2) % M2 57 | p2 = (p2 * P2) % M2 58 | 59 | return h1, h2 60 | 61 | 62 | left = lambda i: 2 * i + 1 63 | right = lambda i: 2 * i + 2 64 | parent = lambda i: (i - 1) // 2 65 | 66 | 67 | def heapify(a, n, i): 68 | l = left(i) 69 | r = right(i) 70 | max_index = i 71 | 72 | if l < n and a[l] > a[max_index]: 73 | max_index = l 74 | 75 | if r < n and a[r] > a[max_index]: 76 | max_index = r 77 | 78 | if max_index != i: 79 | a[max_index], a[i] = a[i], a[max_index] 80 | heapify(a, n, max_index) 81 | 82 | 83 | def build_heap(a): 84 | n = len(a) 85 | for i in range(parent(n - 1), -1, -1): 86 | heapify(a, n, i) 87 | 88 | 89 | def heapsort(a): 90 | n = len(a) 91 | build_heap(a) 92 | 93 | for i in range(n - 1, 0, -1): 94 | a[i], a[0] = a[0], a[i] 95 | heapify(a, i, 0) 96 | 97 | 98 | def strong_string(T): 99 | n = len(T) 100 | 101 | # (1) 102 | for i in range(n): 103 | rev = T[i][::-1] 104 | T[i] = hash(T[i] if T[i] < rev else rev) 105 | 106 | # (2) 107 | heapsort(T) 108 | 109 | # (3) 110 | max_strength = 0 111 | current_strength = 1 112 | prev = T[0] 113 | 114 | for i in range(1, n): 115 | if T[i] == prev: 116 | current_strength += 1 117 | else: 118 | max_strength = max(max_strength, current_strength) 119 | current_strength = 1 120 | prev = T[i] 121 | 122 | return max(max_strength, current_strength) 123 | 124 | 125 | # Odkomentuj by uruchomic duze testy 126 | runtests(strong_string, all_tests=True) 127 | 128 | # Zakomentuj gdy uruchamiasz duze testy 129 | # runtests(strong_string, all_tests=False) 130 | -------------------------------------------------------------------------------- /sorting/problems/offline/offline2/zad2.py: -------------------------------------------------------------------------------- 1 | """ 2 | Sebastian Flajszer 3 | 4 | Złożoność czasowa: O(nlogn) 5 | 6 | Opis: 7 | (1) Do zliczania wykorzystujemy działanie algorytmu merge sort. Inwersje możemy 8 | wykryć podczas operacji merge, kiedy element z tablicy right jest mniejszy od 9 | elementu z tablicy left. Właśnie te sytuacje zliczamy dla każdego poziomu wywołania 10 | merge. Na każdym poziomie wywołania merge zliczamy tylko i wyłącznie inwersje na tym poziomie, 11 | ponieważ inwersje na głębszych poziomach rekurencji zostały już policzone. 12 | """ 13 | 14 | from zad2testy import runtests 15 | 16 | 17 | def merge(A, l, mid, r): 18 | inversions = 0 19 | 20 | left_n = mid - l + 1 21 | right_n = r - mid 22 | 23 | left = A[l:l + left_n] 24 | right = A[mid + 1:mid + 1 + right_n] 25 | 26 | i = 0 27 | j = 0 28 | k = l 29 | 30 | while i < left_n and j < right_n: 31 | if left[i] < right[j]: 32 | A[k] = left[i] 33 | i += 1 34 | else: 35 | A[k] = right[j] 36 | j += 1 37 | inversions += left_n - i 38 | k += 1 39 | 40 | while i < left_n: 41 | A[k] = left[i] 42 | i += 1 43 | k += 1 44 | 45 | while j < right_n: 46 | A[k] = right[j] 47 | j += 1 48 | k += 1 49 | 50 | return inversions 51 | 52 | 53 | def merge_sort(A, l, r): 54 | inv = 0 55 | 56 | if l < r: 57 | mid = (l + r) // 2 58 | inv += merge_sort(A, l, mid) + merge_sort(A, mid + 1, r) + merge(A, l, mid, r) 59 | 60 | return inv 61 | 62 | 63 | def count_inversions(A): 64 | return merge_sort(A, 0, len(A) - 1) 65 | 66 | 67 | # Odkomentuj by uruchomic duze testy 68 | runtests(count_inversions, all_tests=True) 69 | 70 | # Zakomentuj gdy uruchamiasz duze testy 71 | # runtests(count_inversions, all_tests=False) 72 | -------------------------------------------------------------------------------- /sorting/problems/snow.py: -------------------------------------------------------------------------------- 1 | def merge(a, l, mid, r): 2 | left = a[l:mid + 1] 3 | right = a[mid + 1:r + 1] 4 | 5 | i = 0 6 | j = 0 7 | k = l 8 | 9 | while i < len(left) and j < len(right): 10 | if left[i] > right[j]: 11 | a[k] = left[i] 12 | i += 1 13 | else: 14 | a[k] = right[j] 15 | j += 1 16 | k += 1 17 | 18 | while i < len(left): 19 | a[k] = left[i] 20 | i += 1 21 | k += 1 22 | 23 | while j < len(right): 24 | a[k] = right[j] 25 | j += 1 26 | k += 1 27 | 28 | 29 | def merge_sort(a, l, r): 30 | if l < r: 31 | mid = (l + r) // 2 32 | merge_sort(a, l, mid) 33 | merge_sort(a, mid + 1, r) 34 | merge(a, l, mid, r) 35 | 36 | 37 | def snow(a): 38 | merge_sort(a, 0, len(a) - 1) 39 | 40 | day = 0 41 | total = 0 42 | 43 | for i in range(len(a)): 44 | if a[i] - day < 0: 45 | break 46 | 47 | total += a[i] - day 48 | day += 1 49 | 50 | return total 51 | 52 | 53 | if __name__ == "__main__": 54 | a = [6, 6, 0, 6, 7, 6, 6, 6, 6] 55 | print(snow(a)) 56 | -------------------------------------------------------------------------------- /sorting/problems/sort_chaotic.py: -------------------------------------------------------------------------------- 1 | class Node: 2 | def __init__(self, val=None): 3 | self.val = val 4 | self.next = None 5 | 6 | @staticmethod 7 | def from_list(values): 8 | dummy = Node() 9 | head = dummy 10 | 11 | for v in values: 12 | new = Node(v) 13 | head.next = new 14 | head = new 15 | 16 | return dummy.next 17 | 18 | def print(self): 19 | curr = self 20 | 21 | while curr: 22 | print(curr.val, end=" -> ") 23 | curr = curr.next 24 | 25 | print("*") 26 | 27 | 28 | left = lambda i: 2 * i + 1 29 | right = lambda i: 2 * i + 2 30 | parent = lambda i: (i - 1) // 2 31 | 32 | 33 | def heapify(heap, n, i): 34 | l = left(i) 35 | r = right(i) 36 | min_index = i 37 | 38 | if l < n and heap[l].val < heap[min_index].val: 39 | min_index = l 40 | 41 | if r < n and heap[r].val < heap[min_index].val: 42 | min_index = r 43 | 44 | if min_index != i: 45 | heap[min_index], heap[i] = heap[i], heap[min_index] 46 | heapify(heap, n, min_index) 47 | 48 | 49 | def insert_heap(heap, n, node): 50 | heap[n] = node 51 | n += 1 52 | i = n - 1 53 | 54 | while i > 0 and heap[i].val < heap[parent(i)].val: 55 | heap[i], heap[parent(i)] = heap[parent(i)], heap[i] 56 | i = parent(i) 57 | 58 | return n 59 | 60 | 61 | def pop_heap(heap, n): 62 | top = heap[0] 63 | n -= 1 64 | heap[0], heap[n] = heap[n], heap[0] 65 | heapify(heap, n, 0) 66 | return top, n 67 | 68 | 69 | def sort_chaotic(p, k): 70 | dummy = Node() 71 | head = dummy 72 | 73 | heap = [None] * (k + 1) 74 | heap_size = 0 75 | 76 | def append(): 77 | nonlocal heap_size, head 78 | top, heap_size = pop_heap(heap, heap_size) 79 | top.next = None 80 | head.next = top 81 | head = head.next 82 | 83 | curr = p 84 | 85 | while curr: 86 | tmp = curr.next 87 | 88 | # Fill heap with k + 1 values 89 | if heap_size < k + 1: 90 | heap_size = insert_heap(heap, heap_size, curr) 91 | 92 | # If we have k values on the heap remove the 93 | # smallest one as it can't be anywhere else. 94 | if heap_size == k + 1: 95 | append() 96 | 97 | curr = tmp 98 | 99 | while heap_size > 0: 100 | append() 101 | 102 | return dummy.next 103 | 104 | 105 | if __name__ == "__main__": 106 | p = Node.from_list([1, 0, 3, 2, 4, 6, 5]) 107 | k = 1 108 | 109 | sorted = sort_chaotic(p, k) 110 | sorted.print() 111 | -------------------------------------------------------------------------------- /sorting/problems/staircase_step_count.py: -------------------------------------------------------------------------------- 1 | """ 2 | Mamy dwie tablice stairs[k] oraz people[l]. Tablica stairs[k] zawiera wysokosci schodkow, a people[l] zawiera maksymala 3 | wysokosc schodka, na ktora moze wejsc dana osoba. Jaka bedzie calkowita ilosc "krokow" wszystkich ludzi? 4 | """ 5 | 6 | from random import randint 7 | 8 | """ 9 | Pomysl: 10 | (1) Przechodzimy maksymalna wysokosc stopnia dla kazdej osoby. 11 | 12 | (2) Kazda osoba wchodzi po schodach tak dlugo dopoki 13 | nie trafi na stopien wyzszy niz jej maksymalny stopien. 14 | Przy kazdej "udanej" iteracji zwiekszamy licznik krokow. 15 | 16 | Zlozonosc: O(l*k) 17 | """ 18 | 19 | 20 | def stairs_distance_v1(people, stairs): 21 | total = 0 22 | 23 | # (1) 24 | for max_height in people: 25 | # (2) 26 | for step in stairs: 27 | if step > max_height: 28 | break 29 | total += 1 30 | 31 | return total 32 | 33 | 34 | """ 35 | Pomysl: 36 | (1) Tworzymy tablice z minimalna wysokoscia stopnia potrzebna na dotarcie na dany stopien. 37 | Na przyklad tablica [1, 5, 6, 6, 6, 8, 19] informuje nas, ze dotarcie na 3 stopien wymaga od 38 | osoby mozliwosci wejscia na stopien o wysokosci 6. Zauwazmy ze tablica ta jest posortowana rosnaca, 39 | poniewaz po wystapieniu wysokosci k, wszystkie wysokosci na kolejnych indeksach musza byc conajmniej k. 40 | 41 | (2) Przechodzimy przez tablice people i dla kazdej osiagalnej wysokosci wyszukujemy binarnie w tablicy 42 | utworzonej w kroku (1) indeks pierwszego schodka wyzszego od szukanej wysokosci. Na przyklad 43 | 44 | Szukamy liczby stopni o wysokosci <= 4, czyli indeksu pierwszego stopnia o wysokosci > 4. 45 | 46 | count_steps([1, 2, 3, 4, 6], 4) == 4 47 | count_steps([1, 2, 3, 3, 6], 4) == 4 48 | 49 | Wyniki otrzymane dla kazdej osoby sumujemy. 50 | 51 | Zlozonosc: O(l*logk) 52 | 53 | """ 54 | 55 | 56 | def count_steps(a, l, r, v): 57 | if l >= r: 58 | return l 59 | 60 | mid = l + (r - l) // 2 61 | 62 | if a[mid] <= v: 63 | return count_steps(a, mid + 1, r, v) 64 | else: 65 | return count_steps(a, l, mid, v) 66 | 67 | 68 | def stairs_distance_v2(people, stairs): 69 | # (1) 70 | min_height = float("-inf") 71 | reachable = [min_height := max(min_height, step) for step in stairs] 72 | 73 | k = len(stairs) 74 | total = 0 75 | 76 | # (2) 77 | for height in people: 78 | total += count_steps(reachable, 0, k, height) 79 | 80 | return total 81 | 82 | 83 | for _ in range(10): 84 | # arrange 85 | people = [randint(10, 90) for _ in range(5)] 86 | stairs = [randint(10, 50) for _ in range(10)] 87 | expected = stairs_distance_v1(people, stairs) 88 | 89 | # test 90 | got = stairs_distance_v2(people, stairs) 91 | 92 | # assert 93 | assert got == expected 94 | print("OK") 95 | -------------------------------------------------------------------------------- /sorting/problems/three_sum.py: -------------------------------------------------------------------------------- 1 | """ 2 | Three sum implementation without a Hash Map. 3 | """ 4 | 5 | from random import randint 6 | 7 | 8 | def merge(a, l, mid, r): 9 | left_n = mid - l + 1 10 | right_n = r - mid 11 | 12 | left = a[l:l + left_n] 13 | right = a[mid + 1:mid + 1 + right_n] 14 | 15 | i = 0 16 | j = 0 17 | k = l 18 | 19 | while i < left_n and j < right_n: 20 | if left[i] < right[j]: 21 | a[k] = left[i] 22 | i += 1 23 | else: 24 | a[k] = right[j] 25 | j += 1 26 | k += 1 27 | 28 | while i < left_n: 29 | a[k] = left[i] 30 | i += 1 31 | k += 1 32 | 33 | while j < right_n: 34 | a[k] = right[j] 35 | j += 1 36 | k += 1 37 | 38 | 39 | def merge_sort(a, l, r): 40 | if l < r: 41 | mid = l + (r - l) // 2 42 | merge_sort(a, l, mid) 43 | merge_sort(a, mid + 1, r) 44 | merge(a, l, mid, r) 45 | 46 | 47 | def two_sum(a, l, r, k): 48 | i = l 49 | j = r - 1 50 | 51 | while i < j: 52 | curr = a[i] + a[j] 53 | 54 | if curr == k: 55 | return True 56 | elif curr < k: 57 | i += 1 58 | else: 59 | j -= 1 60 | 61 | return False 62 | 63 | 64 | """ 65 | Pomysl: 66 | (1) Sortujemy tablice a. 67 | 68 | (2) Przechodzimy przez kazdy indeks i tablicy a. W prawej podtablicy wywolujemy two_sum dla 69 | wartosci k - a[i], ktory jednak nie musi sortowac tablicy a, poniewaz juz to zrobilismy w 70 | kroku (1), wiec dziala w czasie O(n). 71 | 72 | Zlozonosc Czasowa: 73 | Sortowanie: O(nlogn) 74 | Przetworzenie: O(n^2) 75 | Razem: O(n^2) 76 | """ 77 | 78 | 79 | def three_sum(a, k): 80 | n = len(a) 81 | 82 | # (1) 83 | merge_sort(a, 0, n - 1) 84 | 85 | for i in range(n): 86 | # (2) 87 | if two_sum(a, i + 1, n, k - a[i]): 88 | return True 89 | 90 | return False 91 | 92 | 93 | for _ in range(10): 94 | # arrange 95 | a = [randint(0, 99) for _ in range(10)] 96 | x = a[0] 97 | y = a[1] 98 | z = a[2] 99 | 100 | # test 101 | result = three_sum(a, x + y + z) 102 | 103 | # assert 104 | assert result is True 105 | print("\tOK") 106 | -------------------------------------------------------------------------------- /sorting/problems/two_sum.py: -------------------------------------------------------------------------------- 1 | """ 2 | Two sum implementation without a Hash Map. 3 | 4 | The "without a Hash Map" is important here, as with it 5 | this problem can be solved in O(n) time and O(n) space. 6 | """ 7 | 8 | from random import randint 9 | 10 | 11 | def merge(a, l, q, r): 12 | left_n = q - l + 1 13 | right_n = r - q 14 | 15 | left = a[l:l + left_n] 16 | right = a[q + 1:q + 1 + right_n] 17 | 18 | i = 0 19 | j = 0 20 | k = l 21 | 22 | while i < left_n and j < right_n: 23 | if left[i] < right[j]: 24 | a[k] = left[i] 25 | i += 1 26 | else: 27 | a[k] = right[j] 28 | j += 1 29 | k += 1 30 | 31 | while i < left_n: 32 | a[k] = left[i] 33 | i += 1 34 | k += 1 35 | 36 | while j < right_n: 37 | a[k] = right[j] 38 | j += 1 39 | k += 1 40 | 41 | 42 | def merge_sort(a, l, r): 43 | if l < r: 44 | q = (l + r) // 2 45 | merge_sort(a, l, q) 46 | merge_sort(a, q + 1, r) 47 | merge(a, l, q, r) 48 | 49 | 50 | def binary_search(a, l, r, v): 51 | if l > r: 52 | return False 53 | 54 | mid = l + (r - l) // 2 55 | 56 | if a[mid] == v: 57 | return True 58 | elif v < a[mid]: 59 | return binary_search(a, l, mid - 1, v) 60 | else: 61 | return binary_search(a, mid + 1, r, v) 62 | 63 | 64 | """ 65 | Idea: 66 | (1) Iterate through every value x. 67 | 68 | (2) Search for value y after x so that x + y == k. 69 | By starting from index after x we won't process 70 | every pair twice. 71 | 72 | Time Complexity: O(n^2) 73 | """ 74 | 75 | 76 | def two_sum_v1(a, k): 77 | n = len(a) 78 | 79 | # (1) 80 | for i in range(n): 81 | # (2) 82 | for j in range(i, n): 83 | if a[i] + a[j] == k: 84 | return True 85 | 86 | return False 87 | 88 | 89 | """ 90 | Idea: 91 | (1) Sort input array a. 92 | 93 | (2) Iterate through every value x. 94 | Search for k - x in the right sub array using binary search. 95 | We can ignore the left sub array, as we want to check every pair 96 | just once. 97 | 98 | Time Complexity: 99 | Sorting: O(nlogn) 100 | Processing: O(nlogn) 101 | """ 102 | 103 | 104 | def two_sum_v2(a, k): 105 | n = len(a) 106 | 107 | # (1) 108 | merge_sort(a, 0, n - 1) 109 | 110 | # (2) 111 | for i in range(n): 112 | remainder = k - a[i] 113 | 114 | if binary_search(a, i + 1, n - 1, remainder): 115 | return True 116 | 117 | return False 118 | 119 | 120 | """ 121 | Idea: 122 | (1) Sort input array a. 123 | 124 | (2) Maintain two pointers, left pointing to 0 and right pointing to n-1. 125 | Notice that our array is sorted, so incrementing left pointer increases 126 | the value, and similarly decrementing right decrements it. 127 | 128 | So while l