├── .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 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
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