├── README.md ├── data_structure ├── binary_tree.py ├── graph.py ├── hash_table.py ├── home_tips.py ├── linked_list.py ├── list_dict_set.py ├── queue_.py ├── stack.py └── usefull_link.md ├── graph ├── bfs_iterative.py ├── bfs_recursive.py ├── dfs_iterative.py ├── dfs_recursive.py ├── dijkstra.py ├── networkx_lib.py └── usefull_link.md ├── greedy_dynamic ├── datasets │ ├── tsp0015.txt │ ├── tsp0038.txt │ └── tsp0100.txt ├── greedy_salesman.py ├── held_carp.py ├── knap_sack.py ├── kruskals_algorithm.py ├── prim.py └── up_down_knap_sack.py ├── heap ├── example usage.py ├── heap_sort.py ├── heapq_example.py ├── heapq_priority_queue.py ├── max_heap.py ├── min_heap.py └── priority_queue.py ├── linear_random ├── miller_rabin_test.py ├── monte_carlo.py ├── simplex.py └── universal_hash.py ├── recursion ├── Sierpinski triangle.py ├── basic_drawing.py ├── factorial.py ├── fibonachi.py ├── file_generator.py ├── interpolate_Julia.mp4 ├── julia_quadratic_fractal.py ├── julia_set.gif ├── linked_list.py ├── mandelbrot.gif ├── mandelbrot_fractal.py ├── pathlib_recursion.py ├── string_lengh.py ├── turtle_recursion_tree_final.py └── usefull_link.md ├── search ├── binary_search.py ├── boyer_moore_search.py ├── hash_search.py ├── indexed_sequential_search.py ├── interpolation_search.py ├── kmp_search.py ├── linear_search.py ├── naive_search.py ├── rabin_karp_search.py └── usefull_link.md ├── sorting ├── bubble_sort.py ├── insertion_sort.py ├── merge_sort.py ├── quick_sort.py ├── radix_sort.py ├── selection_sort.py ├── shell_sort.py └── usefull_link.md └── tree ├── avl_tree.py ├── bst_tree.py ├── pre_in_post_order.py ├── usefull_link.md └── visual_tree.py /README.md: -------------------------------------------------------------------------------- 1 | # Basic-Algorithms-and-Data-Structures-Neoversity -------------------------------------------------------------------------------- /data_structure/binary_tree.py: -------------------------------------------------------------------------------- 1 | print("TreeNode") 2 | # import networkx as nx 3 | # import matplotlib.pyplot as plt 4 | # 5 | # # Визначаємо клас для вузлів дерева 6 | # class TreeNode: 7 | # def __init__(self, value): 8 | # self.value = value # Значення вузла 9 | # self.left = None # Лівий дочірній вузол 10 | # self.right = None # Правий дочірній вузол 11 | # 12 | # # Функція для вставки вузлів у бінарне дерево в рівневому порядку 13 | # def insert_level_order(arr, root, i, n): 14 | # # Базовий випадок для рекурсії 15 | # if i < n: 16 | # temp = TreeNode(arr[i]) # Створюємо новий вузол з поточним значенням масиву 17 | # root = temp 18 | # 19 | # # Вставляємо лівий дочірній вузол 20 | # root.left = insert_level_order(arr, root.left, 2 * i + 1, n) 21 | # 22 | # # Вставляємо правий дочірній вузол 23 | # root.right = insert_level_order(arr, root.right, 2 * i + 2, n) 24 | # 25 | # return root 26 | # 27 | # # Функція для додавання ребер до графу та встановлення позицій вузлів для візуалізації 28 | # def add_edges(graph, node, positions, x=0, y=0, layer=1): 29 | # if node is not None: 30 | # # Додаємо вузол до графу з його позицією 31 | # graph.add_node(node.value, pos=(x, y)) 32 | # 33 | # # Якщо існує лівий дочірній вузол, додаємо ребро та встановлюємо позицію 34 | # if node.left: 35 | # graph.add_edge(node.value, node.left.value) 36 | # left_x = x - 1 / layer # Обчислюємо нову x-позицію для лівого дочірнього вузла 37 | # add_edges(graph, node.left, positions, x=left_x, y=y - 1, layer=layer + 1) 38 | # 39 | # # Якщо існує правий дочірній вузол, додаємо ребро та встановлюємо позицію 40 | # if node.right: 41 | # graph.add_edge(node.value, node.right.value) 42 | # right_x = x + 1 / layer # Обчислюємо нову x-позицію для правого дочірнього вузла 43 | # add_edges(graph, node.right, positions, x=right_x, y=y - 1, layer=layer + 1) 44 | # 45 | # # Функція для побудови бінарного дерева 46 | # def plot_tree(root): 47 | # graph = nx.DiGraph() # Створюємо направлений граф 48 | # positions = {} # Словник для збереження позицій вузлів 49 | # add_edges(graph, root, positions) # Додаємо вузли та ребра до графу 50 | # positions = nx.get_node_attributes(graph, 'pos') # Отримуємо позиції вузлів 51 | # labels = {node: node for node in graph.nodes()} # Створюємо мітки для вузлів 52 | # 53 | # # Малюємо граф 54 | # nx.draw(graph, positions, with_labels=True, labels=labels, node_size=2000, 55 | # node_color='skyblue', font_size=16, font_weight='bold', arrows=False) 56 | # plt.title('Binary Tree Visualization') # Встановлюємо заголовок графіку 57 | # plt.show() # Відображаємо графік 58 | # 59 | # # Приклад використання 60 | # arr = [1, 8, 3, 9, 5, 6, 7] # Масив, що представляє бінарне дерево 61 | # n = len(arr) # Кількість елементів у масиві 62 | # root = insert_level_order(arr, None, 0, n) # Вставляємо вузли у бінарне дерево 63 | # 64 | # # Візуалізуємо бінарне дерево 65 | # plot_tree(root) 66 | 67 | print("binarytree") 68 | from binarytree import tree, bst, heap 69 | 70 | # Генерація випадкового бінарного дерева та повернення його кореневого вузла. 71 | my_tree = tree(height=3, is_perfect=False) # Створюємо бінарне дерево висотою 3, яке може бути неповним 72 | print(my_tree) # Виводимо згенероване бінарне дерево 73 | 74 | # Генерація випадкового бінарного дерева пошуку (BST) та повернення його кореневого вузла. 75 | my_bst = bst(height=3, is_perfect=True) # Створюємо бінарне дерево пошуку висотою 3, яке буде повним 76 | print(my_bst) # Виводимо згенероване бінарне дерево пошуку 77 | 78 | # Генерація випадкової максимальної купи (max heap) та повернення її кореневого вузла. 79 | my_heap = heap(height=3, is_max=True, is_perfect=False) # Створюємо максимальну купу висотою 3, яка може бути неповною 80 | print(my_heap) # Виводимо згенеровану максимальну купу 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /data_structure/graph.py: -------------------------------------------------------------------------------- 1 | import networkx as nx 2 | import matplotlib.pyplot as plt 3 | 4 | # Створення зразкового графу 5 | G = nx.Graph() # Ініціалізуємо пустий граф 6 | 7 | # Додавання вузлів 8 | G.add_nodes_from([1, 2, 3, 4, 5]) # Додаємо вузли до графу 9 | 10 | # Додавання ребер 11 | G.add_edges_from([(1, 5), (2, 3), (2, 5), (3, 5)]) # Додаємо ребра між вузлами 12 | 13 | # Малювання графу 14 | pos = nx.spring_layout(G) # Обчислюємо позиції для всіх вузлів для гарного відображення 15 | nx.draw(G, pos, with_labels=True, # Малюємо граф з позиціями вузлів та мітками 16 | node_size=700, # Встановлюємо розмір вузлів 17 | node_color='skyblue', # Встановлюємо колір вузлів 18 | font_size=15, # Встановлюємо розмір шрифта для міток 19 | font_color='black', # Встановлюємо колір шрифта для міток 20 | font_weight='bold', # Встановлюємо жирність шрифта для міток 21 | edge_color='gray') # Встановлюємо колір ребер 22 | 23 | # Відображення графу 24 | plt.title('Network Graph Example') # Встановлюємо заголовок для графіку 25 | plt.show() # Відображаємо графік 26 | -------------------------------------------------------------------------------- /data_structure/hash_table.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | 3 | print(hash("Yaroslav")) 4 | print(hash("Anatoliy")) 5 | print(hash("Oleksandr")) 6 | print(hash("Miko")) 7 | 8 | hash_data = hashlib.sha256("Yaroslav".encode()) 9 | print(hash_data.hexdigest()) -------------------------------------------------------------------------------- /data_structure/home_tips.py: -------------------------------------------------------------------------------- 1 | from time import sleep 2 | 3 | try: 4 | while True: 5 | print("Process file") 6 | sleep(0.2) 7 | except KeyboardInterrupt: 8 | print("Finish file processing") 9 | 10 | -------------------------------------------------------------------------------- /data_structure/linked_list.py: -------------------------------------------------------------------------------- 1 | class Node: 2 | def __init__(self, data: int=None): 3 | self.data = data # Зберігає дані вузла 4 | self.next = None # Посилання на наступний вузол 5 | 6 | class LinkedList: 7 | def __init__(self): 8 | self.head = None # Початок списку, спочатку пустий 9 | 10 | def insert_at_beginning(self, data): 11 | new_node = Node(data) # Створює новий вузол з даними 12 | new_node.next = self.head # Вказує, що новий вузол має посилатися на поточний головний вузол 13 | self.head = new_node # Робить новий вузол головним вузлом списку 14 | 15 | def insert_at_end(self, data): 16 | new_node = Node(data) # Створює новий вузол з даними 17 | if self.head is None: # Якщо список пустий 18 | self.head = new_node # Робить новий вузол головним вузлом 19 | else: 20 | cur = self.head # Починає з головного вузла 21 | while cur.next: # Проходить до кінця списку 22 | cur = cur.next 23 | cur.next = new_node # Додає новий вузол в кінці списку 24 | 25 | def insert_after(self, prev_node: Node, data): 26 | if prev_node is None: # Перевіряє, чи існує попередній вузол 27 | print("Попереднього вузла не існує.") 28 | return 29 | new_node = Node(data) # Створює новий вузол з даними 30 | new_node.next = prev_node.next # Вказує, що новий вузол має посилатися на вузол після попереднього вузла 31 | prev_node.next = new_node # Вставляє новий вузол після попереднього вузла 32 | 33 | def delete_node(self, key: int): 34 | cur = self.head # Починає з головного вузла 35 | if cur and cur.data == key: # Якщо головний вузол містить потрібні дані 36 | self.head = cur.next # Робить наступний вузол головним 37 | cur = None # Видаляє вузол 38 | return 39 | prev = None # Змінна для зберігання попереднього вузла 40 | while cur and cur.data != key: # Проходить список у пошуках потрібних даних 41 | prev = cur 42 | cur = cur.next 43 | if cur is None: # Якщо вузол з потрібними даними не знайдено 44 | return 45 | prev.next = cur.next # Видаляє вузол з потрібними даними 46 | cur = None # Звільняє пам'ять, видаляючи вузол 47 | 48 | def search_element(self, data: int) -> Node | None: 49 | cur = self.head # Починає з головного вузла 50 | while cur: # Проходить список у пошуках потрібних даних 51 | if cur.data == data: # Якщо знайдено потрібні дані 52 | return cur # Повертає вузол з потрібними даними 53 | cur = cur.next 54 | return None # Якщо вузол з потрібними даними не знайдено 55 | 56 | def print_list(self): 57 | current = self.head # Починає з головного вузла 58 | while current: # Проходить весь список 59 | print(current.data, end=" -> ") # Виводить дані вузла 60 | current = current.next # Переходить до наступного вузла 61 | print("None") # Вказує на кінець списку 62 | 63 | if __name__ == '__main__': 64 | llist = LinkedList() 65 | 66 | # Вставляємо вузли в початок 67 | llist.insert_at_beginning(5) 68 | llist.insert_at_beginning(10) 69 | llist.insert_at_beginning(13) 70 | llist.insert_at_beginning(100) 71 | llist.insert_at_beginning(-10) 72 | llist.insert_at_beginning(15) 73 | llist.print_list() 74 | 75 | # Вставляємо вузли в кінець 76 | llist.insert_at_end(20) 77 | llist.insert_at_end(25) 78 | 79 | # Друк зв'язного списку 80 | print("Зв'язний список:") 81 | llist.print_list() 82 | 83 | # Видаляємо вузол 84 | llist.delete_node(10) 85 | 86 | print("\nЗв'язний список після видалення вузла з даними 10:") 87 | llist.print_list() 88 | 89 | # Пошук елемента у зв'язному списку 90 | print("\nШукаємо елемент 10:") 91 | element = llist.search_element(10) 92 | print(element.data) if element else print("Такого значення в списку немає") 93 | 94 | print("\nШукаємо елемент 100:") 95 | element = llist.search_element(100) 96 | print(element.data) if element else print("Такого значення в списку немає") -------------------------------------------------------------------------------- /data_structure/list_dict_set.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | print("List") 4 | # list_example = [1, 2, 3, 4, 5, None, True, "Test", [1, 2]] 5 | # add_list = [1, 2, 3, 4, 99] 6 | # new_list = list_example + add_list 7 | # print(new_list) 8 | # print(list_example) 9 | # 10 | # list_example.remove(None) 11 | # print(list_example) 12 | # 13 | # list_example.reverse() 14 | # print(list_example) 15 | # 16 | # list_example.append(275424577) 17 | # print(list_example) 18 | # 19 | # list_example.pop() 20 | # print(list_example) 21 | # 22 | # print(sorted(dir(list_example), reverse=True)) 23 | 24 | print("Dict") 25 | # dict_example = {"key": "Value", (12, 3, 4, 5, 6): True, None: False} 26 | # print(dict_example) 27 | # 28 | # dict_example.update({1234: "test"}) 29 | # print(dict_example) 30 | # 31 | # dict_example[999] = "test" 32 | # print(dict_example) 33 | # 34 | # 35 | # dict_example.pop((12, 3, 4, 5, 6)) 36 | # print(dict_example) 37 | # 38 | # del dict_example[None] 39 | # print(dict_example) 40 | # 41 | # print(sorted(dir(dict_example), reverse=True)) 42 | 43 | print("Set") 44 | # set1 = {1, 2, 3, 4, 5} 45 | # set2 = {4, 5, 6, 7, 8} 46 | # 47 | # set1.add(90) 48 | # set2.pop() 49 | # print(set2) 50 | # print(set1.union(set2)) # Output: {1, 2, 3, 4, 5, 6, 7, 8} 51 | # print(set1.intersection(set2)) # Output: {4, 5} 52 | # print(set1.difference(set2)) # Output: {1, 2, 3} 53 | # print(sorted(dir(set1), reverse=True)) 54 | 55 | print("Array") 56 | # array_example = np.array([12, 10, 67, 8]) 57 | # add_array = np.array([10, 20, 30, 40]) 58 | # new_array = array_example + add_array 59 | # print(array_example, add_array, new_array, sep="\n") 60 | # print(sorted(dir(array_example), reverse=True)) -------------------------------------------------------------------------------- /data_structure/queue_.py: -------------------------------------------------------------------------------- 1 | print("Basic queue") 2 | # from queue import Queue 3 | # # Ініціалізуємо чергу з максимальним розміром 3 4 | # q = Queue(maxsize=3) 5 | # # Додаємо елементи 'a' і 'b' до черги 6 | # q.put('a') 7 | # q.put('b') 8 | # # Перевіряємо, чи черга повна (повертає True або False) 9 | # print(q.full()) # Виведе False, оскільки черга ще не заповнена 10 | # # Додаємо елемент 'c' до черги 11 | # q.put('c') 12 | # # Знову перевіряємо, чи черга повна 13 | # print(q.full()) # Виведе True, оскільки черга тепер заповнена 14 | # # Виводимо всі елементи черги як рядок, розділений пробілами 15 | # print(' '.join(list(q.queue))) # Виведе 'a b c' 16 | # # Якщо черга не повна, додаємо елемент 'd' 17 | # if not q.full(): 18 | # q.put('d') 19 | # else: 20 | # # Якщо черга повна, вилучаємо один елемент і додаємо 'd' 21 | # el = q.get() # Вилучаємо перший елемент з черги 22 | # q.put('d') # Додаємо елемент 'd' до черги 23 | # # Виводимо всі елементи черги як рядок, розділений пробілами 24 | # print(' '.join(list(q.queue))) # Виведе 'b c d' 25 | 26 | 27 | print("Bank queue") 28 | # from queue import Queue 29 | # import random 30 | # # Клас, який представляє клієнта банку 31 | # class Client: 32 | # def __init__(self, name): 33 | # self.name = name # Ім'я клієнта 34 | # self.operations = random.randint(1, 5) # Випадкова кількість операцій для клієнта 35 | # # Клас, який представляє банк 36 | # class Bank: 37 | # def __init__(self): 38 | # self.clients = Queue() # Ініціалізуємо порожню чергу для клієнтів 39 | # # Метод для додавання нового клієнта до черги 40 | # def add_client(self, client): 41 | # self.clients.put(client) # Додаємо клієнта до черги 42 | # # Метод для обслуговування клієнтів у черзі 43 | def serve_clients(self): 44 | while not self.clients.empty(): # Поки черга не пуста 45 | current_client = self.clients.get() # Отримуємо клієнта з початку черги 46 | print(f"Обслуговуємо клієнта {current_client.name} з {current_client.operations} операцій") 47 | # Тут можна додати час обслуговування та іншу логіку 48 | else: 49 | print("Всі клієнти обслуговані, черга пуста") # Повідомлення, що черга пуста 50 | 51 | # if __name__ == '__main__': 52 | # # Створюємо банк 53 | # bank = Bank() 54 | # # Додаємо клієнтів до банку 55 | # for i in range(5): 56 | # bank.add_client(Client(f"Клієнт-{i}")) # Створюємо нових клієнтів і додаємо їх до черги 57 | # # Обслуговуємо клієнтів 58 | # bank.serve_clients() # Викликаємо метод для обслуговування клієнтів 59 | 60 | print("Deque") 61 | # from collections import deque 62 | # 63 | # # Масив чисел, який ми будемо обробляти 64 | # initial_numbers = [23, 11, 12, 32, 10, 25, 65, 43, 42, 89, 100, 3] 65 | # 66 | # # Функція для обробки масиву чисел 67 | # def process_numbers(numbers: list) -> list: 68 | # dq = deque() # Ініціалізуємо порожню двосторонню чергу 69 | # dq.append(0) # Додаємо 0 до черги (початковий елемент) 70 | # for number in numbers: # Проходимося по кожному числу в масиві 71 | # if number % 2 == 0: # Перевіряємо, чи є число парним 72 | # dq.append(number) # Додаємо парне число до кінця черги 73 | # else: # Якщо число непарне 74 | # dq.appendleft(number) # Додаємо непарне число на початок черги 75 | # return dq # Перетворюємо чергу назад у список і повертаємо його 76 | # 77 | # # Виклик функції для обробки масиву чисел і збереження результату 78 | # processed_numbers = process_numbers(initial_numbers) 79 | # # Виведення результату 80 | # print(processed_numbers) 81 | # processed_numbers.popleft() 82 | # print(processed_numbers) -------------------------------------------------------------------------------- /data_structure/stack.py: -------------------------------------------------------------------------------- 1 | print("Stack") 2 | class Stack: 3 | def __init__(self): 4 | self.stack = [] 5 | 6 | # Додавання елемента до стеку 7 | def push(self, item): 8 | self.stack.append(item) 9 | 10 | # Видалення елемента зі стеку 11 | def pop(self): 12 | if len(self.stack) < 1: 13 | return None 14 | return self.stack.pop() 15 | 16 | # Перевірка, чи стек порожній 17 | def is_empty(self): 18 | return len(self.stack) == 0 19 | 20 | # Перегляд верхнього елемента стеку без його видалення 21 | def peek(self): 22 | if not self.is_empty(): 23 | return self.stack[-1] 24 | 25 | def __str__(self): 26 | if not self.is_empty(): 27 | return str(self.stack) 28 | else: 29 | return None 30 | 31 | 32 | if __name__ == '__main__': 33 | stack = Stack() 34 | 35 | for i in range(5): 36 | stack.push(i) 37 | 38 | print(stack) 39 | print(stack.peek()) 40 | print("Pop", stack.pop()) 41 | print(stack) 42 | print(stack.peek()) 43 | 44 | print("Inf Post") 45 | def infix_to_postfix(exp: str) -> str: 46 | priority = {'+': 1, "-": 1, "*": 2, "/": 2} # Оператори та їх пріоритети 47 | output = [] # Список для зберігання постфіксного виразу 48 | stack = [] # Стек для тимчасового зберігання операторів 49 | # Розділяємо вхідний інфіксний вираз на токени (операнди та оператори) 50 | for token in exp.split(): 51 | if token.isdigit(): 52 | output.append(token) # Якщо токен є операндом (число), додаємо його до виходу 53 | elif token in priority: 54 | # Якщо токен є оператором, обробляємо пріоритет операторів за допомогою стеку 55 | while stack and priority[stack[-1]] >= priority[token]: 56 | output.append(stack.pop()) # Видаляємо оператори з вищим або рівним пріоритетом 57 | stack.append(token) # Додаємо поточний оператор до стеку 58 | # Виводимо поточний стан 59 | print("{:<10} {:<30} {:<30}".format(token, " ".join(output), " ".join(stack))) 60 | # Видаляємо всі залишкові оператори зі стеку до виходу 61 | while stack: 62 | output.append(stack.pop()) 63 | print("{:<10} {:<30} {:<30}".format(token, " ".join(output), " ".join(stack))) 64 | # Повертаємо постфіксний вираз у вигляді рядка 65 | return ' '.join(output) 66 | 67 | # Вхідний інфіксний вираз 68 | expression = "3 + 4 * 2 + 1 - 3 * 2" 69 | # Перетворюємо інфіксний вираз в постфіксний 70 | postfix_expr = infix_to_postfix(expression) 71 | # Виводимо результат 72 | print(postfix_expr) # Виведе "3 4 2 * + 1 + 3 2 * -" 73 | print(eval(expression)) 74 | -------------------------------------------------------------------------------- /data_structure/usefull_link.md: -------------------------------------------------------------------------------- 1 | Last-in-first-out(LIFO): 2 | https://www.investopedia.com/terms/l/lifo.asp 3 | Stack: 4 | https://www.geeksforgeeks.org/introduction-to-stack-data-structure-and-algorithm-tutorials/ 5 | 6 | First-in-first-out(FIFO): 7 | https://www.investopedia.com/terms/f/fifo.asp 8 | Queue: 9 | https://www.geeksforgeeks.org/introduction-to-queue-data-structure-and-algorithm-tutorials/ 10 | 11 | Linked List: 12 | https://www.analyticsvidhya.com/blog/2024/02/linked-lists-in-python/ 13 | https://medium.com/@uppert83/linked-lists-in-python-implementation-88ae726cf639 14 | 15 | Hash: 16 | https://kinsta.com/blog/python-hashing/ 17 | 18 | Hash Collision: 19 | https://medium.com/@Faris_PY/hash-map-in-python-collision-load-factor-rehashing-1484ea7d4bc0 -------------------------------------------------------------------------------- /graph/bfs_iterative.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | 3 | def bfs_iterative(graph, start): 4 | # Ініціалізація порожньої множини для зберігання відвіданих вершин 5 | visited = set() 6 | # Ініціалізація черги з початковою вершиною 7 | queue = deque([start]) 8 | 9 | while queue: # Поки черга не порожня, продовжуємо обхід 10 | # Вилучаємо першу вершину з черги 11 | vertex = queue.popleft() 12 | # Перевіряємо, чи була вершина відвідана раніше 13 | if vertex not in visited: 14 | # Якщо не була відвідана, друкуємо її 15 | print(vertex, end=" ") 16 | # Додаємо вершину до множини відвіданих вершин 17 | visited.add(vertex) 18 | # Додаємо всіх невідвіданих сусідів вершини до кінця черги 19 | # Операція різниці множин вилучає вже відвідані вершини зі списку сусідів 20 | queue.extend(set(graph[vertex]) - visited) 21 | # Повертаємо множину відвіданих вершин після завершення обходу 22 | # return visited 23 | 24 | # Представлення графа за допомогою списку суміжності 25 | graph = { 26 | 'A': ['B', 'C'], 27 | 'B': ['A', 'D', 'E'], 28 | 'C': ['A', 'F'], 29 | 'D': ['B'], 30 | 'E': ['B', 'F'], 31 | 'F': ['C', 'E'] 32 | } 33 | 34 | # Запуск алгоритму BFS 35 | bfs_iterative(graph, 'A') -------------------------------------------------------------------------------- /graph/bfs_recursive.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | 3 | def bfs_recursive(graph, queue, visited=None): 4 | # Перевіряємо, чи існує множина відвіданих вершин, якщо ні, то ініціалізуємо нову 5 | if visited is None: 6 | visited = set() 7 | # Якщо черга порожня, завершуємо рекурсію 8 | if not queue: 9 | return 10 | # Вилучаємо вершину з початку черги 11 | vertex = queue.popleft() 12 | # Перевіряємо, чи відвідували раніше дану вершину 13 | if vertex not in visited: 14 | # Якщо не відвідували, друкуємо вершину 15 | print(vertex, end=" ") 16 | # Додаємо вершину до множини відвіданих вершин. 17 | visited.add(vertex) 18 | # Додаємо невідвіданих сусідів даної вершини в кінець черги. 19 | queue.extend(set(graph[vertex]) - visited) 20 | # Рекурсивний виклик функції з тією ж чергою та множиною відвіданих вершин 21 | bfs_recursive(graph, queue, visited) 22 | 23 | # Представлення графа за допомогою списку суміжності 24 | graph = { 25 | "A": ["B", "C"], 26 | "B": ["A", "D", "E"], 27 | "C": ["A", "F"], 28 | "D": ["B"], 29 | "E": ["B", "F"], 30 | "F": ["C", "E"], 31 | } 32 | 33 | # Запуск рекурсивного алгоритму BFS 34 | bfs_recursive(graph, deque(["A"])) 35 | 36 | 37 | 38 | 39 | 40 | # import matplotlib.pyplot as plt 41 | # import networkx as nx 42 | # from collections import deque 43 | # 44 | # # Функція для малювання графа з відвіданими ребрами 45 | # def draw_graph(graph, visited_edges): 46 | # G = nx.Graph() 47 | # for node, neighbors in graph.items(): 48 | # for neighbor in neighbors: 49 | # G.add_edge(node, neighbor, color='blue') 50 | # 51 | # pos = nx.spring_layout(G) 52 | # colors = [G[u][v]['color'] for u, v in G.edges()] 53 | # nx.draw(G, pos, with_labels=True, edge_color=colors, node_color='lightblue', node_size=500, font_size=10, font_weight='bold') 54 | # 55 | # for edge in visited_edges: 56 | # G[edge[0]][edge[1]]['color'] = 'red' 57 | # 58 | # colors = [G[u][v]['color'] for u, v in G.edges()] 59 | # nx.draw(G, pos, with_labels=True, edge_color=colors, node_color='lightblue', node_size=500, font_size=10, font_weight='bold') 60 | # plt.show() 61 | # 62 | # # Функція BFS з відображенням відвіданих ребер 63 | # def bfs_recursive(graph, queue, visited=None, visited_edges=None): 64 | # # Перевіряємо, чи існує множина відвіданих вершин, якщо ні, то ініціалізуємо нову 65 | # if visited is None: 66 | # visited = set() 67 | # # Перевіряємо, чи існує список відвіданих ребер, якщо ні, то ініціалізуємо новий 68 | # if visited_edges is None: 69 | # visited_edges = [] 70 | # # Якщо черга порожня, завершуємо рекурсію 71 | # if not queue: 72 | # return 73 | # # Вилучаємо вершину з початку черги 74 | # vertex = queue.popleft() 75 | # # Перевіряємо, чи відвідували раніше дану вершину 76 | # if vertex not in visited: 77 | # # Якщо не відвідували, друкуємо вершину 78 | # print(vertex, end=" ") 79 | # # Додаємо вершину до множини відвіданих вершин 80 | # visited.add(vertex) 81 | # # Додаємо невідвіданих сусідів даної вершини в кінець черги та додаємо ребро до списку відвіданих ребер 82 | # for neighbor in set(graph[vertex]) - visited: 83 | # queue.append(neighbor) 84 | # visited_edges.append((vertex, neighbor)) 85 | # print(f"\nVisited edge: {vertex} -> {neighbor}") # Друкуємо ребро 86 | # draw_graph(graph, visited_edges) # Малюємо граф з оновленими ребрами 87 | # # Рекурсивний виклик функції з тією ж чергою та множиною відвіданих вершин 88 | # bfs_recursive(graph, queue, visited, visited_edges) 89 | # 90 | # # Представлення графа за допомогою списку суміжності 91 | # graph = { 92 | # "A": ["B", "C"], 93 | # "B": ["A", "D", "E"], 94 | # "C": ["A", "F"], 95 | # "D": ["B"], 96 | # "E": ["B", "F"], 97 | # "F": ["C", "E"], 98 | # } 99 | # 100 | # # Запуск рекурсивного алгоритму BFS 101 | # bfs_recursive(graph, deque(["A"])) 102 | -------------------------------------------------------------------------------- /graph/dfs_iterative.py: -------------------------------------------------------------------------------- 1 | def dfs_iterative(graph, start_vertex): 2 | visited = set() 3 | # Використовуємо стек для зберігання вершин 4 | stack = [start_vertex] 5 | while stack: 6 | # Вилучаємо вершину зі стеку 7 | vertex = stack.pop() 8 | if vertex not in visited: 9 | print(vertex, end=' ') 10 | # Відвідуємо вершину 11 | visited.add(vertex) 12 | # Додаємо сусідні вершини до стеку 13 | stack.extend(reversed(graph[vertex])) 14 | 15 | # Представлення графа за допомогою списку суміжності 16 | graph = { 17 | 'A': ['B', 'C'], 18 | 'B': ['A', 'D', 'E'], 19 | 'C': ['A', 'F'], 20 | 'D': ['B'], 21 | 'E': ['B', 'F'], 22 | 'F': ['C', 'E'] 23 | } 24 | 25 | # Виклик функції DFS 26 | dfs_iterative(graph, 'A') 27 | -------------------------------------------------------------------------------- /graph/dfs_recursive.py: -------------------------------------------------------------------------------- 1 | def dfs_recursive(graph, vertex, visited=None): 2 | if visited is None: 3 | visited = set() 4 | visited.add(vertex) 5 | print(vertex, end=' ') # Відвідуємо вершину 6 | for neighbor in graph[vertex]: 7 | if neighbor not in visited: 8 | dfs_recursive(graph, neighbor, visited) 9 | 10 | # Представлення графа за допомогою списку суміжності 11 | graph = { 12 | 'A': ['B', 'C'], 13 | 'B': ['A', 'D', 'E'], 14 | 'C': ['A', 'F'], 15 | 'D': ['B'], 16 | 'E': ['B', 'F'], 17 | 'F': ['C', 'E'] 18 | } 19 | 20 | # Виклик функції DFS 21 | dfs_recursive(graph, 'A') 22 | 23 | 24 | 25 | 26 | 27 | # import matplotlib.pyplot as plt 28 | # import networkx as nx 29 | # 30 | # # Функція для візуалізації графа з кольоровими ребрами 31 | # def draw_graph_step(graph, pos, visited_edges): 32 | # plt.figure() 33 | # G = nx.Graph(graph) 34 | # nx.draw(G, pos, with_labels=True, node_color='lightblue', node_size=500, font_size=10, font_weight='bold') 35 | # 36 | # red_edges = visited_edges 37 | # blue_edges = [(u, v) for u in graph for v in graph[u] if (u, v) not in visited_edges and (v, u) not in visited_edges] 38 | # 39 | # nx.draw_networkx_edges(G, pos, edgelist=red_edges, edge_color='red', width=2) 40 | # nx.draw_networkx_edges(G, pos, edgelist=blue_edges, edge_color='blue', width=2, style='dashed') 41 | # 42 | # plt.show() 43 | # 44 | # # Функція для обхід графа з візуалізацією 45 | # def dfs_recursive_visual(graph, vertex, visited=None, visited_edges=None, pos=None): 46 | # if visited is None: 47 | # visited = set() 48 | # if visited_edges is None: 49 | # visited_edges = [] 50 | # if pos is None: 51 | # pos = nx.spring_layout(graph) # Layout для графа 52 | # 53 | # visited.add(vertex) 54 | # print(vertex, end=' ') # Відвідуємо вершину 55 | # 56 | # for neighbor in graph[vertex]: 57 | # if neighbor not in visited: 58 | # visited_edges.append((vertex, neighbor)) 59 | # draw_graph_step(graph, pos, visited_edges) # Візуалізація на кожному кроці 60 | # dfs_recursive_visual(graph, neighbor, visited, visited_edges, pos) 61 | # 62 | # # Представлення графа за допомогою списку суміжності 63 | # graph = { 64 | # 'A': ['B', 'C'], 65 | # 'B': ['A', 'D', 'E'], 66 | # 'C': ['A', 'F'], 67 | # 'D': ['B'], 68 | # 'E': ['B', 'F'], 69 | # 'F': ['C', 'E'] 70 | # } 71 | # 72 | # # Створення об'єкта графа для networkx 73 | # G = nx.Graph() 74 | # for node, neighbors in graph.items(): 75 | # for neighbor in neighbors: 76 | # G.add_edge(node, neighbor) 77 | # 78 | # # Виклик функції DFS з візуалізацією 79 | # dfs_recursive_visual(graph, 'A') 80 | -------------------------------------------------------------------------------- /graph/dijkstra.py: -------------------------------------------------------------------------------- 1 | def dijkstra(graph, start): 2 | # Ініціалізація відстаней та множини невідвіданих вершин 3 | distances = {vertex: float('infinity') for vertex in graph} 4 | distances[start] = 0 5 | unvisited = list(graph.keys()) 6 | 7 | while unvisited: 8 | # Знаходження вершини з найменшою відстанню серед невідвіданих 9 | current_vertex = min(unvisited, key=lambda vertex: distances[vertex]) 10 | 11 | # Якщо поточна відстань є нескінченністю, то ми завершили роботу 12 | if distances[current_vertex] == float('infinity'): 13 | break 14 | 15 | for neighbor, weight in graph[current_vertex].items(): 16 | distance = distances[current_vertex] + weight 17 | 18 | # Якщо нова відстань коротша, то оновлюємо найкоротший шлях 19 | if distance < distances[neighbor]: 20 | distances[neighbor] = distance 21 | 22 | # Видаляємо поточну вершину з множини невідвіданих 23 | unvisited.remove(current_vertex) 24 | 25 | return distances 26 | 27 | # Приклад графа у вигляді словника 28 | graph = { 29 | 'A': {'B': 5, 'C': 10}, 30 | 'B': {'A': 5, 'D': 3}, 31 | 'C': {'A': 10, 'D': 2}, 32 | 'D': {'B': 3, 'C': 2, 'E': 4}, 33 | 'E': {'D': 4} 34 | } 35 | 36 | # Виклик функції для вершини A 37 | dijkstra(graph, 'A') 38 | # Виклик функції для вершини A 39 | print(dijkstra(graph, 'A')) 40 | 41 | # 42 | # def print_table(distances, visited): 43 | # # Верхній рядок таблиці 44 | # print("{:<10} {:<10} {:<10}".format("Вершина", "Відстань", "Перевірено")) 45 | # print("-" * 30) 46 | # 47 | # # Вивід даних для кожної вершини 48 | # for vertex in distances: 49 | # distance = distances[vertex] 50 | # if distance == float('infinity'): 51 | # distance = "∞" 52 | # else: 53 | # distance = str(distance) 54 | # 55 | # status = "Так" if vertex in visited else "Ні" 56 | # print("{:<10} {:<10} {:<10}".format(vertex, distance, status)) 57 | # print("\\n") 58 | # 59 | # def dijkstra(graph, start): 60 | # distances = {vertex: float('infinity') for vertex in graph} 61 | # distances[start] = 0 62 | # unvisited = list(graph.keys()) 63 | # visited = [] 64 | # 65 | # while unvisited: 66 | # current_vertex = min(unvisited, key=lambda vertex: distances[vertex]) 67 | # 68 | # if distances[current_vertex] == float('infinity'): 69 | # break 70 | # 71 | # for neighbor, weight in graph[current_vertex].items(): 72 | # distance = distances[current_vertex] + weight 73 | # if distance < distances[neighbor]: 74 | # distances[neighbor] = distance 75 | # 76 | # visited.append(current_vertex) 77 | # unvisited.remove(current_vertex) 78 | # 79 | # # Вивід таблиці після кожного кроку 80 | # print_table(distances, visited) 81 | # 82 | # return distances 83 | # 84 | # # Приклад графа у вигляді словника 85 | # graph = { 86 | # 'A': {'B': 5, 'C': 10}, 87 | # 'B': {'A': 5, 'D': 3}, 88 | # 'C': {'A': 10, 'D': 2}, 89 | # 'D': {'B': 3, 'C': 2, 'E': 4}, 90 | # 'E': {'D': 4} 91 | # } 92 | # 93 | # # Виклик функції для вершини A 94 | # dijkstra(graph, 'A') 95 | # # Виклик функції для вершини A 96 | # print(dijkstra(graph, 'A')) -------------------------------------------------------------------------------- /graph/networkx_lib.py: -------------------------------------------------------------------------------- 1 | import networkx as nx 2 | import matplotlib.pyplot as plt 3 | 4 | # G = nx.Graph() # неорієнтований граф 5 | # 6 | # G.add_node("A") # Добав вершину 7 | # G.add_nodes_from(["B", "C", "D"]) # Добав вершини 8 | # # G.add_edge("A", "B") # Добав ребро 9 | # G.add_edges_from([("A", "C"), ("B", "C"), ("B", "D")]) # Добав ребра 10 | # # G.remove_node("A") 11 | # # G.remove_nodes_from(["B", "C", "D"]) 12 | # # G.remove_edge("A", "B") 13 | # # G.remove_edges_from([("A", "C"), ("B", "C"), ("B", "D")]) 14 | # print(G.nodes()) # ['A', 'B', 'C', 'D'] 15 | # print(G.edges()) # [('A', 'C'), ('B', 'C'), ('B', 'D')] 16 | # print(nx.is_connected(G)) 17 | # 18 | # nx.draw(G, with_labels=True) 19 | # plt.show() 20 | # 21 | 22 | # DG = nx.DiGraph() # орієнтований граф 23 | # G = nx.Graph() 24 | # G.add_edges_from([("A", "B"), ("B", "C")]) 25 | # DG = nx.DiGraph(G) 26 | # nx.draw(DG, with_labels=True) 27 | # plt.show() 28 | 29 | 30 | # G = nx.complete_graph(8) # створення повного графа 31 | # nx.draw(G, with_labels=True) 32 | # plt.show() 33 | 34 | 35 | # G = nx.complete_graph(4) 36 | # options = { 37 | # "node_color": "yellow", 38 | # "edge_color": "lightblue", 39 | # "node_size": 500, 40 | # "width": 3, 41 | # "with_labels": True 42 | # } 43 | # nx.draw(G, **options) 44 | # plt.show() 45 | 46 | 47 | # G = nx.complete_graph(8) 48 | # pos = nx.circular_layout(G) # Кругове формування 49 | # nx.draw(G, pos, with_labels=True) 50 | # plt.title("Circular Layout") 51 | # plt.show() 52 | 53 | 54 | 55 | # G = nx.complete_graph(8) 56 | # pos = nx.random_layout(G) # Випадкове формування 57 | # nx.draw(G, pos, with_labels=True) 58 | # plt.title("Random Layout") 59 | # plt.show() 60 | 61 | # G = nx.complete_graph(8) 62 | # pos = [[0, 1, 2], [3, 4], [5, 6, 7]] # Вказує камери для розташування вершин у вигляді відносних кіл 63 | # pos = nx.shell_layout(G, pos) 64 | # nx.draw(G, pos, with_labels=True) 65 | # plt.title("Shell Layout") 66 | # plt.show() 67 | 68 | # # Створення графа 69 | # G = nx.Graph() 70 | # 71 | # # Додавання міст і доріг 72 | # G.add_edge('A', 'B', weight=5) 73 | # G.add_edge('A', 'C', weight=10) 74 | # G.add_edge('B', 'D', weight=3) 75 | # G.add_edge('C', 'D', weight=2) 76 | # G.add_edge('D', 'E', weight=4) 77 | # 78 | # # Візуалізація графа 79 | # pos = nx.spring_layout(G, seed=42) 80 | # nx.draw(G, pos, with_labels=True, node_size=700, node_color="skyblue", font_size=15, width=2) 81 | # labels = nx.get_edge_attributes(G, 'weight') 82 | # nx.draw_networkx_edge_labels(G, pos, edge_labels=labels) 83 | # 84 | # plt.show() 85 | 86 | # # Створення графа 87 | # graph = { 88 | # 'A': ['B', 'C'], 89 | # 'B': ['A', 'D', 'E'], 90 | # 'C': ['A', 'F'], 91 | # 'D': ['B'], 92 | # 'E': ['B', 'F'], 93 | # 'F': ['C', 'E'] 94 | # } 95 | # 96 | # G = nx.Graph(graph) 97 | # 98 | # # DFS 99 | # dfs_tree = nx.dfs_tree(G, source='A') 100 | # print(list(dfs_tree.edges())) # виведе ребра DFS-дерева з коренем у вузлі A 101 | # print(list(dfs_tree.nodes())) # виведе вузли DFS-дерева з коренем у вузлі A 102 | # # BFS 103 | # bfs_tree = nx.bfs_tree(G, source='A') 104 | # print(list(bfs_tree.edges())) # виведе ребра BFS-дерева з коренем у вузлі A 105 | # print(list(bfs_tree.nodes())) # виведе вузли BFS-дерева з коренем у вузлі A -------------------------------------------------------------------------------- /graph/usefull_link.md: -------------------------------------------------------------------------------- 1 | Comparing Dijkstra’s and A* Search Algorithm 2 | https://medium.com/@miguell.m/dijkstras-and-a-search-algorithm-2e67029d7749 3 | 4 | Applications of the 20 Most Popular Graph Algorithms 5 | https://memgraph.com/blog/graph-algorithms-applications 6 | 7 | Graphs and Real-Life Applications 8 | https://rajshah001.medium.com/graphs-and-real-life-application-28759b77b833 9 | 10 | Use cases for graph databases 11 | https://6point6.co.uk/insights/use-cases-for-graph-databases/ -------------------------------------------------------------------------------- /greedy_dynamic/datasets/tsp0015.txt: -------------------------------------------------------------------------------- 1 | 15 2 | 20833.3333 17100.0000 3 | 20900.0000 17066.6667 4 | 21300.0000 13016.6667 5 | 21600.0000 14150.0000 6 | 21600.0000 14966.6667 7 | 21600.0000 16500.0000 8 | 22183.3333 13133.3333 9 | 22583.3333 14300.0000 10 | 22683.3333 12716.6667 11 | 23616.6667 15866.6667 12 | 23700.0000 15933.3333 13 | 23883.3333 14533.3333 14 | 24166.6667 13250.0000 15 | 25149.1667 12365.8333 16 | 26133.3333 14500.0000 -------------------------------------------------------------------------------- /greedy_dynamic/datasets/tsp0038.txt: -------------------------------------------------------------------------------- 1 | 38 2 | 42102.500000 11003.611100 3 | 42373.888900 11108.611100 4 | 42885.833300 11133.333300 5 | 42712.500000 11155.833300 6 | 42933.333300 11183.333300 7 | 42853.333300 11297.500000 8 | 42929.444400 11310.277800 9 | 42983.333300 11416.666700 10 | 43000.277800 11423.888900 11 | 42057.222200 11438.333300 12 | 43252.777800 11461.111100 13 | 43187.222200 11485.555600 14 | 42855.277800 11503.055600 15 | 42106.388900 11511.388900 16 | 42841.944400 11522.222200 17 | 43136.666700 11569.444400 18 | 43150.000000 11583.333300 19 | 43148.055600 11595.000000 20 | 43150.000000 11600.000000 21 | 42686.666700 11690.555600 22 | 41836.111100 11715.833300 23 | 42814.444400 11751.111100 24 | 42651.944400 11770.277800 25 | 42884.444400 11785.277800 26 | 42673.611100 11822.777800 27 | 42660.555600 11846.944400 28 | 43290.555600 11963.055600 29 | 43026.111100 11973.055600 30 | 42195.555600 12058.333300 31 | 42477.500000 12149.444400 32 | 43355.555600 12286.944400 33 | 42433.333300 12300.000000 34 | 43156.388900 12355.833300 35 | 43189.166700 12363.333300 36 | 42711.388900 12372.777800 37 | 43334.722200 12386.666700 38 | 42895.555600 12421.666700 39 | 42973.333300 12645.000000 -------------------------------------------------------------------------------- /greedy_dynamic/datasets/tsp0100.txt: -------------------------------------------------------------------------------- 1 | 100 2 | 57633.3333 30133.3333 3 | 57100.0000 30166.6667 4 | 57583.3333 30233.3333 5 | 56850.0000 30250.0000 6 | 56950.0000 30250.0000 7 | 57583.3333 30250.0000 8 | 56966.6667 30300.0000 9 | 56816.6667 30316.6667 10 | 56466.6667 30400.0000 11 | 56783.3333 30400.0000 12 | 57433.3333 30433.3333 13 | 56550.0000 30466.6667 14 | 56516.6667 30483.3333 15 | 56450.0000 30500.0000 16 | 56666.6667 30500.0000 17 | 57866.6667 30550.0000 18 | 56883.3333 30566.6667 19 | 57683.3333 30600.0000 20 | 56900.0000 30616.6667 21 | 56166.6667 30633.3333 22 | 57033.3333 30683.3333 23 | 57516.6667 30683.3333 24 | 56600.0000 30716.6667 25 | 56733.3333 30733.3333 26 | 57316.6667 30733.3333 27 | 56750.0000 30750.0000 28 | 57783.3333 30783.3333 29 | 56750.0000 30833.3333 30 | 56366.6667 30866.6667 31 | 55516.6667 30900.0000 32 | 56300.0000 30916.6667 33 | 55483.3333 30933.3333 34 | 55550.0000 30933.3333 35 | 56650.0000 30950.0000 36 | 55550.0000 30966.6667 37 | 57533.3333 30966.6667 38 | 55683.3333 31000.0000 39 | 56250.0000 31000.0000 40 | 56566.6667 31016.6667 41 | 56600.0000 31033.3333 42 | 56883.3333 31033.3333 43 | 56016.6667 31083.3333 44 | 56516.6667 31083.3333 45 | 57016.6667 31083.3333 46 | 57516.6667 31083.3333 47 | 57600.0000 31083.3333 48 | 57833.3333 31083.3333 49 | 55666.6667 31100.0000 50 | 55983.3333 31100.0000 51 | 57016.6667 31100.0000 52 | 56466.6667 31116.6667 53 | 55750.0000 31166.6667 54 | 57000.0000 31166.6667 55 | 57083.3333 31166.6667 56 | 56283.3333 31183.3333 57 | 56933.3333 31183.3333 58 | 55750.0000 31200.0000 59 | 57233.3333 31200.0000 60 | 55666.6667 31216.6667 61 | 56316.6667 31216.6667 62 | 55633.3333 31233.3333 63 | 55883.3333 31233.3333 64 | 57116.6667 31233.3333 65 | 55333.3333 31250.0000 66 | 55950.0000 31250.0000 67 | 56833.3333 31250.0000 68 | 57166.6667 31250.0000 69 | 57200.0000 31250.0000 70 | 56433.3333 31283.3333 71 | 55866.6667 31300.0000 72 | 56016.6667 31300.0000 73 | 57066.6667 31300.0000 74 | 57100.0000 31300.0000 75 | 57133.3333 31300.0000 76 | 57150.0000 31300.0000 77 | 57200.0000 31300.0000 78 | 57700.0000 31300.0000 79 | 55200.0000 31316.6667 80 | 57800.0000 31333.3333 81 | 57833.3333 31333.3333 82 | 57883.3333 31333.3333 83 | 55383.3333 31350.0000 84 | 56633.3333 31350.0000 85 | 57783.3333 31350.0000 86 | 57800.0000 31350.0000 87 | 57983.3333 31350.0000 88 | 55083.3333 31366.6667 89 | 55666.6667 31366.6667 90 | 55850.0000 31366.6667 91 | 55866.6667 31383.3333 92 | 57966.6667 31383.3333 93 | 56366.6667 31400.0000 94 | 57783.3333 31400.0000 95 | 54916.6667 31416.6667 96 | 56683.3333 31416.6667 97 | 55233.3333 31433.3333 98 | 55416.6667 31450.0000 99 | 56383.3333 31450.0000 100 | 55166.6667 31466.6667 101 | 56833.3333 31466.6667 -------------------------------------------------------------------------------- /greedy_dynamic/greedy_salesman.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from time import time 3 | 4 | def algorithm(cities): # Часова складність: O(n^3) 5 | """ 6 | Функція шукає найкоротший шлях для відвідування всіх міст, починаючи з кожного міста. 7 | Це робить її часову складність O(n^3), оскільки для кожного міста ми обчислюємо 8 | відстані до всіх інших міст і порівнюємо шляхи. 9 | """ 10 | best_order = [] # Найкращий порядок міст 11 | best_length = float('inf') # Найкоротша довжина шляху (початково нескінченність) 12 | 13 | for i_start, start in enumerate(cities): # Перебираємо всі міста як стартові точки 14 | order = [i_start] # Початковий порядок міст (починаємо з поточного міста) 15 | length = 0 # Початкова довжина шляху 16 | 17 | i_next, next, dist = get_closest(start, cities, order) # Знаходимо найближче місто до стартового 18 | length += dist # Додаємо відстань до загальної довжини шляху 19 | order.append(i_next) # Додаємо індекс наступного міста до порядку 20 | 21 | while len(order) < cities.shape[0]: # Поки не відвідаємо всі міста 22 | i_next, next, dist = get_closest(next, cities, order) # Знаходимо найближче місто до поточного 23 | length += dist # Додаємо відстань до загальної довжини шляху 24 | order.append(i_next) # Додаємо індекс наступного міста до порядку 25 | 26 | # print(order) # Для відлагодження: друкуємо порядок міст 27 | 28 | if length < best_length: # Якщо знайдений шлях коротший за найкращий 29 | best_length = length # Оновлюємо найкращу довжину шляху 30 | best_order = order # Оновлюємо найкращий порядок міст 31 | 32 | return best_order, best_length # Повертаємо найкращий порядок міст і найкоротшу довжину шляху 33 | 34 | def get_closest(city, cities, visited): # Часова складність: O(n) 35 | """ 36 | Функція знаходить найближче місто до поточного, яке ще не відвідано. 37 | Її часова складність O(n), оскільки ми перевіряємо всі міста. 38 | """ 39 | best_distance = float('inf') # Найкоротша відстань (початково нескінченність) 40 | 41 | for i, c in enumerate(cities): # Перебираємо всі міста 42 | if i not in visited: # Якщо місто ще не відвідане 43 | distance = dist_squared(city, c) # Обчислюємо відстань до поточного міста 44 | 45 | if distance < best_distance: # Якщо відстань менша за найкращу 46 | closest_city = c # Оновлюємо найближче місто 47 | i_closest_city = i # Оновлюємо індекс найближчого міста 48 | best_distance = distance # Оновлюємо найкращу відстань 49 | 50 | return i_closest_city, closest_city, best_distance # Повертаємо індекс, координати і відстань до найближчого міста 51 | 52 | def dist_squared(c1, c2): # Часова складність: O(1) 53 | """ 54 | Функція знаходить найближче місто до поточного, яке ще не відвідано. 55 | Її часова складність O(n), оскільки ми перевіряємо всі міста. 56 | """ 57 | return pow(c2[0] - c1[0], 2) + pow(c2[1] - c1[1], 2) # Обчислюємо квадрат евклідової відстані між двома містами 58 | 59 | 60 | 61 | def main(): 62 | # loading data 63 | f = open("datasets/tsp0038.txt", 'r').read().splitlines() 64 | f.pop(0) 65 | cities = np.array([tuple(map(float, coord.split())) for coord in f]) 66 | 67 | # calculating path 68 | start = time() 69 | path, length = algorithm(cities) 70 | print(path) 71 | tottime = time() - start 72 | print("Знайдено шлях довжиною %s за %s секунд" % (round(length, 2), round(tottime, 2))) 73 | 74 | 75 | if __name__ == "__main__": 76 | main() -------------------------------------------------------------------------------- /greedy_dynamic/held_carp.py: -------------------------------------------------------------------------------- 1 | # import sys 2 | # import numpy as np 3 | # import itertools 4 | # from copy import deepcopy 5 | # 6 | # 7 | # def held_karp(dists): 8 | # L = {} 9 | # 10 | # for k in range(1, nb_vertices): 11 | # L[((k), k)] = (dists[0][k], 0) 12 | # print('L=', L) 13 | # 14 | # for subset_size in range(2, nb_vertices): 15 | # for subset in itertools.combinations(range(1, nb_vertices), subset_size): 16 | # 17 | # list_subset = list(int(k) for k in subset) 18 | # # Find the lowest cost to get to this subset 19 | # for k in subset: 20 | # list_subset_prev = deepcopy(list_subset) 21 | # list_subset_prev.remove(k) 22 | # res = [] 23 | # for m in subset: 24 | # if m == 0 or m == k: 25 | # continue 26 | # if (len(list_subset_prev) == 1): 27 | # 28 | # res.append((L[((list_subset_prev[0]), m)][0] + dists[m][k], m)) 29 | # else: 30 | # res.append((L[(tuple(list_subset_prev), m)][0] + dists[m][k], m)) 31 | # L[(tuple(list_subset), k)] = min(res) 32 | # 33 | # list_vertices = list(int(k) for k in range(1, nb_vertices)) 34 | # 35 | # # Calculate optimal cost 36 | # res = [] 37 | # for k in range(1, nb_vertices): 38 | # res.append((L[(tuple(list_vertices), k)][0] + dists[k][0], k)) 39 | # opt, parent = min(res) 40 | # 41 | # return opt 42 | # 43 | # 44 | # if __name__ == '__main__': 45 | # 46 | # with open('datasets/tsp0015.txt') as data_file: 47 | # vertices = [] 48 | # nb_vertices = int(data_file.readline()) 49 | # print("expected number of vertices : {0}\n".format(nb_vertices)) 50 | # for line in data_file: 51 | # vertices.append(tuple(float(x) for x in line.split())) 52 | # 53 | # vertices = np.array(vertices) 54 | # # print(vertices.shape) 55 | # print(vertices) 56 | # 57 | # dists = np.zeros((nb_vertices, nb_vertices)) 58 | # for i in range(nb_vertices): 59 | # for j in range(nb_vertices): 60 | # dists[i, j] = np.sqrt(np.sum((vertices[i, :] - vertices[j, :]) ** 2)) 61 | # 62 | # # print(dists.shape) 63 | # print(dists) 64 | # # print(len(dists)) 65 | # # print('') 66 | # 67 | # print(held_karp(dists)) 68 | 69 | import math 70 | 71 | 72 | def held_karp_salesman(distances): 73 | """ 74 | Знаходить оптимальний маршрут комівояжера і його вартість з використанням динамічного програмування 75 | та алгоритму Хелда-Карпа. 76 | 77 | Часова складність: O(n^2 * 2^n) 78 | Просторова складність: O(n * 2^n) 79 | 80 | Параметри: 81 | distances (list[list[int]]): Матриця відстаней між містами. 82 | 83 | Повертає: 84 | tuple: Оптимальний маршрут (список міст у порядку відвідування) і його вартість (мінімальна відстань). 85 | """ 86 | n = len(distances) # Кількість міст 87 | dp = [[math.inf] * n for _ in range(1 << n)] # Таблиця для збереження мінімальних відстаней 88 | parent = [[None] * n for _ in range(1 << n)] # Таблиця для збереження шляхів 89 | 90 | # Початковий випадок: початок з міста 0 91 | dp[1][0] = 0 92 | 93 | # Заповнення таблиці DP 94 | for mask in range(1 << n): # Перебираємо всі можливі підмножини міст 95 | for last in range(n): # Перебираємо всі можливі останні відвідані міста 96 | if not (mask & (1 << last)): # Якщо місто не входить до підмножини, пропускаємо його 97 | continue 98 | for next in range(n): # Перебираємо всі можливі наступні міста 99 | if mask & (1 << next): # Якщо місто вже відвідане, пропускаємо його 100 | continue 101 | new_mask = mask | (1 << next) # Оновлюємо підмножину з новим містом 102 | new_dist = dp[mask][last] + distances[last][next] # Обчислюємо нову відстань 103 | if new_dist < dp[new_mask][next]: # Якщо нова відстань менша за поточну 104 | dp[new_mask][next] = new_dist # Оновлюємо таблицю DP 105 | parent[new_mask][next] = last # Зберігаємо шлях 106 | 107 | # Знаходимо оптимальний маршрут та мінімальну вартість 108 | min_cost = math.inf # Початкова мінімальна вартість 109 | end_city = None # Місто, де закінчується маршрут 110 | full_mask = (1 << n) - 1 # Маска для всіх міст 111 | 112 | for last in range(1, n): # Перебираємо всі міста, щоб знайти мінімальну вартість маршруту 113 | cost = dp[full_mask][last] + distances[last][0] # Додаємо вартість повернення в початкове місто 114 | if cost < min_cost: # Якщо знайдена вартість менша за поточну мінімальну 115 | min_cost = cost # Оновлюємо мінімальну вартість 116 | end_city = last # Зберігаємо місто, де закінчується маршрут 117 | 118 | # Відновлюємо оптимальний маршрут 119 | tour = [] # Список для збереження маршруту 120 | mask = full_mask # Маска для всіх міст 121 | last = end_city # Починаємо з кінцевого міста 122 | while mask: # Поки маска не пуста 123 | tour.append(last) # Додаємо місто до маршруту 124 | new_last = parent[mask][last] # Знаходимо попереднє місто 125 | mask ^= (1 << last) # Видаляємо місто з маски 126 | last = new_last # Переходимо до попереднього міста 127 | tour = tour[::-1] # Обертаємо маршрут, щоб почати з початкового міста 128 | tour.append(0) # Додаємо початкове місто в кінець для завершення циклу 129 | 130 | return tour, min_cost # Повертаємо оптимальний маршрут та його вартість 131 | 132 | 133 | # Приклад використання функції 134 | distances = [ 135 | [0, 20, 42, 35], 136 | [20, 0, 30, 34], 137 | [42, 30, 0, 12], 138 | [35, 34, 12, 0] 139 | ] 140 | 141 | 142 | 143 | tour, min_cost = held_karp_salesman(distances) 144 | 145 | print("Оптимальний маршрут:", tour) 146 | print("Мінімальна відстань:", min_cost) 147 | -------------------------------------------------------------------------------- /greedy_dynamic/knap_sack.py: -------------------------------------------------------------------------------- 1 | # Табуляція/2D масив Рішення 2 | def knapSack_2D_array(capacity, weights, values, n): # O(n×capacity) V = O(n×capacity) 3 | # Створити двовимірний список для зберігання максимальних значень параметру knapsack 4 | # Ініціалізуємо усі значення 0 5 | dp = [[0 for _ in range(capacity + 1)] for i in range(len(weights) + 1)] 6 | 7 | # Перебір кожного елементу 8 | for i in range(n): 9 | # Перебрати всі можливі варіанти об'єму рюкзака 10 | for curr_capacity in range(capacity + 1): 11 | # Якщо вага поточного предмета менша або дорівнює поточному об'єму 12 | if weights[i] <= curr_capacity: 13 | # Вибираємо між включенням та виключенням поточного елементу 14 | # Якщо включаємо поточний елемент, 15 | # додамо його вартість до максимального значення, яке можна отримати 16 | # з урахуванням залишкової ємності та попередніх елементів 17 | # Важливе зауваження для мене: 18 | # Віднімаючи weights[i] від поточної ємності curr_capacity, 19 | # ми фактично обчислюємо, скільки ємності залишилося після включення елемента. 20 | # Потім, додаючи values[i] до виразу, 21 | # ми додаємо значення поточного елемента до максимально можливого значення 22 | # за допомогою залишкової ємності. 23 | # Отже, це означає, що я фактично обчислюю значення, якщо додаю поточний елемент 24 | # додаючи значення поточного елемента до значень решти елементів 25 | # якщо я включу поточний елемент. 26 | # Якщо ми виключаємо поточний елемент, максимальне значення залишається таким же, як у попередній комірці 27 | dp[i + 1][curr_capacity] = max( 28 | dp[i][curr_capacity], values[i] + dp[i][curr_capacity - weights[i]] 29 | ) 30 | else: 31 | # Якщо вага поточного елементу більша за поточну місткість, 32 | # ми не можемо включити елемент, тому максимальне значення залишається таким самим, як у попередній комірці 33 | dp[i + 1][curr_capacity] = dp[i][curr_capacity] 34 | 35 | # Повернути максимальне значення, яке може бути досягнуте з усіма елементами та заданою ємністю 36 | 37 | return dp[-1][-1] 38 | 39 | 40 | # Рішення для одновимірного масиву 41 | def knapSack_one_1D_Array(capacity, weights, values, n) -> int: # O(n×capacity) V = O(capacity) 42 | # Створюємо 1D список для зберігання максимально можливого значення для кожного об'єму 43 | dp = [0 for _ in range(capacity + 1)] 44 | 45 | # Перебір кожного елементу 46 | for i in range(n): 47 | # Зворотний цикл - через нього нам потрібен лише один 1D список 48 | for curr_cap in range(capacity, weights[i] - 1, -1): 49 | # Якщо вага поточного елементу менша або дорівнює поточній ємності, 50 | # обчислити максимальне значення, яке може бути досягнуте включенням або виключенням поточного елементу 51 | if weights[i] <= curr_cap: 52 | dp[curr_cap] = max(values[i] + dp[curr_cap - weights[i]], dp[curr_cap]) 53 | else: 54 | # Якщо вага поточного елементу перевищує поточну ємність, то перериваємо цикл 55 | break 56 | 57 | # Повернути максимально досяжне значення для даної ємності 58 | return dp[-1] 59 | 60 | 61 | # Рішення двох одновимірних масивів 62 | def knapSack_two_1D_Array(capacity, weights, values, n) -> int: # O(n×capacity) V = O(capacity) 63 | # Ініціалізуємо два 1D списки для зберігання максимально можливого значення для кожної ємності 64 | # до розгляду поточного елементу та після розгляду поточного елементу. 65 | max_values_before = [0 for _ in range(capacity + 1)] 66 | max_values_after = [0 for _ in range(capacity + 1)] 67 | 68 | # Перебір кожного елементу 69 | for item_index in range(n): 70 | # Оновити список max_values_after для кожної ємності 71 | for curr_capacity in range(capacity + 1): 72 | if weights[item_index] <= curr_capacity: 73 | # Якщо поточний предмет можна помістити в рюкзак, обчислити максимальне значення 74 | # включивши або виключивши поточний предмет. 75 | max_values_after[curr_capacity] = max( 76 | values[item_index] + max_values_before[curr_capacity - weights[item_index]], 77 | max_values_before[curr_capacity]) 78 | else: 79 | # Якщо поточний елемент не може бути включений, зберегти попереднє значення. 80 | max_values_after[curr_capacity] = max_values_before[curr_capacity] 81 | 82 | # Встановити список max_values_before таким, що дорівнює списку max_values_after для наступної ітерації. 83 | max_values_before[:] = max_values_after[:] 84 | 85 | # Повернути максимальне значення, досяжне для даної ємності. 86 | return max_values_after[-1] 87 | 88 | 89 | # Рекурсивне рішення 90 | def knapSack_recursive(W, wt, val, n): # O(2^n) V = O(n) 91 | return knapSackRecursion(W, wt, val, n, 0, 0) 92 | 93 | def knapSackRecursion(max_weight, wt, val, n, ind, curr_weight): 94 | if ind >= n or curr_weight >= max_weight: 95 | return 0 96 | 97 | # Включити поточний елемент, якщо його вага в межах ємності 98 | if wt[ind] <= max_weight - curr_weight: 99 | include_curr = val[ind] + knapSackRecursion(max_weight, wt, val, n, ind + 1, curr_weight + wt[ind]) 100 | else: 101 | include_curr = 0 102 | 103 | # Виключення поточного елементу і перехід до наступного елементу 104 | exclude_curr = knapSackRecursion(max_weight, wt, val, n, ind + 1, curr_weight) 105 | 106 | # Повернути максимальне значення, отримане при включенні або виключенні поточного елементу 107 | return max(include_curr, exclude_curr) 108 | 109 | # Пояснення: 110 | # W - максимальна вага рюкзака. 111 | # wt - список ваг предметів. 112 | # val - список вартостей предметів. 113 | # n - кількість предметів. 114 | 115 | W = 10 116 | wt = [1, 3, 4, 6, 2, 1] 117 | val = [10, 40, 50, 70, 90, 40] 118 | n = len(wt) 119 | print(knapSack_2D_array(W, wt, val, n)) 120 | print(knapSack_one_1D_Array(W, wt, val, n)) 121 | print(knapSack_two_1D_Array(W, wt, val, n)) 122 | print(knapSack_recursive(W, wt, val, n)) 123 | 124 | -------------------------------------------------------------------------------- /greedy_dynamic/kruskals_algorithm.py: -------------------------------------------------------------------------------- 1 | class Graph: 2 | """ 3 | Iніціалізація графа та додавання ребер і вершин: 4 | 5 | __init__: Ініціалізує граф із заданою кількістю вершин. 6 | add_edge: Додає ребро між двома вершинами з заданою вагою. 7 | add_vertex_data: Додає назву (дані) до вершини. 8 | """ 9 | def __init__(self, size): 10 | # Ініціалізуємо граф із заданою кількістю вершин 11 | self.size = size 12 | self.edges = [] # Список для збереження ребер у форматі (вага, вершина u, вершина v) 13 | self.vertex_data = [''] * size # Список для збереження назв вершин 14 | 15 | def add_edge(self, u, v, weight): # O(1) 16 | # Додаємо ребро з вершини u до вершини v з заданою вагою 17 | if 0 <= u < self.size and 0 <= v < self.size: 18 | self.edges.append((u, v, weight)) # Додаємо ребро до списку 19 | 20 | def add_vertex_data(self, vertex, data): # O(1) 21 | # Додаємо назву (дані) до вершини 22 | if 0 <= vertex < self.size: 23 | self.vertex_data[vertex] = data 24 | """ 25 | Функції для роботи з множинами: 26 | 27 | find: Знаходить корінь множини для вершини з урахуванням стиснення шляхів. 28 | union: Об'єднує дві множини на основі рангу. 29 | """ 30 | def find(self, parent, i): # O(log V) 31 | # Функція знаходить кореневу вершину множини з урахуванням стиснення шляхів 32 | if parent[i] == i: 33 | return i 34 | return self.find(parent, parent[i]) 35 | 36 | def union(self, parent, rank, x, y): # O(log V) 37 | # Об'єднує дві множини на основі рангу 38 | xroot = self.find(parent, x) 39 | yroot = self.find(parent, y) 40 | if rank[xroot] < rank[yroot]: 41 | parent[xroot] = yroot 42 | elif rank[xroot] > rank[yroot]: 43 | parent[yroot] = xroot 44 | else: 45 | parent[yroot] = xroot 46 | rank[xroot] += 1 47 | 48 | def kruskals_algorithm(self): # O(E log E + E log V) 49 | """ 50 | Алгоритм Крускала (kruskals_algorithm): 51 | Ініціалізація: 52 | result: Список для збереження MST. 53 | i: Лічильник ребер. 54 | self.edges = sorted(self.edges, key=lambda item: item[2]): Сортуємо ребра за вагою. 55 | parent, rank: Ініціалізуємо множини для кожної вершини. 56 | """ 57 | # Реалізація алгоритму Крускала для пошуку MST 58 | result = [] # Список для збереження MST 59 | i = 0 # Лічильник ребер 60 | 61 | # Сортуємо ребра за вагою 62 | self.edges = sorted(self.edges, key=lambda item: item[2]) 63 | 64 | parent = [] 65 | rank = [] 66 | 67 | # Ініціалізуємо множини для кожної вершини 68 | for node in range(self.size): 69 | parent.append(node) # Кожна вершина є своїм власним батьком 70 | rank.append(0) # Початковий ранг усіх вершин 0 71 | 72 | """ 73 | Основний цикл: 74 | Проходимо по всіх ребрах (while i < len(self.edges)). 75 | Знаходимо корені множин для вершин u і v. 76 | Якщо вершини u і v не утворюють цикл, додаємо ребро до MST і об'єднуємо множини. 77 | """ 78 | while i < len(self.edges): # Проходимо по всіх ребрах 79 | u, v, weight = self.edges[i] 80 | i += 1 81 | 82 | x = self.find(parent, u) # Знаходимо корінь множини для вершини u 83 | y = self.find(parent, v) # Знаходимо корінь множини для вершини v 84 | 85 | # Якщо вершини u і v не утворюють цикл, додаємо ребро до MST 86 | if x != y: 87 | result.append((u, v, weight)) # Додаємо ребро до результату 88 | self.union(parent, rank, x, y) # Об'єднуємо множини x і y 89 | 90 | # Виводимо результат 91 | print("Ребро \tВага") 92 | for u, v, weight in result: 93 | print(f"{self.vertex_data[u]}-{self.vertex_data[v]} \t{weight}") 94 | 95 | # Приклад використання графа 96 | g = Graph(7) 97 | g.add_vertex_data(0, 'A') 98 | g.add_vertex_data(1, 'B') 99 | g.add_vertex_data(2, 'C') 100 | g.add_vertex_data(3, 'D') 101 | g.add_vertex_data(4, 'E') 102 | g.add_vertex_data(5, 'F') 103 | g.add_vertex_data(6, 'G') 104 | 105 | # Додаємо ребра до графа 106 | g.add_edge(0, 1, 4) # A-B, 4 107 | g.add_edge(0, 6, 10) # A-G, 10 108 | g.add_edge(0, 2, 9) # A-C, 9 109 | g.add_edge(1, 2, 8) # B-C, 8 110 | g.add_edge(2, 3, 5) # C-D, 5 111 | g.add_edge(2, 4, 2) # C-E, 2 112 | g.add_edge(2, 6, 7) # C-G, 7 113 | g.add_edge(3, 4, 3) # D-E, 3 114 | g.add_edge(3, 5, 7) # D-F, 7 115 | g.add_edge(4, 6, 6) # E-G, 6 116 | g.add_edge(5, 6, 11) # F-G, 11 117 | 118 | # Запускаємо алгоритм Крускала для знаходження MST 119 | print("Алгоритм Крускала для знаходження MST:") 120 | g.kruskals_algorithm() 121 | -------------------------------------------------------------------------------- /greedy_dynamic/prim.py: -------------------------------------------------------------------------------- 1 | class Graph: 2 | """ 3 | Ініціалізація графа та додавання ребер і вершин: 4 | 5 | __init__: Ініціалізує граф із заданою кількістю вершин, створюючи матрицю суміжності. 6 | add_edge: Додає ребро між двома вершинами з заданою вагою. 7 | add_vertex_data: Додає назву (дані) до вершини. 8 | """ 9 | def __init__(self, size): 10 | # Ініціалізуємо матрицю суміжності для графа та розмір графа 11 | self.adj_matrix = [[0] * size for _ in range(size)] 12 | self.size = size 13 | self.vertex_data = [''] * size # Зберігаємо назви вершин 14 | 15 | def add_edge(self, u, v, weight): # O(1) 16 | # Додаємо ребро між вершинами u і v з заданою вагою 17 | if 0 <= u < self.size and 0 <= v < self.size: 18 | self.adj_matrix[u][v] = weight 19 | self.adj_matrix[v][u] = weight # Для неорієнтованого графа 20 | 21 | def add_vertex_data(self, vertex, data): # O(1) 22 | # Додаємо назву (дані) до вершини 23 | if 0 <= vertex < self.size: 24 | self.vertex_data[vertex] = data 25 | 26 | def prims_algorithm(self): # O(V^2) 27 | """ 28 | Алгоритм Пріма (prims_algorithm): 29 | 30 | Ініціалізація: 31 | in_mst: Вершини, що включені до MST. 32 | key_values: Ключові значення для вибору мінімальної ваги ребра. 33 | parents: Масив для збереження MST. 34 | 35 | """ 36 | # Ініціалізуємо структури для зберігання стану MST 37 | in_mst = [False] * self.size # Вершини, що включені до MST 38 | key_values = [float('inf')] * self.size # Ключові значення для вибору мінімальної ваги ребра 39 | parents = [-1] * self.size # Масив для збереження MST 40 | 41 | key_values[0] = 0 # Початкова вершина 42 | 43 | print("Edge \tWeight") 44 | for _ in range(self.size): # Повторюємо для всіх вершин 45 | """ 46 | Основний цикл: 47 | Знаходимо вершину u з мінімальним ключовим значенням, яка не включена до MST. 48 | Включаємо вершину u до MST. 49 | Пропускаємо першу вершину, оскільки вона не має батька. 50 | Оновлюємо ключове значення для суміжних вершин, якщо ребро має меншу вагу, і оновлюємо батька для вершини. 51 | 52 | """ 53 | # Знаходимо вершину u з мінімальним ключовим значенням, яка не включена до MST 54 | u = min((v for v in range(self.size) if not in_mst[v]), key=lambda v: key_values[v]) 55 | 56 | in_mst[u] = True # Включаємо вершину u до MST 57 | 58 | if parents[u] != -1: # Пропускаємо першу вершину, оскільки вона не має батька 59 | print(f"{self.vertex_data[parents[u]]}-{self.vertex_data[u]} \t{self.adj_matrix[u][parents[u]]}") 60 | 61 | for v in range(self.size): # Перевіряємо всі суміжні вершини 62 | # Оновлюємо ключове значення для вершини v, якщо ребро (u, v) має меншу вагу 63 | if 0 < self.adj_matrix[u][v] < key_values[v] and not in_mst[v]: 64 | key_values[v] = self.adj_matrix[u][v] 65 | parents[v] = u # Оновлюємо батька для вершини v 66 | 67 | # Приклад використання графа 68 | g = Graph(8) 69 | 70 | # Додаємо назви вершин 71 | g.add_vertex_data(0, 'A') 72 | g.add_vertex_data(1, 'B') 73 | g.add_vertex_data(2, 'C') 74 | g.add_vertex_data(3, 'D') 75 | g.add_vertex_data(4, 'E') 76 | g.add_vertex_data(5, 'F') 77 | g.add_vertex_data(6, 'G') 78 | g.add_vertex_data(7, 'H') 79 | 80 | # Додаємо ребра до графа з їх вагою 81 | g.add_edge(0, 1, 4) # A - B 82 | g.add_edge(0, 3, 3) # A - D 83 | g.add_edge(1, 2, 3) # B - C 84 | g.add_edge(1, 3, 5) # B - D 85 | g.add_edge(1, 4, 6) # B - E 86 | g.add_edge(2, 4, 4) # C - E 87 | g.add_edge(2, 7, 2) # C - H 88 | g.add_edge(3, 4, 7) # D - E 89 | g.add_edge(3, 5, 4) # D - F 90 | g.add_edge(4, 5, 5) # E - F 91 | g.add_edge(4, 6, 3) # E - G 92 | g.add_edge(5, 6, 7) # F - G 93 | g.add_edge(6, 7, 5) # G - H 94 | 95 | # Запускаємо алгоритм Пріма для знаходження MST 96 | print("Prim's Algorithm MST:") 97 | g.prims_algorithm() 98 | -------------------------------------------------------------------------------- /greedy_dynamic/up_down_knap_sack.py: -------------------------------------------------------------------------------- 1 | def knapsack(values, weights, capacity, i=0): 2 | if len(values) == i: 3 | return 0 4 | elif capacity < 0: 5 | return float("-inf") 6 | else: 7 | return max(values[i]+knapsack(values, weights, capacity-weights[i], i+1), 8 | knapsack(values, weights, capacity, i+1)) 9 | 10 | def find_knapsack_top(values,weights, capacity, i=0, lookup= None): 11 | lookup = {} if lookup is None else lookup 12 | if (i,capacity) in lookup: 13 | return lookup[(i,capacity)] 14 | 15 | if len(values) == i or capacity < 0: 16 | return 0 17 | elif (weights[i] > capacity): 18 | return find_knapsack_top(values, weights, capacity, i+1, lookup) 19 | else: 20 | lookup[(i,capacity)] = max(values[i]+ find_knapsack_top(values,weights,capacity-weights[i],i+1,lookup), 21 | find_knapsack_top(values, weights,capacity,i+1, lookup)) 22 | return lookup[(i,capacity)] -------------------------------------------------------------------------------- /heap/example usage.py: -------------------------------------------------------------------------------- 1 | import heapq 2 | 3 | 4 | class Task: 5 | def __init__(self, description, story_points, priority_level): 6 | self.description = description 7 | self.story_points = story_points 8 | self.priority_level = priority_level 9 | 10 | def __lt__(self, other): 11 | # Порівнюємо спочатку за рівнем пріоритету, потім за кількістю story points 12 | priority_order = {'high': 0, 'medium': 1, 'low': 2} 13 | if priority_order[self.priority_level] == priority_order[other.priority_level]: 14 | return self.story_points < other.story_points 15 | return priority_order[self.priority_level] < priority_order[other.priority_level] 16 | 17 | def __repr__(self): 18 | return f"Task(description={self.description}, story_points={self.story_points}, priority_level={self.priority_level})" 19 | 20 | 21 | class TaskBoard: 22 | def __init__(self): 23 | self.task_heap = [] 24 | 25 | def add_task(self, description, story_points, priority_level): 26 | task = Task(description, story_points, priority_level) 27 | heapq.heappush(self.task_heap, task) 28 | 29 | def get_next_task(self): 30 | return heapq.heappop(self.task_heap) if self.task_heap else None 31 | 32 | def peek_next_task(self): 33 | return self.task_heap[0] if self.task_heap else None 34 | 35 | def is_empty(self): 36 | return len(self.task_heap) == 0 37 | 38 | 39 | # Приклад використання 40 | def example_usage(): 41 | task_board = TaskBoard() 42 | task_board.add_task("Implement login feature", 5, "high") 43 | task_board.add_task("Fix bug in user profile", 2, "medium") 44 | task_board.add_task("Update documentation", 1, "low") 45 | task_board.add_task("Refactor codebase", 8, "medium") 46 | task_board.add_task("Write unit tests", 3, "high") 47 | 48 | # Перегляд завдань 49 | print("All tasks on the board:") 50 | for task in task_board.task_heap: 51 | print(task) 52 | 53 | # Отримання наступного завдання 54 | print("\nNext task to work on:") 55 | print(task_board.get_next_task()) # Очікуємо завдання з пріоритетом "high" і найменшою кількістю story points 56 | 57 | # Перегляд наступного завдання без його видалення 58 | print("\nPeek next task without removing:") 59 | print(task_board.peek_next_task()) # Очікуємо завдання з пріоритетом "high" (наступне за важливістю) 60 | 61 | # Видалення і отримання всіх завдань у порядку пріоритету 62 | print("\nRemoving tasks in order of priority:") 63 | while not task_board.is_empty(): 64 | print(task_board.get_next_task()) 65 | 66 | 67 | # Виконання прикладу 68 | example_usage() 69 | -------------------------------------------------------------------------------- /heap/heap_sort.py: -------------------------------------------------------------------------------- 1 | from max_heap import MaxHeap 2 | from min_heap import MinHeap 3 | 4 | array = [12, 54, -3, 654, 2, 8, -564, 23, 235] 5 | 6 | def heapsort(arr): # Часова складність: O(n log n) 7 | # Сортування масиву за допомогою мін-купи 8 | heap = MinHeap(arr) # Створюємо мін-купу з масиву 9 | # Витягуємо всі елементи з купи у відсортованому порядку 10 | return [heap.extract_min() for _ in range(len(heap.heap))] 11 | 12 | print(heapsort(array)) 13 | 14 | def heapsort(arr): # Часова складність: O(n log n) 15 | # Сортування масиву за допомогою макс-купи 16 | heap = MaxHeap(arr) # Створюємо макс-купу з масиву 17 | # Витягуємо всі елементи з купи у відсортованому порядку 18 | return [heap.extract_max() for _ in range(len(heap.heap))] 19 | 20 | print(heapsort(array)) -------------------------------------------------------------------------------- /heap/heapq_example.py: -------------------------------------------------------------------------------- 1 | import heapq 2 | 3 | 4 | # Min-Heap 5 | print("Min-Heap") 6 | # Створення порожньої мін-купи 7 | min_heap = [] 8 | 9 | # Додавання елементів до мін-купи 10 | heapq.heappush(min_heap, 5) 11 | heapq.heappush(min_heap, 3) 12 | heapq.heappush(min_heap, 8) 13 | heapq.heappush(min_heap, 1) 14 | 15 | # Вміст мін-купи 16 | print(min_heap) # Очікуємо [1, 3, 8, 5] 17 | 18 | # Витягування найменшого елемента 19 | min_element = heapq.heappop(min_heap) 20 | print(min_element) # Очікуємо 1 21 | 22 | # Вміст мін-купи після витягування 23 | print(min_heap) # Очікуємо [3, 5, 8] 24 | 25 | # Max-Heap 26 | print("Max-Heap") 27 | # Створення порожньої макс-купи 28 | max_heap = [] 29 | 30 | # Додавання елементів до макс-купи (використовуючи негативні значення) 31 | heapq.heappush(max_heap, -5) 32 | heapq.heappush(max_heap, -3) 33 | heapq.heappush(max_heap, -8) 34 | heapq.heappush(max_heap, -1) 35 | 36 | # Вміст макс-купи (позитивні значення) 37 | print([-x for x in max_heap]) # Очікуємо [8, 3, 5, 1] 38 | 39 | # Витягування найбільшого елемента 40 | max_element = -heapq.heappop(max_heap) 41 | print(max_element) # Очікуємо 8 42 | 43 | # Вміст макс-купи після витягування (позитивні значення) 44 | print([-x for x in max_heap]) # Очікуємо [5, 3, 1] 45 | 46 | 47 | 48 | # Finding k Smallest Elements 49 | print("Finding k Smallest Elements") 50 | # Список елементів 51 | data = [5, 3, 8, 1, 2, 7] 52 | 53 | # Знаходження 3 найменших елементів 54 | k_smallest = heapq.nsmallest(3, data) 55 | print(k_smallest) # Очікуємо [1, 2, 3] 56 | 57 | # Finding k Largest Elements 58 | print("Finding k Largest Elements") 59 | 60 | # Список елементів 61 | data = [5, 3, 8, 1, 2, 7] 62 | 63 | # Знаходження 3 найбільших елементів 64 | k_largest = heapq.nlargest(3, data) 65 | print(k_largest) # Очікуємо [8, 7, 5] 66 | 67 | # Merging Multiple Sorted Inputs 68 | print("Merging Multiple Sorted Inputs") 69 | 70 | # Список відсортованих входів 71 | list1 = [1, 4, 7] 72 | list2 = [2, 5, 8] 73 | list3 = [3, 6, 9] 74 | 75 | # Об'єднання відсортованих входів 76 | merged_list = list(heapq.merge(list1, list2, list3)) 77 | print(merged_list) # Очікуємо [1, 2, 3, 4, 5, 6, 7, 8, 9] -------------------------------------------------------------------------------- /heap/heapq_priority_queue.py: -------------------------------------------------------------------------------- 1 | import heapq 2 | 3 | class PriorityQueueWithHeapq: 4 | def __init__(self): 5 | self.queue = [] # Ініціалізуємо порожній список для черги 6 | 7 | def enqueue(self, element): # Часова складність: O(log n) 8 | heapq.heappush(self.queue, -element) # Використовуємо мін-купу heapq для макс-черги (використовуючи негативні значення) 9 | 10 | def peek(self): # Часова складність: O(1) 11 | return -self.queue[0] if self.queue else None # Повертаємо найбільший елемент (перший у черзі) 12 | 13 | def dequeue(self): # Часова складність: O(log n) 14 | return -heapq.heappop(self.queue) if self.queue else None # Видаляємо і повертаємо найбільший елемент 15 | 16 | def is_empty(self): # Часова складність: O(1) 17 | return len(self.queue) == 0 # Перевіряємо, чи черга порожня 18 | 19 | def change_priority(self, old, new): # Часова складність: O(n) 20 | try: 21 | index = self.queue.index(-old) # Знаходимо індекс старого значення 22 | self.queue[index] = -new # Оновлюємо значення 23 | if new > old: 24 | heapq._siftup(self.queue, index) # Застосовуємо siftup, якщо нове значення більше 25 | else: 26 | heapq._siftdown(self.queue, 0, index) # Застосовуємо siftdown, якщо нове значення менше 27 | except ValueError: 28 | pass # Якщо старе значення не знайдено, нічого не робимо 29 | 30 | # Тестова функція для черги з пріоритетами 31 | def test_priority_queue(): 32 | pq = PriorityQueueWithHeapq() 33 | 34 | # Додаємо елементи в чергу 35 | pq.enqueue(5) 36 | pq.enqueue(3) 37 | pq.enqueue(8) 38 | pq.enqueue(1) 39 | 40 | # Перевіряємо вміст черги 41 | print([-elem for elem in pq.queue]) # Очікуємо [8, 3, 5, 1] 42 | 43 | # Перевіряємо найбільший елемент 44 | assert pq.peek() == 8, "Test failed: peek" 45 | 46 | # Виймаємо найбільший елемент 47 | assert pq.dequeue() == 8, "Test failed: dequeue" 48 | print([-elem for elem in pq.queue]) # Очікуємо [5, 3, 1] 49 | 50 | # Перевіряємо чергу на порожність 51 | assert not pq.is_empty(), "Test failed: is_empty" 52 | 53 | # Зміна пріоритету за значенням 54 | pq.change_priority(5, 7) # Зміна пріоритету елемента зі значенням 5 на 7 55 | print([-elem for elem in pq.queue]) # Очікуємо [7, 3, 1] 56 | 57 | # Виймаємо всі елементи по черзі 58 | assert pq.dequeue() == 7, "Test failed: dequeue" 59 | assert pq.dequeue() == 3, "Test failed: dequeue" 60 | assert pq.dequeue() == 1, "Test failed: dequeue" 61 | assert pq.is_empty(), "Test failed: is_empty" 62 | 63 | print("All tests passed.") 64 | 65 | # Виконання тестової функції 66 | test_priority_queue() 67 | -------------------------------------------------------------------------------- /heap/max_heap.py: -------------------------------------------------------------------------------- 1 | class MaxHeap: 2 | def __init__(self, arr=None): # Часова складність: O(n) 3 | # Ініціалізація макс-купи, створюючи порожній список 4 | self.heap = [] 5 | if type(arr) is list: # Якщо переданий масив 6 | self.heap = arr.copy() # Копіюємо масив у купу 7 | for i in range(len(self.heap))[::-1]: # Перебираємо всі елементи у зворотньому порядку 8 | self._siftdown(i) # Застосовуємо siftdown до кожного елемента 9 | 10 | def _siftup(self, i): # Часова складність: O(log n) 11 | # Операція siftup для підтримки властивості купи 12 | parent = (i-1)//2 # Обчислення індексу батьківського вузла 13 | # Поки елемент більший за батьківський 14 | while i != 0 and self.heap[i] > self.heap[parent]: 15 | # Міняємо місцями елемент і батька 16 | self.heap[i], self.heap[parent] = self.heap[parent], self.heap[i] 17 | i = parent # Перехід до батьківського вузла 18 | parent = (i-1)//2 # Оновлення індексу батьківського вузла 19 | 20 | def _siftdown(self, i): # Часова складність: O(log n) 21 | # Операція siftdown для підтримки властивості купи 22 | left = 2*i + 1 # Лівий дочірній вузол 23 | right = 2*i + 2 # Правий дочірній вузол 24 | # Поки елемент менший за будь-який з дочірніх 25 | while (left < len(self.heap) and self.heap[i] < self.heap[left]) or (right < len(self.heap) and self.heap[i] < self.heap[right]): 26 | # Визначення найбільшого дочірнього вузла 27 | biggest = left if (right >= len(self.heap) or self.heap[left] > self.heap[right]) else right 28 | # Міняємо місцями елемент і найбільший дочірній вузол 29 | self.heap[i], self.heap[biggest] = self.heap[biggest], self.heap[i] 30 | i = biggest # Перехід до дочірнього вузла 31 | left = 2*i + 1 # Оновлення лівого дочірнього вузла 32 | right = 2*i + 2 # Оновлення правого дочірнього вузла 33 | 34 | def insert(self, element): # Часова складність: O(log n) 35 | # Вставка нового елемента в купу 36 | self.heap.append(element) # Додаємо елемент у кінець купи 37 | self._siftup(len(self.heap)-1) # Застосовуємо siftup до нового елемента 38 | 39 | def get_max(self): # Часова складність: O(1) 40 | # Повернення найбільшого елемента (кореня купи) 41 | return self.heap[0] if len(self.heap) > 0 else None # Перевірка на порожність і повернення кореня 42 | 43 | def extract_max(self): # Часова складність: O(log n) 44 | # Витяг найбільшого елемента з купи 45 | if len(self.heap) == 0: # Якщо купа порожня 46 | return None # Повертаємо None 47 | maxval = self.heap[0] # Збереження найбільшого елемента (кореня) 48 | self.heap[0], self.heap[-1] = self.heap[-1], self.heap[0] # Міняємо місцями корінь і останній елемент 49 | self.heap.pop() # Видаляємо останній елемент (колишній корінь) 50 | self._siftdown(0) # Застосовуємо siftdown до нового кореня 51 | return maxval # Повертаємо найбільший елемент 52 | 53 | def update_by_index(self, i, new): # Часова складність: O(log n) 54 | # Оновлення значення елемента за індексом 55 | old = self.heap[i] # Збереження старого значення 56 | self.heap[i] = new # Оновлення значення 57 | if new > old: # Якщо нове значення більше старого 58 | self._siftup(i) # Застосовуємо siftup 59 | else: # Якщо нове значення менше або рівне старому 60 | self._siftdown(i) # Застосовуємо siftdown 61 | 62 | def update(self, old, new): # Часова складність: O(n) 63 | # Оновлення значення елемента за старим значенням 64 | if old in self.heap: # Якщо старе значення є в купі 65 | self.update_by_index(self.heap.index(old), new) # Знаходимо індекс старого значення і оновлюємо 66 | 67 | if __name__ == '__main__': 68 | # Тести для MaxHeap 69 | print("\nTesting MaxHeap:") 70 | max_heap = MaxHeap() # Створюємо порожню макс-купку 71 | max_heap.insert(5) # Додаємо елемент 5 72 | max_heap.insert(3) # Додаємо елемент 3 73 | max_heap.insert(8) # Додаємо елемент 8 74 | max_heap.insert(1) # Додаємо елемент 1 75 | print(max_heap.heap) # Очікуємо [8, 3, 5, 1] 76 | 77 | print(max_heap.get_max()) # Очікуємо 8, найбільший елемент 78 | 79 | print(max_heap.extract_max()) # Очікуємо 8, видалення найбільшого елемента 80 | print(max_heap.heap) # Очікуємо [5, 3, 1] 81 | 82 | max_heap.update(3, 6) # Оновлюємо значення 3 на 6 83 | print(max_heap.heap) # Очікуємо [6, 5, 1] 84 | 85 | # Додаткові тести для MaxHeap з початковим масивом 86 | print("\nTesting MaxHeap with initial array:") 87 | initial_array = [9, 4, 7, 1, 3, 6, 2] 88 | max_heap_from_array = MaxHeap(initial_array) # Створюємо макс-купку з початковим масивом 89 | print(max_heap_from_array.heap) # Очікуємо [9, 4, 7, 1, 3, 6, 2] 90 | -------------------------------------------------------------------------------- /heap/min_heap.py: -------------------------------------------------------------------------------- 1 | class MinHeap: 2 | def __init__(self, arr=None): # Часова складність: O(n) 3 | # Ініціалізація мін-купи, створюючи порожній список 4 | self.heap = [] 5 | if type(arr) is list: # Якщо переданий масив 6 | self.heap = arr.copy() # Копіюємо масив у купу 7 | for i in range(len(self.heap))[::-1]: # Перебираємо всі елементи у зворотньому порядку 8 | self._siftdown(i) # Застосовуємо siftdown до кожного елемента 9 | 10 | def _siftup(self, i): # Часова складність: O(log n) 11 | # Операція siftup для підтримки властивості купи 12 | parent = (i-1)//2 # Обчислення індексу батьківського вузла 13 | # Поки елемент менший за батьківський 14 | while i != 0 and self.heap[i] < self.heap[parent]: 15 | # Міняємо місцями елемент і батька 16 | self.heap[i], self.heap[parent] = self.heap[parent], self.heap[i] 17 | i = parent # Перехід до батьківського вузла 18 | parent = (i-1)//2 # Оновлення індексу батьківського вузла 19 | 20 | def _siftdown(self, i): # Часова складність: O(log n) 21 | # Операція siftdown для підтримки властивості купи 22 | left = 2*i + 1 # Лівий дочірній вузол 23 | right = 2*i + 2 # Правий дочірній вузол 24 | # Поки елемент більший за будь-який з дочірніх 25 | while (left < len(self.heap) and self.heap[i] > self.heap[left]) or (right < len(self.heap) and self.heap[i] > self.heap[right]): 26 | # Визначення найменшого дочірнього вузла 27 | smallest = left if (right >= len(self.heap) or self.heap[left] < self.heap[right]) else right 28 | # Міняємо місцями елемент і найменший дочірній вузол 29 | self.heap[i], self.heap[smallest] = self.heap[smallest], self.heap[i] 30 | i = smallest # Перехід до дочірнього вузла 31 | left = 2*i + 1 # Оновлення лівого дочірнього вузла 32 | right = 2*i + 2 # Оновлення правого дочірнього вузла 33 | 34 | def insert(self, element): # Часова складність: O(log n) 35 | # Вставка нового елемента в купу 36 | self.heap.append(element) # Додаємо елемент у кінець купи 37 | self._siftup(len(self.heap)-1) # Застосовуємо siftup до нового елемента 38 | 39 | def get_min(self): # Часова складність: O(1) 40 | # Повернення найменшого елемента (кореня купи) 41 | return self.heap[0] if len(self.heap) > 0 else None # Перевірка на порожність і повернення кореня 42 | 43 | def extract_min(self): # Часова складність: O(log n) 44 | # Витяг найменшого елемента з купи 45 | if len(self.heap) == 0: # Якщо купа порожня 46 | return None # Повертаємо None 47 | minval = self.heap[0] # Збереження найменшого елемента (кореня) 48 | self.heap[0], self.heap[-1] = self.heap[-1], self.heap[0] # Міняємо місцями корінь і останній елемент 49 | self.heap.pop() # Видаляємо останній елемент (колишній корінь) 50 | self._siftdown(0) # Застосовуємо siftdown до нового кореня 51 | return minval # Повертаємо найменший елемент 52 | 53 | def update_by_index(self, i, new): # Часова складність: O(log n) 54 | # Оновлення значення елемента за індексом 55 | old = self.heap[i] # Збереження старого значення 56 | self.heap[i] = new # Оновлення значення 57 | if new < old: # Якщо нове значення менше старого 58 | self._siftup(i) # Застосовуємо siftup 59 | else: # Якщо нове значення більше або рівне старому 60 | self._siftdown(i) # Застосовуємо siftdown 61 | 62 | def update(self, old, new): # Часова складність: O(n) 63 | # Оновлення значення елемента за старим значенням 64 | if old in self.heap: # Якщо старе значення є в купі 65 | # Знаходимо індекс старого значення і оновлюємо 66 | self.update_by_index(self.heap.index(old), new) 67 | 68 | 69 | if __name__ == '__main__': 70 | # Тести для MinHeap 71 | print("Testing MinHeap:") 72 | min_heap = MinHeap() # Створюємо порожню мін-купку 73 | min_heap.insert(5) # Додаємо елемент 5 74 | min_heap.insert(3) # Додаємо елемент 3 75 | min_heap.insert(8) # Додаємо елемент 8 76 | min_heap.insert(1) # Додаємо елемент 1 77 | print(min_heap.heap) # Очікуємо [1, 3, 8, 5] 78 | 79 | print(min_heap.get_min()) # Очікуємо 1, найменший елемент 80 | 81 | print(min_heap.extract_min()) # Очікуємо 1, видалення найменшого елемента 82 | print(min_heap.heap) # Очікуємо [3, 5, 8] 83 | 84 | min_heap.update(5, 2) # Оновлюємо значення 5 на 2 85 | print(min_heap.heap) # Очікуємо [2, 3, 8] 86 | 87 | # Додаткові тести для MinHeap з початковим масивом 88 | print("\nTesting MinHeap with initial array:") 89 | initial_array = [9, 4, 7, 1, 3, 6, 2] 90 | min_heap_from_array = MinHeap(initial_array) # Створюємо мін-купку з початковим масивом 91 | print(min_heap_from_array.heap) # Очікуємо [1, 3, 2, 4, 9, 6, 7] 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /heap/priority_queue.py: -------------------------------------------------------------------------------- 1 | from max_heap import MaxHeap 2 | 3 | class PriorityQueue: 4 | def __init__(self): # Часова складність: O(1) 5 | # Ініціалізація черги з пріоритетами за допомогою макс-купи 6 | self.queue = MaxHeap() # Створюємо порожню макс-купу 7 | 8 | def enqueue(self, element): # Часова складність: O(log n) 9 | # Додаємо елемент у чергу 10 | self.queue.insert(element) # Вставка елемента в макс-купу 11 | 12 | def peek(self): # Часова складність: O(1) 13 | # Повертаємо найбільший елемент (корінь купи) без видалення 14 | return self.queue.get_max() # Отримання найбільшого елемента з макс-купи 15 | 16 | def dequeue(self, element): # Часова складність: O(log n) 17 | # Видаляємо і повертаємо найбільший елемент з черги 18 | return self.queue.extract_max() # Витяг найбільшого елемента з макс-купи 19 | 20 | def is_empty(self): # Часова складність: O(1) 21 | # Перевіряємо, чи черга порожня 22 | return len(self.queue.heap) == 0 # Повертаємо True, якщо купка порожня, інакше False 23 | 24 | def change_priority_by_index(self, i, new): # Часова складність: O(log n) 25 | # Зміна пріоритету елемента за індексом 26 | self.queue.update_by_index(i, new) # Оновлення значення елемента в макс-купі за індексом 27 | 28 | def change_priority(self, old, new): # Часова складність: O(n) 29 | # Зміна пріоритету елемента за старим значенням 30 | self.queue.update(old, new) # Оновлення значення елемента в макс-купі за старим значенням 31 | 32 | if __name__ == '__main__': 33 | # Тести для PriorityQueue 34 | print("Testing PriorityQueue:") 35 | priority_queue = PriorityQueue() # Створюємо чергу з пріоритетами 36 | 37 | # Додаємо елементи в чергу 38 | priority_queue.enqueue(5) 39 | priority_queue.enqueue(3) 40 | priority_queue.enqueue(8) 41 | priority_queue.enqueue(1) 42 | 43 | # Перевіряємо вміст черги 44 | print(priority_queue.queue.heap) # Очікуємо [8, 3, 5, 1] 45 | 46 | # Перевіряємо найбільший елемент 47 | print(priority_queue.peek()) # Очікуємо 8 48 | 49 | # Виймаємо найбільший елемент 50 | print(priority_queue.dequeue(None)) # Очікуємо 8 51 | print(priority_queue.queue.heap) # Очікуємо [5, 3, 1] 52 | 53 | # Перевіряємо чергу на порожність 54 | print(priority_queue.is_empty()) # Очікуємо False 55 | 56 | # Зміна пріоритету за індексом 57 | priority_queue.change_priority_by_index(1, 6) # Зміна пріоритету елемента з індексом 1 на 6 58 | print(priority_queue.queue.heap) # Очікуємо [6, 5, 1] 59 | 60 | # Зміна пріоритету за значенням 61 | priority_queue.change_priority(5, 7) # Зміна пріоритету елемента зі значенням 5 на 7 62 | print(priority_queue.queue.heap) # Очікуємо [7, 6, 1] 63 | 64 | # Виймаємо всі елементи по черзі 65 | print(priority_queue.dequeue(None)) # Очікуємо 7 66 | print(priority_queue.dequeue(None)) # Очікуємо 6 67 | print(priority_queue.dequeue(None)) # Очікуємо 1 68 | print(priority_queue.is_empty()) # Очікуємо True 69 | 70 | # Додаткові тести з початковим масивом 71 | print("\nTesting PriorityQueue with initial array:") 72 | initial_array = [9, 4, 7, 1, 3, 6, 2] 73 | priority_queue_from_array = PriorityQueue() 74 | for elem in initial_array: 75 | priority_queue_from_array.enqueue(elem) 76 | 77 | print(priority_queue_from_array.queue.heap) # Очікуємо [9, 4, 7, 1, 3, 6, 2] 78 | 79 | # Перевіряємо найбільший елемент у черзі з початковим масивом 80 | print(priority_queue_from_array.peek()) 81 | -------------------------------------------------------------------------------- /linear_random/miller_rabin_test.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | def is_prime(n, k=5): 4 | """Перевірка простоти числа n за допомогою алгоритму Рабіна-Міллера.""" 5 | 6 | # Якщо n < 2, воно не може бути простим 7 | if n < 2: 8 | return False 9 | # Якщо n == 2 або n == 3, воно є простим 10 | if n in (2, 3): 11 | return True 12 | # Якщо n парне, воно не є простим 13 | if n % 2 == 0: 14 | return False 15 | 16 | # 1) Представляємо число n як n-1 = 2^r * d, де d — непарне число. 17 | r, d = 0, n - 1 18 | while d % 2 == 0: 19 | d //= 2 20 | r += 1 21 | 22 | # def fermat_test(n, k=5): 23 | # if n <= 1: 24 | # return False 25 | # for _ in range(k): 26 | # a = random.randint(1, n - 1) 27 | # if pow(a, n - 1, n) != 1: 28 | # return False 29 | # return True 30 | 31 | 32 | # Перевіряємо k випадкових баз 33 | for _ in range(k): 34 | # 2) Виберіть випадкове число a з діапазону [2, n−2]. 35 | a = random.randint(2, n - 2) 36 | # 3) Обчисліть x = a^m mod n. Якщо x=1 або x=n−1, то перейдіть до наступного кроку. 37 | x = pow(a, d, n) 38 | if x == 1 or x == n - 1: 39 | continue 40 | # 4) Повторіть q разів: обчисліт x=x^2 modn. Якщо x=n−1, то перейдіть до наступного кроку. 41 | for _ in range(r - 1): 42 | x = pow(x, 2, n) 43 | if x == n - 1: 44 | break 45 | else: # Якщо попередній крок не вдалося завершити, то n є складеним. 46 | return False 47 | # Якщо жоден із кроків не визначив n як складене, то n ймовірно просте. 48 | return True 49 | 50 | 51 | 52 | # Тестування функції 53 | number = 61 54 | iterations = 10 55 | if is_prime(number, iterations): 56 | print(f"Число {number} ймовірно є простим.") 57 | else: 58 | print(f"Число {number} є складеним.") 59 | -------------------------------------------------------------------------------- /linear_random/monte_carlo.py: -------------------------------------------------------------------------------- 1 | """ 2 | Розглянемо квадрат зі стороною 2, центр якого співпадає з центром вписаного в 3 | нього кола радіусом 1. Якщо випадково кидати точки на площині, то відношення кількості точок, 4 | що потрапили в коло, до загальної кількості точок буде приблизно дорівнювати 5 | відношенню площі кола до площі квадрата, тобто π/4. 6 | """ 7 | 8 | import random 9 | import matplotlib.pyplot as plt 10 | 11 | def monte_carlo_pi(num_samples): 12 | # 1. Визначення моделі або системи. 13 | inside_circle = 0 14 | x_inside, y_inside = [], [] 15 | x_outside, y_outside = [], [] 16 | # 2. Генерація випадкових вхідних даних 17 | for _ in range(num_samples): 18 | x = random.uniform(-1, 1) 19 | y = random.uniform(-1, 1) 20 | # 3. Виконання обчислень 21 | if x**2 + y**2 <= 1: 22 | inside_circle += 1 23 | x_inside.append(x) 24 | y_inside.append(y) 25 | else: 26 | x_outside.append(x) 27 | y_outside.append(y) 28 | 29 | # 4. Агрегування та аналіз результатів 30 | pi_estimate = 4 * inside_circle / num_samples 31 | return pi_estimate, x_inside, y_inside, x_outside, y_outside 32 | 33 | # Задаємо кількість випадкових точок 34 | num_samples = 10 35 | 36 | # Запускаємо метод Монте-Карло для обчислення π 37 | pi_estimate, x_inside, y_inside, x_outside, y_outside = monte_carlo_pi(num_samples) 38 | 39 | # Виводимо результат 40 | print(f"Оцінка значення π за методом Монте-Карло з {num_samples} випадкових точок: {pi_estimate}") 41 | 42 | # Візуалізація результатів 43 | plt.figure(figsize=(8, 8)) 44 | plt.scatter(x_inside, y_inside, color='blue', s=1, label='Точки всередині кола') 45 | plt.scatter(x_outside, y_outside, color='red', s=1, label='Точки поза колом') 46 | circle = plt.Circle((0, 0), 1, color='black', fill=False, linewidth=2) 47 | plt.gca().add_patch(circle) 48 | plt.xlim(-1, 1) 49 | plt.ylim(-1, 1) 50 | plt.gca().set_aspect('equal', adjustable='box') 51 | plt.title(f"Метод Монте-Карло для оцінки π\nЧисло точок: {num_samples}\nОцінка π: {pi_estimate}") 52 | plt.legend() 53 | plt.show() 54 | 55 | 56 | """ 57 | - У фінансовій сфері метод застосовується для оцінки ризиків, 58 | ціноутворення опціонів та інших фінансових інструментів. 59 | 60 | Припустимо, у нас є європейський опціон типу "call" на акцію, поточна ціна якої S0. 61 | Страйк ціна опціону K, опціон закінчується через T років, безризикова процентна ставка r, 62 | а волатильність акції σ. 63 | Ціль: оцінити вартість опціону за допомогою методу Монте-Карло. 64 | """ 65 | 66 | # import numpy as np 67 | # import matplotlib.pyplot as plt 68 | # 69 | # # Параметри опціону 70 | # S0 = 100 # Поточна ціна акції 71 | # K = 110 # Страйк ціна 72 | # T = 1 # Час до закінчення опціону в роках 73 | # r = 0.05 # Безризикова процентна ставка 74 | # sigma = 0.2 # Волатильність 75 | # 76 | # # Параметри симуляції 77 | # num_simulations = 10000 # Кількість симуляцій 78 | # num_steps = 252 # Кількість кроків на рік (щоденні кроки) 79 | # 80 | # # Функція для обчислення ціни опціону методом Монте-Карло 81 | # def monte_carlo_option_pricing(S0, K, T, r, sigma, num_simulations, num_steps): 82 | # dt = T / num_steps # Часовий крок 83 | # prices = np.zeros((num_simulations, num_steps + 1)) 84 | # prices[:, 0] = S0 85 | # 86 | # for t in range(1, num_steps + 1): 87 | # z = np.random.standard_normal(num_simulations) 88 | # prices[:, t] = prices[:, t - 1] * np.exp((r - 0.5 * sigma**2) * dt + sigma * np.sqrt(dt) * z) 89 | # 90 | # payoffs = np.maximum(prices[:, -1] - K, 0) # Payoff для опціону call 91 | # option_price = np.exp(-r * T) * np.mean(payoffs) # Дисконтована середня вартість 92 | # 93 | # return option_price, prices 94 | # 95 | # # Обчислення ціни опціону 96 | # option_price, prices = monte_carlo_option_pricing(S0, K, T, r, sigma, num_simulations, num_steps) 97 | # 98 | # # Вивід результату 99 | # print(f"Оцінена вартість опціону (call) методом Монте-Карло: {option_price:.2f} $") 100 | # 101 | # # Візуалізація кількох симуляцій 102 | # plt.figure(figsize=(10, 6)) 103 | # for i in range(min(num_simulations, 10)): # Відобразимо тільки 10 симуляцій 104 | # plt.plot(prices[i, :]) 105 | # plt.title('Симуляції цін акцій за методом Монте-Карло') 106 | # plt.xlabel('Часові кроки') 107 | # plt.ylabel('Ціна акції') 108 | # plt.show() 109 | 110 | 111 | """ 112 | Використовуючи велику кількість випадкових вибірок, можна оцінити ймовірності 113 | подій та різні статистичні характеристики, такі як середнє значення, дисперсія, медіана тощо. 114 | 115 | 116 | Припустимо, що ми хочемо оцінити майбутню вартість портфеля акцій через рік. 117 | Ми знаємо початкову вартість портфеля, середню річну дохідність та волатильність 118 | (стандартне відхилення річної дохідності). За допомогою методу Монте-Карло ми 119 | зможемо оцінити розподіл майбутніх вартостей портфеля, а також обчислити такі 120 | статистичні характеристики, як середнє значення, дисперсія та медіана. 121 | """ 122 | 123 | # import numpy as np 124 | # import matplotlib.pyplot as plt 125 | # 126 | # # Параметри портфеля 127 | # initial_portfolio_value = 100000 # Початкова вартість портфеля 128 | # expected_return = 0.07 # Середня річна дохідність 129 | # volatility = 0.15 # Річна волатильність (стандартне відхилення) 130 | # 131 | # # Параметри симуляції 132 | # num_simulations = 10000 # Кількість симуляцій 133 | # num_years = 1 # Період симуляції у роках 134 | # 135 | # # Функція для симуляції майбутньої вартості портфеля методом Монте-Карло 136 | # def monte_carlo_portfolio_value(initial_value, expected_return, volatility, num_simulations, num_years): 137 | # dt = 1 / 252 # Часовий крок (252 торгових дні в році) 138 | # num_steps = int(num_years / dt) # Кількість кроків 139 | # portfolio_values = np.zeros((num_simulations, num_steps + 1)) 140 | # portfolio_values[:, 0] = initial_value 141 | # 142 | # for t in range(1, num_steps + 1): 143 | # z = np.random.standard_normal(num_simulations) 144 | # portfolio_values[:, t] = portfolio_values[:, t - 1] * np.exp((expected_return - 0.5 * volatility**2) * dt + volatility * np.sqrt(dt) * z) 145 | # 146 | # final_values = portfolio_values[:, -1] 147 | # return final_values 148 | # 149 | # # Симуляція майбутньої вартості портфеля 150 | # final_portfolio_values = monte_carlo_portfolio_value(initial_portfolio_value, expected_return, volatility, num_simulations, num_years) 151 | # 152 | # # Обчислення статистичних характеристик 153 | # mean_value = np.mean(final_portfolio_values) 154 | # variance_value = np.var(final_portfolio_values) 155 | # median_value = np.median(final_portfolio_values) 156 | # 157 | # # Вивід результатів 158 | # print(f"Середнє значення майбутньої вартості портфеля: {mean_value:.2f} $") 159 | # print(f"Дисперсія майбутньої вартості портфеля: {variance_value:.2f}") 160 | # print(f"Медіана майбутньої вартості портфеля: {median_value:.2f} $") 161 | # 162 | # # Візуалізація розподілу майбутньої вартості портфеля 163 | # plt.figure(figsize=(10, 6)) 164 | # plt.hist(final_portfolio_values, bins=50, edgecolor='k', alpha=0.7) 165 | # plt.axvline(mean_value, color='r', linestyle='dashed', linewidth=2, label=f'Середнє значення: {mean_value:.2f} $') 166 | # plt.axvline(median_value, color='g', linestyle='dashed', linewidth=2, label=f'Медіана: {median_value:.2f} $') 167 | # plt.title('Розподіл майбутньої вартості портфеля за методом Монте-Карло') 168 | # plt.xlabel('Майбутня вартість портфеля ($)') 169 | # plt.ylabel('Частота') 170 | # plt.legend() 171 | # plt.show() 172 | -------------------------------------------------------------------------------- /linear_random/simplex.py: -------------------------------------------------------------------------------- 1 | """ 2 | Завдання 3 | Уявімо, що у нас є компанія, яка виробляє два види продуктів: A та B. 4 | Для виробництва цих продуктів використовуються три види ресурсів: 5 | ресурс 1, ресурс 2 та ресурс 3. Кожен продукт приносить певний прибуток, 6 | і ми хочемо максимізувати загальний прибуток. 7 | 8 | Вхідні дані: 9 | Продукт A приносить прибуток $3 за одиницю, продукт B - $5 за одиницю. 10 | Кількість ресурсів: 11 | Ресурс 1: 4 одиниці. 12 | Ресурс 2: 12 одиниць. 13 | Ресурс 3: 18 одиниць. 14 | Продукт A потребує: 15 | Ресурс 1: 1 одиницю на одиницю продукту. 16 | Ресурс 2: 3 одиниці на одиницю продукту. 17 | Ресурс 3: 2 одиниці на одиницю продукту. 18 | Продукт B потребує: 19 | Ресурс 1: 2 одиниці на одиницю продукту. 20 | Ресурс 2: 2 одиниці на одиницю продукту. 21 | Ресурс 3: 4 одиниці на одиницю продукту. 22 | Постановка задачі: 23 | Ми хочемо знайти оптимальні кількості продуктів A і B, які потрібно виробити, 24 | щоб максимізувати загальний прибуток, не перевищуючи доступні ресурси. 25 | 26 | Максимізувати: 3A + 5B 27 | З обмеженнями: 28 | 1) 1A+2B≤4 29 | 2) 3A+2B≤12 30 | 3) 2A+4B≤18 31 | 4) A≥0,B≥0 32 | """ 33 | 34 | 35 | import numpy as np 36 | 37 | def simplex(c, A, b): 38 | """ 39 | Реалізація симплекс-методу для задачі лінійного програмування 40 | """ 41 | m, n = A.shape 42 | 43 | # Створюємо таблицю симплекс-методу 44 | tableau = np.zeros((m+1, n+1)) 45 | tableau[:-1, :-1] = A 46 | tableau[:-1, -1] = b 47 | tableau[-1, :-1] = -c 48 | 49 | # Функція для знаходження стовпця з найменшим значенням у останньому рядку 50 | def get_pivot_column(): 51 | return np.argmin(tableau[-1, :-1]) 52 | 53 | # Функція для знаходження рядка з мінімальним відношенням останнього стовпця до відповідного елементу в обраному стовпці 54 | def get_pivot_row(pivot_col): 55 | rows = np.where(tableau[:-1, pivot_col] > 0)[0] 56 | return rows[np.argmin(tableau[rows, -1] / tableau[rows, pivot_col])] 57 | 58 | # Поки є від'ємні значення у останньому рядку, продовжуємо ітерації 59 | while np.min(tableau[-1, :-1]) < 0: 60 | pivot_col = get_pivot_column() 61 | pivot_row = get_pivot_row(pivot_col) 62 | 63 | pivot_value = tableau[pivot_row, pivot_col] 64 | tableau[pivot_row] /= pivot_value 65 | 66 | for row in range(m+1): 67 | if row != pivot_row: 68 | tableau[row] -= tableau[row, pivot_col] * tableau[pivot_row] 69 | 70 | # Оптимальні значення змінних 71 | x = np.zeros(n) 72 | for i in range(n): 73 | col = tableau[:-1, i] 74 | if np.sum(col == 1) == 1 and np.sum(col == 0) == m-1: 75 | x[i] = tableau[np.where(col == 1)[0][0], -1] 76 | 77 | # Оптимальне значення цільової функції 78 | max_value = tableau[-1, -1] 79 | 80 | return np.round(x).astype(int), int(round(max_value)) 81 | 82 | # Вхідні дані 83 | c = np.array([3, 5]) 84 | A = np.array([ 85 | [1, 2], 86 | [3, 2], 87 | [2, 4] 88 | ]) 89 | b = np.array([8, 12, 18]) 90 | 91 | # Розв'язання задачі симплекс методом 92 | x, max_value = simplex(c, A, b) 93 | 94 | print(f"Оптимальна кількість продукту A: {x[0]}") 95 | print(f"Оптимальна кількість продукту B: {x[1]}") 96 | print(f"Максимальний прибуток: {max_value}") 97 | 98 | 99 | # import pulp 100 | # 101 | # # Ініціалізація моделі 102 | # model = pulp.LpProblem("Maximize Profit", pulp.LpMaximize) 103 | # 104 | # # Визначення змінних 105 | # A = pulp.LpVariable('A', lowBound=0, cat='Integer') # Кількість продукту A 106 | # B = pulp.LpVariable('B', lowBound=0, cat='Integer') # Кількість продукту B 107 | # 108 | # # Функція цілі (Максимізація прибутку) 109 | # model += 3 * A + 5 * B, "Profit" 110 | # 111 | # # Додавання обмежень 112 | # model += 1 * A + 2 * B <= 8 # Обмеження по ресурсу 1 113 | # model += 3 * A + 2 * B <= 12 # Обмеження по ресурсу 2 114 | # model += 2 * A + 4 * B <= 18 # Обмеження по ресурсу 3 115 | # 116 | # # Розв'язання моделі 117 | # model.solve() 118 | # 119 | # # Вивід результатів 120 | # print("Виробляти продуктів A:", A.varValue) 121 | # print("Виробляти продуктів B:", B.varValue) 122 | # print("Максимальний прибуток:", pulp.value(model.objective)) 123 | -------------------------------------------------------------------------------- /linear_random/universal_hash.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | class UniversalHash: 4 | def __init__(self, m, max_key): 5 | self.m = m 6 | self.p = self._next_prime(max_key) 7 | self.a = random.randint(1, self.p - 1) 8 | self.b = random.randint(0, self.p - 1) 9 | 10 | def _next_prime(self, n): 11 | while True: 12 | n += 1 13 | for i in range(2, int(n ** 0.5) + 1): 14 | if n % i == 0: 15 | break 16 | else: 17 | return n 18 | 19 | def hash(self, key): 20 | return ((self.a * key + self.b) % self.p) % self.m 21 | 22 | # Приклад використання: 23 | hasher = UniversalHash(100, 1000) 24 | print(hasher.hash(123)) 25 | print(hasher.hash(456)) 26 | -------------------------------------------------------------------------------- /recursion/Sierpinski triangle.py: -------------------------------------------------------------------------------- 1 | import turtle 2 | def draw_sierpinski(length,depth): 3 | if depth==0: 4 | for i in range(0,3): 5 | t.fd(length) 6 | t.left(120) 7 | else: 8 | draw_sierpinski(length/2,depth-1) 9 | t.fd(length/2) 10 | draw_sierpinski(length/2,depth-1) 11 | t.bk(length/2) 12 | t.left(60) 13 | t.fd(length/2) 14 | t.right(60) 15 | draw_sierpinski(length/2,depth-1) 16 | t.left(60) 17 | t.bk(length/2) 18 | t.right(60) 19 | 20 | window = turtle.Screen() 21 | t = turtle.Turtle() 22 | draw_sierpinski(100,3) 23 | window.exitonclick() 24 | -------------------------------------------------------------------------------- /recursion/basic_drawing.py: -------------------------------------------------------------------------------- 1 | import turtle 2 | 3 | MAX_LENGTH = 300 4 | INCREMENT = 10 5 | 6 | 7 | def draw_spiral(a_turtle, line_length): 8 | if line_length > MAX_LENGTH: 9 | return 10 | a_turtle.forward(line_length) 11 | a_turtle.right(90) 12 | draw_spiral(a_turtle, line_length + INCREMENT) 13 | 14 | 15 | charlie = turtle.Turtle(shape="turtle") 16 | charlie.pensize(5) 17 | charlie.color("red") 18 | draw_spiral(charlie, 10) 19 | turtle.done() -------------------------------------------------------------------------------- /recursion/factorial.py: -------------------------------------------------------------------------------- 1 | # Iterative 2 | def factorial_iterative_while(n): # Condition-controlled version 3 | result = 1 4 | while n >= 1: 5 | result *= n 6 | n -= 1 7 | return result 8 | 9 | # Recursive 10 | def factorial_recursive(n): 11 | if n <= 1: 12 | # Base case 13 | return 1 14 | else: 15 | # Recursive case 16 | return n * factorial_recursive(n - 1) 17 | 18 | 19 | memory = {} 20 | def memoize_factorial(f): 21 | def inner(num): 22 | if num not in memory: 23 | memory[num] = f(num) 24 | print('result saved in memory') 25 | else: 26 | print('returning result from saved memory') 27 | return memory[num] 28 | return inner 29 | 30 | @memoize_factorial 31 | def factorial(n): 32 | if n <= 1: 33 | return 1 34 | else: 35 | return n * factorial(n - 1) 36 | 37 | 38 | # Let's do some basic testing 39 | print(factorial_iterative_while(4), factorial_recursive(4), factorial(4), sep="\t") 40 | print(factorial_iterative_while(6), factorial_recursive(6), factorial(6), sep="\t") 41 | print(factorial_iterative_while(1), factorial_recursive(1), factorial(1), sep="\t") 42 | print(factorial_iterative_while(0), factorial_recursive(0), factorial(0), sep="\t") 43 | print(factorial_iterative_while(-7), factorial_recursive(-7), factorial(-7), sep="\t") 44 | print(factorial_iterative_while(5), factorial_recursive(5), factorial(5), sep="\t") 45 | -------------------------------------------------------------------------------- /recursion/fibonachi.py: -------------------------------------------------------------------------------- 1 | import time 2 | from functools import lru_cache 3 | 4 | # https://stackoverflow.com/questions/360748/computational-complexity-of-fibonacci-sequence 5 | # F(6) * <-- only once 6 | # F(5) * <-- only once too 7 | # F(4) ** 8 | # F(3) **** 9 | # F(2) ******** 10 | # F(1) **************** <-- 16 11 | # F(0) ******************************** <-- 32 12 | # Now, in terms of complexity: 13 | # O( F(6) ) = O(2^6) 14 | # O( F(n) ) = O(2^n) 15 | 16 | def fibonacci(n): 17 | """ 18 | Returns the n-th Fibonacci number. 19 | """ 20 | if n == 0 or n == 1: 21 | return n 22 | first, second = 0, 1 23 | 24 | for i in range(n): 25 | first, second = second, first + second 26 | return first 27 | 28 | 29 | def fibonacci_recursive(n): 30 | """ 31 | Returns the n-th Fibonacci number. 32 | """ 33 | if n == 0 or n == 1: 34 | return n 35 | return fibonacci_recursive(n - 1) + fibonacci_recursive(n - 2) 36 | 37 | 38 | @lru_cache 39 | def fibonacci_lru(n): 40 | """ 41 | Returns the n-th Fibonacci number. 42 | """ 43 | if n == 0 or n == 1: 44 | return n 45 | return fibonacci_lru(n - 1) + fibonacci_lru(n - 2) 46 | 47 | 48 | # Manual caching using a dictionary. 49 | def fibonacci_cache(n, cache=None): 50 | if cache is None: 51 | cache = {} 52 | if n in cache: 53 | return cache[n] 54 | if n == 0 or n == 1: 55 | return n 56 | result = fibonacci_cache(n - 1, cache) + fibonacci_cache(n - 2, cache) 57 | cache[n] = result 58 | return result 59 | 60 | 61 | n = 40 62 | 63 | start = time.perf_counter() 64 | fibonacci(n) 65 | end = time.perf_counter() 66 | print("Plain version. Seconds taken: {:.7f}".format(end - start)) 67 | 68 | start = time.perf_counter() 69 | fibonacci_recursive(n) 70 | end = time.perf_counter() 71 | print("Plain recursive version. Seconds taken: {:.7f}".format(end - start)) 72 | 73 | start = time.perf_counter() 74 | fibonacci_lru(n) 75 | end = time.perf_counter() 76 | print("lru cache version. Seconds taken: {:.7f}".format(end - start)) 77 | 78 | start = time.perf_counter() 79 | fibonacci_cache(n) 80 | end = time.perf_counter() 81 | print("Manual cache version. Seconds taken: {:.7f}".format(end - start)) -------------------------------------------------------------------------------- /recursion/file_generator.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | from pathlib import Path 3 | from random import randint, choice, choices 4 | # pip install numpy 5 | import numpy 6 | # pip install Pillow 7 | from PIL import Image 8 | 9 | MESSAGE = "Hello, Привіт" 10 | 11 | 12 | def get_random_filename(): 13 | random_value = '()+,-0123456789;=@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz' \ 14 | '{}~абвгдеєжзиіїйклмнопрстуфхцчшщьюяАБВГДЕЄЖЗИІЇЙКЛМНОПРСТУФХЦЧШЩЬЮЯ' 15 | return ''.join(choices(random_value, k=8)) 16 | 17 | 18 | def generate_text_files(path): 19 | documents = ('DOC', 'DOCX', 'TXT', 'PDF', 'XLSX', 'PPTX') 20 | with open(path / f"{get_random_filename()}.{choice(documents).lower()}", "wb") as f: 21 | f.write(MESSAGE.encode()) 22 | 23 | 24 | def generate_archive_files(path): 25 | archive = ('ZIP', 'GZTAR', 'TAR') 26 | shutil.make_archive(f"{path}/{get_random_filename()}", f'{choice(archive).lower()}', path) 27 | 28 | 29 | def generate_image(path): 30 | images = ('JPEG', 'PNG', 'JPG') 31 | image_array = numpy.random.rand(100, 100, 3) * 255 32 | image = Image.fromarray(image_array.astype('uint8')) 33 | image.save(f"{path}/{get_random_filename()}.{choice(images).lower()}") 34 | 35 | 36 | def generate_folders(path): 37 | folder_name = ['temp', 'folder', 'dir', 'tmp', 'OMG', 'is_it_true', 'no_way', 'find_it'] 38 | folder_path = Path( 39 | f"{path}/" + '/'.join(choices(folder_name, weights=[10, 10, 1, 1, 1, 1, 1, 1], k=randint(5, len(folder_name))))) 40 | folder_path.mkdir(parents=True, exist_ok=True) 41 | 42 | 43 | def generate_folder_forest(path): 44 | for i in range(0, randint(2, 5)): 45 | generate_folders(path) 46 | 47 | 48 | def generate_random_files(path): 49 | for i in range(3, randint(5, 7)): 50 | function_list = [generate_text_files, generate_archive_files, generate_image] 51 | choice(function_list)(path) 52 | 53 | 54 | def parse_folder_recursion(path): 55 | for elements in path.iterdir(): 56 | if elements.is_dir(): 57 | generate_random_files(path) 58 | parse_folder_recursion(elements) 59 | 60 | 61 | def exist_parent_folder(path): 62 | path.mkdir(parents=True, exist_ok=True) 63 | 64 | 65 | def file_generator(path): 66 | exist_parent_folder(path) 67 | generate_folder_forest(path) 68 | parse_folder_recursion(path) 69 | 70 | 71 | if __name__ == '__main__': 72 | parent_folder_path = Path("Temp") 73 | file_generator(parent_folder_path) 74 | -------------------------------------------------------------------------------- /recursion/interpolate_Julia.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goitacademy/Basic-Algorithms-and-Data-Structures-Neoversity/a10d4cdbac13640f50bb6bc4559702f1a7ce483e/recursion/interpolate_Julia.mp4 -------------------------------------------------------------------------------- /recursion/julia_quadratic_fractal.py: -------------------------------------------------------------------------------- 1 | # First variant 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | import matplotlib.animation as animation 5 | # 6 | # def julia_quadratic(zx, zy, cx, cy, threshold): 7 | # """Calculates whether the number z[0] = zx + i*zy with a constant c = x + i*y 8 | # belongs to the Julia set. In order to belong, the sequence 9 | # z[i + 1] = z[i]**2 + c, must not diverge after 'threshold' number of steps. 10 | # The sequence diverges if the absolute value of z[i+1] is greater than 4. 11 | # 12 | # :param float zx: the x component of z[0] 13 | # :param float zy: the y component of z[0] 14 | # :param float cx: the x component of the constant c 15 | # :param float cy: the y component of the constant c 16 | # :param int threshold: the number of iterations to considered it converged 17 | # """ 18 | # # initial conditions 19 | # z = complex(zx, zy) 20 | # c = complex(cx, cy) 21 | # 22 | # for i in range(threshold): 23 | # z = z ** 2 + c 24 | # if abs(z) > 4.: # it diverged 25 | # return i 26 | # 27 | # return threshold - 1 # it didn't diverge 28 | # 29 | # 30 | # x_start, y_start = -2, -2 # an interesting region starts here 31 | # width, height = 4, 4 # for 4 units up and right 32 | # density_per_unit = 200 # how many pixles per unit 33 | # 34 | # # real and imaginary axis 35 | # re = np.linspace(x_start, x_start + width, width * density_per_unit) 36 | # im = np.linspace(y_start, y_start + height, height * density_per_unit) 37 | # 38 | # threshold = 20 # max allowed iterations 39 | # frames = 100 # number of frames in the animation 40 | # 41 | # # we represent c as c = r*cos(a) + i*r*sin(a) = r*e^{i*a} 42 | # r = 0.7885 43 | # a = np.linspace(0, 2 * np.pi, frames) 44 | # 45 | # fig = plt.figure(figsize=(10, 10)) # instantiate a figure to draw 46 | # ax = plt.axes() # create an axes object 47 | # 48 | # 49 | # def animate(i): 50 | # ax.clear() # clear axes object 51 | # ax.set_xticks([], []) # clear x-axis ticks 52 | # ax.set_yticks([], []) # clear y-axis ticks 53 | # 54 | # X = np.empty((len(re), len(im))) # the initial array-like image 55 | # cx, cy = r * np.cos(a[i]), r * np.sin(a[i]) # the initial c number 56 | # 57 | # # iterations for the given threshold 58 | # for i in range(len(re)): 59 | # for j in range(len(im)): 60 | # X[i, j] = julia_quadratic(re[i], im[j], cx, cy, threshold) 61 | # 62 | # img = ax.imshow(X.T, interpolation="bicubic", cmap='magma') 63 | # return [img] 64 | # 65 | # 66 | # anim = animation.FuncAnimation(fig, animate, frames=frames, interval=50, blit=True) 67 | # anim.save('julia_set.gif', writer='imagemagick') 68 | 69 | 70 | # Second variant 71 | import argparse 72 | import numpy as np 73 | #pip install opencv-python 74 | import cv2 75 | from tqdm import tqdm 76 | 77 | 78 | def generate_colors(n): 79 | palette = [(0, 0, 0)] 80 | img = np.zeros([1, 1, 3], dtype=np.uint8) 81 | for i in range(n): 82 | img[:] = ((i/n) * 255, 255 * 0.85, 255) 83 | rgb = cv2.cvtColor(img, cv2.COLOR_HSV2BGR) 84 | palette.append((rgb[0,0][0], rgb[0,0][1], rgb[0,0][2])) 85 | 86 | return palette 87 | 88 | 89 | def interpolate(c1, c2, steps): 90 | delta1 = (c2[0] - c1[0]) / steps 91 | delta2 = (c2[1] - c1[1]) / steps 92 | res = [] 93 | cc1 = c1[0] 94 | cc2 = c1[1] 95 | for i in range(steps): 96 | res.append((cc1, cc2)) 97 | cc1 += delta1 98 | cc2 += delta2 99 | 100 | return res 101 | 102 | 103 | def process_julia(max_iter, c, palette, width, height): 104 | w, h, zoom = width, height, 0.7 105 | move_x = 0.0 106 | move_y = 0.0 107 | img = np.zeros([h, w, 3], dtype=np.uint8) 108 | 109 | c_x = c[0] 110 | c_y = c[1] 111 | for x in range(w): 112 | for y in range(h): 113 | zx = 1.5 * (x - w / 2) / (0.5 * zoom * w) + move_x 114 | zy = 1.0 * (y - h / 2) / (0.5 * zoom * h) + move_y 115 | i = max_iter 116 | while zx * zx + zy * zy < 20 and i > 1: 117 | tmp = zx * zx - zy * zy + c_x 118 | zy, zx = 2.0 * zx * zy + c_y, tmp 119 | i -= 1 120 | 121 | index = (i * len(palette)) // max_iter 122 | img[y, x] = (palette[index][0], palette[index][1], palette[index][2]) 123 | 124 | cv2.imshow('', img) 125 | cv2.waitKey(1) 126 | 127 | return img 128 | 129 | 130 | if __name__ == '__main__': 131 | parser = argparse.ArgumentParser() 132 | parser.add_argument('-o', '--output', default='interpolate_Julia.mp4', type=str, help='resulting video') 133 | parser.add_argument('-i', '--iterations', default=100, type=int, help='number of iterations') 134 | args = parser.parse_args() 135 | print(args) 136 | 137 | width = 800 138 | height = 600 139 | 140 | interps = [] 141 | num_iter = args.iterations 142 | 143 | interps.append(interpolate((-0.16, 1.0405), (-0.722, 0.246), num_iter)) 144 | interps.append(interpolate((-0.722, 0.246), (-0.235125, 0.827215), num_iter)) 145 | interps.append(interpolate((-0.235125, 0.827215),(-1.25066, 0.02012), num_iter)) 146 | interps.append(interpolate((-1.25066, 0.02012), (-0.748, 0.1), num_iter)) 147 | interps.append(interpolate((-0.748, 0.1), (-0.16, 1.0405), num_iter)) 148 | pbar = tqdm(total=(num_iter * len(interps))) 149 | palette = generate_colors(2000) 150 | fourcc = cv2.VideoWriter_fourcc('F', 'M', 'P', '4') 151 | out_cutter_name = args.output 152 | fps = 24 153 | out = cv2.VideoWriter(out_cutter_name, fourcc, fps, (width, height)) 154 | 155 | for interp in interps: 156 | for p in interp: 157 | r = process_julia(num_iter, p, palette, width, height) 158 | out.write(r) 159 | pbar.update(1) 160 | 161 | out.release() -------------------------------------------------------------------------------- /recursion/julia_set.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goitacademy/Basic-Algorithms-and-Data-Structures-Neoversity/a10d4cdbac13640f50bb6bc4559702f1a7ce483e/recursion/julia_set.gif -------------------------------------------------------------------------------- /recursion/linked_list.py: -------------------------------------------------------------------------------- 1 | class Node: 2 | def __init__(self, data, next=None): 3 | self.data = data 4 | self.next = next 5 | 6 | def traverse(head): 7 | # Base case 8 | if head is None: 9 | return 10 | # Recursive case 11 | print(head.data) 12 | traverse(head.next) 13 | 14 | item1 = Node("dog") 15 | item2 = Node("cat") 16 | item3 = Node("rat") 17 | item1.next = item2 18 | item2.next = item3 19 | 20 | traverse(item1) 21 | 22 | head = Node("dog", Node("cat", Node("rat", None))) 23 | traverse(head) -------------------------------------------------------------------------------- /recursion/mandelbrot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goitacademy/Basic-Algorithms-and-Data-Structures-Neoversity/a10d4cdbac13640f50bb6bc4559702f1a7ce483e/recursion/mandelbrot.gif -------------------------------------------------------------------------------- /recursion/mandelbrot_fractal.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | import matplotlib.animation as animation 4 | 5 | x_start, y_start = -2, -1.5 # an interesting region starts here 6 | width, height = 3, 3 # for 3 units up and right 7 | density_per_unit = 250 # how many pixles per unit 8 | 9 | # real and imaginary axis 10 | re = np.linspace(x_start, x_start + width, width * density_per_unit) 11 | im = np.linspace(y_start, y_start + height, height * density_per_unit) 12 | 13 | fig = plt.figure(figsize=(10, 10)) # instantiate a figure to draw 14 | ax = plt.axes() # create an axes object 15 | 16 | 17 | def mandelbrot(x, y, threshold): 18 | """Calculates whether the number c = x + i*y belongs to the 19 | Mandelbrot set. In order to belong, the sequence z[i + 1] = z[i]**2 + c 20 | must not diverge after 'threshold' number of steps. The sequence diverges 21 | if the absolute value of z[i+1] is greater than 4. 22 | 23 | :param float x: the x component of the initial complex number 24 | :param float y: the y component of the initial complex number 25 | :param int threshold: the number of iterations to considered it converged 26 | """ 27 | # initial conditions 28 | c = complex(x, y) 29 | z = complex(0, 0) 30 | 31 | for i in range(threshold): 32 | z = z ** 2 + c 33 | if abs(z) > 4.: # it diverged 34 | return i 35 | 36 | return threshold - 1 # it didn't diverge 37 | 38 | 39 | def animate(i): 40 | ax.clear() # clear axes object 41 | ax.set_xticks([], []) # clear x-axis ticks 42 | ax.set_yticks([], []) # clear y-axis ticks 43 | 44 | X = np.empty((len(re), len(im))) # re-initialize the array-like image 45 | threshold = round(1.15 ** (i + 1)) # calculate the current threshold 46 | 47 | # iterations for the current threshold 48 | for i in range(len(re)): 49 | for j in range(len(im)): 50 | X[i, j] = mandelbrot(re[i], im[j], threshold) 51 | 52 | # associate colors to the iterations with an iterpolation 53 | img = ax.imshow(X.T, interpolation="bicubic", cmap='magma') 54 | return [img] 55 | 56 | 57 | anim = animation.FuncAnimation(fig, animate, frames=45, interval=120, blit=True) 58 | anim.save('mandelbrot.gif', writer='imagemagick') 59 | -------------------------------------------------------------------------------- /recursion/pathlib_recursion.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | 4 | parent_folder_path = Path('.') 5 | os.mkdir('dist') 6 | dist = Path('path_to_save_files') or Path('dist') 7 | def parse_folder(path): 8 | for element in path.iterdir(): 9 | if element.is_dir(): 10 | print(f"Parse folder: This is folder - {element.name}") 11 | parse_folder(element) 12 | if element.is_file(): 13 | print(f"Parse folder: This is file - {element.name}") 14 | 15 | parse_folder(parent_folder_path) -------------------------------------------------------------------------------- /recursion/string_lengh.py: -------------------------------------------------------------------------------- 1 | def iterative_str_len(a_str): 2 | result = 0 3 | for _ in a_str: 4 | result += 1 5 | return result 6 | 7 | 8 | def recursive_str_len(a_str): 9 | if a_str == "": 10 | return 0 11 | return recursive_str_len(a_str[1:]) + 1 12 | 13 | 14 | input_str = "I love recursion" 15 | print(len(input_str)) 16 | print(iterative_str_len(input_str)) 17 | print(recursive_str_len(input_str)) -------------------------------------------------------------------------------- /recursion/turtle_recursion_tree_final.py: -------------------------------------------------------------------------------- 1 | import turtle 2 | screen = turtle.Screen() # Create the screen. 3 | screen.setup(320, 320) # Set Window size. 4 | 5 | ###### TURTLE SHAPE, SPEED, PEN SIZE, COLOR ###### 6 | TTL = turtle.Turtle() 7 | TTL.speed(0) #Set the turtle's speed. 1 is slow, 10 is fast; 0 is fastest. 8 | TTL.color("brown") #Set the turtle's color. 9 | TTL.pensize(1) #Set width of turtle drawing pen. 10 | 11 | ###### SET TURTLE STARTING POSITION ###### 12 | TTL.penup() # Do not let the turtle draw while moving to position (0, 110). 13 | TTL.setposition(0, -100) 14 | TTL.pendown() # Enable the turtle to draw. 15 | TTL.hideturtle() 16 | TTL.setheading(90) 17 | 18 | ###### VARIABLES ###### 19 | # Variable 'branchLength' is the starting length of tree brach. 20 | # Variable 'branchReduction' subtracts from 'branchLength' in 21 | # each recursion iteration. 22 | # Variable 'recursionLevel' is the recursion iteration number. 23 | 24 | ###### DEFINE treeFractal FUNCTION ###### 25 | # Draw a fractal with recursion level, tree branch length, 26 | # branch length reduction for each iteration, and 27 | # the angle by which the branch turns each iteration. 28 | def treeFractal(TTL, recursionLevel, branchLength, branchReduction, angle): 29 | if recursionLevel == 0: 30 | TTL.fd(0) 31 | else: 32 | branchLength = branchLength - branchReduction 33 | TTL.forward(branchLength) 34 | TTL.left(angle) 35 | treeFractal(TTL, recursionLevel-1, branchLength, branchReduction, angle) 36 | TTL.right(angle * 2) 37 | treeFractal(TTL, recursionLevel-1, branchLength, branchReduction, angle) 38 | TTL.left(angle) 39 | TTL.backward(branchLength) 40 | # Call treeFractal function with the following parameters. 41 | # Recursion Level = 7; Branch length = 50. 42 | # Branch reduction each recursion iteration = 5. 43 | # Turn left of right angle by 25 degrees. 44 | treeFractal(TTL, 9, 45, 5, 25) 45 | 46 | screen.exitonclick() # Exit screen -------------------------------------------------------------------------------- /recursion/usefull_link.md: -------------------------------------------------------------------------------- 1 | Recursion: 2 | https://www.educative.io/blog/recursion-in-python-tutorial 3 | 4 | Fractals: 5 | https://nseverkar.medium.com/intro-to-drawing-fractals-with-python-6ad53bbc8208/ 6 | https://elc.github.io/posts/plotting-fractals-step-by-step-with-python/ 7 | 8 | LRU Cache: 9 | https://realpython.com/lru-cache-python/ -------------------------------------------------------------------------------- /search/binary_search.py: -------------------------------------------------------------------------------- 1 | def binary_search(arr, target): 2 | """ 3 | Функція для бінарного пошуку елемента в відсортованому масиві. 4 | 5 | Parameters: 6 | arr (list): Відсортований масив для пошуку. 7 | target: Елемент, який шукаємо. 8 | 9 | Returns: 10 | int: Індекс елемента в масиві або -1, якщо елемент не знайдено. 11 | """ 12 | left = 0 # Ліва межа масиву 13 | right = len(arr) - 1 # Права межа масиву 14 | 15 | while left <= right: 16 | mid = (left + right) // 2 # Знаходимо середину масиву 17 | print(f"left: {left},\t " 18 | f"right: {right},\t " 19 | f"mid: {mid},\t " 20 | f"target: {target},\t " 21 | f"arr_mid: {arr[mid]},\t" 22 | f"arr: {arr[left:right]}") 23 | 24 | 25 | if arr[mid] == target: 26 | return mid # Якщо знайдено шуканий елемент, повертаємо його індекс 27 | elif arr[mid] < target: 28 | left = mid + 1 # Якщо шуканий елемент більший, зміщуємо ліву межу 29 | else: 30 | right = mid - 1 # Якщо шуканий елемент менший, зміщуємо праву межу 31 | 32 | 33 | return -1 # Повертаємо -1, якщо елемент не знайдено 34 | 35 | # Приклад використання 36 | array = [2, 5, 8, 12, 16, 23, 38, 56, 72, 91] 37 | target = 23 38 | result = binary_search(array, target) 39 | 40 | if result != -1: 41 | print(f"Елемент {target} знайдено на індексі {result}") 42 | else: 43 | print(f"Елемент {target} не знайдено в масиві") 44 | -------------------------------------------------------------------------------- /search/boyer_moore_search.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import matplotlib.patches as patches 3 | import numpy as np 4 | 5 | def bad_char_heuristic(pattern): 6 | # Створюємо масив для зберігання індексів останнього входження кожного символу в шаблоні 7 | bad_char = [-1] * 256 8 | # Проходимо через кожен символ шаблону 9 | for i in range(len(pattern)): 10 | # Зберігаємо індекс останнього входження символу 11 | bad_char[ord(pattern[i])] = i 12 | return bad_char 13 | 14 | def good_suffix_heuristic(pattern): 15 | m = len(pattern) 16 | # Створюємо масив для зберігання зсувів для добрих суфіксів 17 | good_suffix = [0] * (m + 1) 18 | # Створюємо масив для зберігання позицій границь 19 | border_pos = [-1] * (m + 1) 20 | i = m 21 | j = m + 1 22 | border_pos[i] = j 23 | 24 | while i > 0: 25 | # Перевіряємо символи шаблону з кінця на відповідність 26 | while j <= m and pattern[i - 1] != pattern[j - 1]: 27 | # Якщо зсув ще не визначено, визначаємо його 28 | if good_suffix[j] == 0: 29 | good_suffix[j] = j - i 30 | # Переходимо до наступної границі 31 | j = border_pos[j] 32 | i -= 1 33 | j -= 1 34 | # Зберігаємо позицію границі 35 | border_pos[i] = j 36 | 37 | j = border_pos[0] 38 | for i in range(m + 1): 39 | # Якщо зсув ще не визначено, визначаємо його 40 | if good_suffix[i] == 0: 41 | good_suffix[i] = j 42 | # Якщо індекс збігається з позицією границі, переходимо до наступної границі 43 | if i == j: 44 | j = border_pos[j] 45 | 46 | return good_suffix 47 | 48 | def boyer_moore_search(text, pattern): 49 | m = len(pattern) 50 | n = len(text) 51 | 52 | # Викликаємо функцію для обчислення масиву поганих символів 53 | bad_char = bad_char_heuristic(pattern) 54 | # Викликаємо функцію для обчислення масиву добрих суфіксів 55 | good_suffix = good_suffix_heuristic(pattern) 56 | 57 | s = 0 # Індекс зсуву шаблону відносно тексту 58 | steps = [] # Список для зберігання кроків візуалізації 59 | while s <= n - m: 60 | j = m - 1 # Індекс поточного символу в шаблоні 61 | 62 | # Перевіряємо символи шаблону з кінця на відповідність тексту 63 | while j >= 0 and pattern[j] == text[s + j]: 64 | j -= 1 65 | 66 | # Якщо всі символи збігаються, знайдено підрядок 67 | if j < 0: 68 | steps.append((s, f"Substring found at index {s}")) 69 | print(f"Знайдено підрядок на індексі {s}") 70 | # Зсуваємо шаблон на значення доброго суфікса 71 | s += good_suffix[0] 72 | else: 73 | # Обчислюємо зсуви за поганим символом і добрим суфіксом 74 | bad_char_shift = j - bad_char[ord(text[s + j])] 75 | good_suffix_shift = good_suffix[j + 1] 76 | # Зсуваємо шаблон на максимальне значення зсувів 77 | s += max(bad_char_shift, good_suffix_shift) 78 | steps.append((s, f"Index: {s}, Bad char: {bad_char_shift}, Good char: {good_suffix_shift}")) 79 | print(f"Зсув: {s}, Поганий символ: {bad_char_shift}, Добрий суфікс: {good_suffix_shift}") 80 | 81 | visualize_search(text, pattern, steps) 82 | return steps 83 | 84 | def visualize_search(text, pattern, steps): 85 | fig, ax = plt.subplots(figsize=(12, len(steps) * 0.5)) 86 | 87 | ax.set_xlim(0, len(text)*1.65) 88 | ax.set_ylim(-len(steps), 1) 89 | ax.set_xticks(range(len(text))) 90 | ax.set_yticks(-np.arange(len(steps))) 91 | ax.set_xticklabels(text) 92 | ax.set_yticklabels([f"Step {i}" for i in range(len(steps))]) 93 | 94 | for step, (shift, action) in enumerate(steps): 95 | for i, char in enumerate(pattern): 96 | ax.add_patch(patches.Rectangle((shift + i, -step - 0.5), 1, 1, edgecolor='black', facecolor='none')) 97 | ax.text(shift + i + 0.5, -step, char, ha='center', va='center', color='black') 98 | ax.text(len(text) + 2, -step, action, ha='left', va='center', color='blue') 99 | 100 | plt.gca().invert_yaxis() 101 | plt.show() 102 | 103 | # Приклад використання алгоритму 104 | text = "ABAAABCDABCAB" 105 | pattern = "ABC" 106 | boyer_moore_search(text, pattern) 107 | -------------------------------------------------------------------------------- /search/hash_search.py: -------------------------------------------------------------------------------- 1 | class HashTable: 2 | def __init__(self, size): 3 | self.size = size # Розмір хеш-таблиці 4 | self.hash_table = [[] for _ in range(size)] # Ініціалізація порожньої хеш-таблиці 5 | 6 | def hash_function(self, key): 7 | return key % self.size # Проста функція хешування, наприклад, залишок від ділення на розмір таблиці 8 | 9 | def insert(self, key, value): 10 | hash_key = self.hash_function(key) # Обчислення хешу для ключа 11 | self.hash_table[hash_key].append((key, value)) # Додавання пари (ключ, значення) до відповідного списку 12 | 13 | def search(self, key): 14 | hash_key = self.hash_function(key) # Обчислення хешу для ключа 15 | print(f"Хеш індекс для пошуку {hash_key}") 16 | # Пошук у списку, який відповідає обчисленому хешу 17 | for item in self.hash_table[hash_key]: 18 | if item[0] == key: # Якщо ключ співпадає, повертаємо відповідне значення 19 | return item[1] 20 | 21 | return None # Якщо ключ не знайдено, повертаємо None 22 | 23 | def display(self): 24 | # Вивід хеш-таблиці з усіма елементами 25 | for i in range(self.size): 26 | print(f"Індекс {i}: {self.hash_table[i]}") 27 | 28 | # Приклад використання хеш-таблиці для пошуку 29 | hash_table = HashTable(10) # Створення хеш-таблиці розміром 10 30 | 31 | # Додавання елементів у хеш-таблицю 32 | hash_table.insert(10, 'A') 33 | # print("Хеш-таблиця після вставки:") 34 | # hash_table.display() 35 | hash_table.insert(25, 'B') 36 | # print("Хеш-таблиця після вставки:") 37 | # hash_table.display() 38 | hash_table.insert(34, 'C') 39 | # print("Хеш-таблиця після вставки:") 40 | # hash_table.display() 41 | hash_table.insert(45, 'D') 42 | # print("Хеш-таблиця після вставки:") 43 | # hash_table.display() 44 | hash_table.insert(54, 'E') 45 | 46 | # Виведення всієї хеш-таблиці 47 | print("Хеш-таблиця після вставки:") 48 | hash_table.display() 49 | 50 | # Пошук елемента за ключем 51 | key_to_search = 34 52 | result = hash_table.search(key_to_search) 53 | if result: 54 | print(f"Знайдено елемент з ключем {key_to_search}: {result}") 55 | else: 56 | print(f"Елемент з ключем {key_to_search} не знайдено") 57 | 58 | -------------------------------------------------------------------------------- /search/indexed_sequential_search.py: -------------------------------------------------------------------------------- 1 | def create_index_table(array, step): 2 | # Функція створює індексну таблицю з відсортованого масиву з кроком `step` 3 | index_table = [] 4 | for i in range(0, len(array), step): 5 | index_table.append((array[i], i)) # Додаємо кортеж (значення, індекс) у таблицю 6 | return index_table 7 | 8 | # Функція для індексно-послідовного пошуку 9 | def indexed_sequential_search(array, index_table, target): 10 | # Пошук в індексній таблиці 11 | start = 0 12 | end = len(index_table) - 1 13 | while start <= end: 14 | mid = (start + end) // 2 # Обчислення середини діапазону 15 | print(f"Шукаємо у індексній таблиці: діапазон {start}-{end}, середина {mid}") 16 | 17 | if index_table[mid][0] == target: 18 | return index_table[mid][1] # Повертаємо індекс з індексної таблиці 19 | elif index_table[mid][0] < target: 20 | start = mid + 1 # Зміщуємо пошук до правої половини 21 | else: 22 | end = mid - 1 # Зміщуємо пошук до лівої половини 23 | print(f"Шукаємо у індексній таблиці: діапазон {start}-{end}, середина {mid}") 24 | # Визначення діапазону для послідовного пошуку 25 | if start == 0: 26 | search_range = (0, index_table[0][1]) 27 | elif start >= len(index_table): 28 | search_range = (index_table[-1][1], len(array)) 29 | else: 30 | search_range = (index_table[start - 1][1], index_table[start][1]) 31 | 32 | print(f"Пошук у діапазоні {search_range}") 33 | 34 | # Послідовний пошук в діапазоні 35 | for i in range(search_range[0], search_range[1]): 36 | print(f"Порівнюємо {array[i]} з {target}") 37 | if array[i] == target: 38 | return i # Повертаємо позицію, де знайдено елемент 39 | return -1 # якщо елемент не знайдено 40 | 41 | 42 | target = 15 43 | 44 | # Основний відсортований масив 45 | main_array = [1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25] 46 | print(f"Основний відсортований масив: {main_array},\nЦілове значення: {target}") 47 | # Створення індексної таблиці з кроком 4 48 | index_table = create_index_table(main_array, 4) 49 | print(f"Індексна таблиця: {index_table}") 50 | 51 | 52 | result = indexed_sequential_search(main_array, index_table, target) 53 | if result != -1: 54 | print(f"Елемент {target} знайдено на позиції {result}") 55 | else: 56 | print(f"Елемент {target} не знайдено") 57 | -------------------------------------------------------------------------------- /search/interpolation_search.py: -------------------------------------------------------------------------------- 1 | def interpolation_search(array, target): 2 | low = 0 3 | high = len(array) - 1 4 | 5 | while low <= high and array[low] <= target <= array[high]: 6 | # Використовуємо формулу інтерполяції для знаходження орієнтованого середнього 7 | mid = low + int(((high - low) / (array[high] - array[low])) * (target - array[low])) 8 | 9 | print(f"Шукаємо {target} в діапазоні [{low}, {high}]") 10 | print(f"Обчислений індекс mid: {mid}") 11 | 12 | if array[mid] == target: 13 | return mid # Повертаємо індекс, де знайдено цільовий елемент 14 | elif array[mid] < target: 15 | low = mid + 1 # Якщо цільовий елемент більший, шукаємо праворуч від mid 16 | else: 17 | high = mid - 1 # Якщо цільовий елемент менший, шукаємо ліворуч від mid 18 | 19 | return -1 # якщо елемент не знайдено 20 | 21 | 22 | # Основний відсортований масив 23 | main_array = [1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25] 24 | 25 | target = 15 26 | result = interpolation_search(main_array, target) 27 | 28 | if result != -1: 29 | print(f"Елемент {target} знайдено на позиції {result}") 30 | else: 31 | print(f"Елемент {target} не знайдено") 32 | -------------------------------------------------------------------------------- /search/kmp_search.py: -------------------------------------------------------------------------------- 1 | def compute_lps(pattern): 2 | lps = [0] * len(pattern) 3 | length = 0 4 | i = 1 5 | 6 | print(f"Побудова lps масиву") 7 | print(f"Шаблон: {pattern}") 8 | print(f"lps: {lps}") 9 | print() 10 | 11 | while i < len(pattern): 12 | if pattern[i] == pattern[length]: 13 | length += 1 14 | lps[i] = length 15 | i += 1 16 | else: 17 | if length != 0: 18 | length = lps[length - 1] 19 | else: 20 | lps[i] = 0 21 | i += 1 22 | 23 | print(f"i: {i}, length: {length}, lps: {lps}") 24 | 25 | print("\nГотовий lps масив:") 26 | print(f"lps: {lps}") 27 | return lps 28 | 29 | 30 | def kmp_search(text, pattern): 31 | print(f"Вхідний масив: {text}\n") 32 | lps = compute_lps(pattern) 33 | i = 0 # index for text 34 | j = 0 # index for pattern 35 | 36 | print("\nПроцес пошуку:") 37 | 38 | while i < len(text): 39 | if pattern[j] == text[i]: 40 | i += 1 41 | j += 1 42 | 43 | if j == len(pattern): 44 | print(f"Знайдено підрядок на індексі {i - j}") 45 | j = lps[j - 1] 46 | elif i < len(text) and pattern[j] != text[i]: 47 | if j != 0: 48 | j = lps[j - 1] 49 | else: 50 | i += 1 51 | 52 | print(f"i: {i}, j: {j}, поточний текст: {text[:i]}, поточний шаблон: {pattern[:j]}") 53 | 54 | 55 | text = "ABABDABACDABABCABAB" 56 | pattern = "ABABCABAB" 57 | kmp_search(text, pattern) 58 | -------------------------------------------------------------------------------- /search/linear_search.py: -------------------------------------------------------------------------------- 1 | def linear_search(arr, target): 2 | """ 3 | Функція для лінійного пошуку елемента в масиві. 4 | 5 | Parameters: 6 | arr (list): Масив для пошуку. 7 | target: Елемент, який шукаємо. 8 | 9 | Returns: 10 | int: Індекс елемента в масиві або -1, якщо елемент не знайдено. 11 | """ 12 | # Проходимо по всьому масиву 13 | for index in range(len(arr)): 14 | # Перевіряємо, чи дорівнює поточний елемент шуканому значенню 15 | if arr[index] == target: 16 | return index # Повертаємо індекс знайденого елемента 17 | return -1 # Повертаємо -1, якщо елемент не знайдено 18 | 19 | # Приклад використання 20 | array = [12, 11, 13, 5, 6, 7] 21 | target = 5 22 | result = linear_search(array, target) 23 | 24 | if result != -1: 25 | print(f"Елемент знайдено на індексі {result}") 26 | else: 27 | print("Елемент не знайдено") 28 | -------------------------------------------------------------------------------- /search/naive_search.py: -------------------------------------------------------------------------------- 1 | def naive_search(text, pattern): 2 | n = len(text) 3 | m = len(pattern) 4 | results = [] 5 | 6 | # Проходимося по тексту 7 | for i in range(n - m + 1): 8 | match = True 9 | # Перевіряємо співпадіння зразка з тексти починаючи з позиції i 10 | for j in range(m): 11 | if text[i + j] != pattern[j]: 12 | match = False 13 | break 14 | # Якщо знайдено співпадіння, додаємо індекс початку співпадіння до результатів 15 | if match: 16 | results.append(i) 17 | 18 | return results 19 | 20 | # Приклад використання 21 | text = "AABAACAADAABAAABAA" 22 | pattern = "AABA" 23 | matches = naive_search(text, pattern) 24 | if matches: 25 | print(f"Знайдено зразок '{pattern}' в тексті '{text}' на позиціях: {matches}") 26 | else: 27 | print(f"Зразок '{pattern}' не знайдено в тексті '{text}'") 28 | -------------------------------------------------------------------------------- /search/rabin_karp_search.py: -------------------------------------------------------------------------------- 1 | # Алгоритм Рабіна-Карпа для пошуку підрядків 2 | 3 | # Функція для обчислення хешу рядка 4 | def calculate_hash(s, prime, base): 5 | hash_value = 0 6 | for char in s: 7 | hash_value = (hash_value * base + ord(char)) % prime 8 | return hash_value 9 | 10 | # Функція для пошуку підрядка в тексті 11 | def rabin_karp_search(text, pattern): 12 | # Константи для хешування 13 | prime = 101 # Просте число для модуля 14 | base = 256 # Кількість символів в алфавіті 15 | 16 | m = len(pattern) 17 | n = len(text) 18 | 19 | # Обчислюємо хеш для підрядка і початкового вікна тексту 20 | pattern_hash = calculate_hash(pattern, prime, base) 21 | current_hash = calculate_hash(text[:m], prime, base) 22 | 23 | result = [] 24 | 25 | for i in range(n - m + 1): 26 | print(f"pattern_hash: {pattern_hash},\t current_hash: {current_hash},\t pattern: {pattern},\t current text: {text[i:i + m]}") 27 | # Якщо хеші співпадають, перевіряємо самі рядки 28 | if pattern_hash == current_hash: 29 | if text[i:i + m] == pattern: 30 | result.append(i) 31 | 32 | # Оновлюємо хеш для наступного вікна 33 | if i < n - m: 34 | current_hash = (current_hash - ord(text[i]) * (base ** (m - 1))) % prime 35 | current_hash = (current_hash * base + ord(text[i + m])) % prime 36 | current_hash = (current_hash + prime) % prime # Забезпечуємо, щоб хеш був додатнім 37 | 38 | return result 39 | 40 | # Приклад використання 41 | text = "ABAAABCDABCAB" 42 | pattern = "ABC" 43 | print(f"Income text: {text},\t Search pattern {pattern}") 44 | result = rabin_karp_search(text, pattern) 45 | 46 | if result: 47 | print(f"Підрядок знайдено на індексах: {result}") 48 | else: 49 | print("Підрядок не знайдено") -------------------------------------------------------------------------------- /search/usefull_link.md: -------------------------------------------------------------------------------- 1 | What is a search algorithm? 2 | https://klu.ai/glossary/search-algorithm 3 | 4 | Understanding The Different Types of Search Algorithms 5 | https://www.luigisbox.com/blog/types-of-search-algorithms/ 6 | 7 | Pattern Matching Algorithm 8 | https://medium.com/@harshitachhangani0503/pattern-matching-algorithm-4ca950792c95 9 | 10 | A Comprehensive List of Similarity Search Algorithms 11 | https://crucialbits.com/blog/a-comprehensive-list-of-similarity-search-algorithms/ -------------------------------------------------------------------------------- /sorting/bubble_sort.py: -------------------------------------------------------------------------------- 1 | def bubble_sort(lst): 2 | n = len(lst) # Дізнаємось довжину масиву 3 | for i in range(n-1): # Повторюємо процес n-1 разів 4 | for j in range(0, n-i-1): # Проходимось по масиву, не заходячи за відсортовану частину 5 | if lst[j] > lst[j+1]: # Якщо елемент більший за наступний 6 | lst[j], lst[j+1] = lst[j+1], lst[j] # Міняємо їх місцями 7 | return lst # Повертаємо відсортований масив 8 | 9 | array = [12, 11, 13, 5, 6, 7] 10 | print(f"Given array is {array}") 11 | print(f"Sorted array is {bubble_sort(array)}") 12 | 13 | 14 | 15 | def bubble_sort_recursive(arr, n): 16 | if n == 1: # Якщо масив має лише один елемент, він вже відсортований 17 | return 18 | 19 | for i in range(n - 1): # Проходимо по всьому масиву 20 | if arr[i] > arr[i + 1]: # Якщо поточний елемент більший за наступний 21 | arr[i], arr[i + 1] = arr[i + 1], arr[i] # Міняємо їх місцями 22 | 23 | bubble_sort_recursive(arr, n - 1) # Викликаємо функцію знову, але для меншого масиву 24 | return arr # Повертаємо відсортований масив 25 | 26 | # Використання рекурсивного сортування 27 | array = [12, 11, 13, 5, 6, 7] 28 | print(f"Given array is {array}") 29 | print(f"Sorted array is {bubble_sort_recursive(array, len(array))}") -------------------------------------------------------------------------------- /sorting/insertion_sort.py: -------------------------------------------------------------------------------- 1 | def insertion_sort(lst): 2 | for i in range(1, len(lst)): # Починаємо з другого елемента масиву 3 | key = lst[i] # Зберігаємо поточний елемент як ключ 4 | j = i - 1 # Починаємо порівнювати з попереднім елементом 5 | while j >= 0 and key < lst[j]: # Поки не дійшли до початку масиву і ключ менший за поточний елемент 6 | lst[j + 1] = lst[j] # Зсуваємо поточний елемент вправо 7 | j -= 1 # Переміщаємось на одну позицію вліво 8 | lst[j + 1] = key # Вставляємо ключ на правильну позицію 9 | return lst # Повертаємо відсортований масив 10 | 11 | array = [12, 11, 13, 5, 6, 7] 12 | print(f"Given array is {array}") 13 | print(f"Sorted array is {insertion_sort(array)}") 14 | 15 | 16 | 17 | 18 | def insertion_sort_recursive(arr, n): 19 | if n <= 1: # Базовий випадок: якщо масив має один або менше елементів, він вже відсортований 20 | return 21 | 22 | insertion_sort_recursive(arr, n - 1) # Сортуємо перші n-1 елементів рекурсивно 23 | 24 | key = arr[n - 1] # Зберігаємо останній елемент як ключ 25 | j = n - 2 # Починаємо порівнювати з попереднім елементом 26 | while j >= 0 and arr[j] > key: # Поки не дійшли до початку масиву і ключ менший за поточний елемент 27 | arr[j + 1] = arr[j] # Зсуваємо поточний елемент вправо 28 | j -= 1 # Переміщаємось на одну позицію вліво 29 | arr[j + 1] = key # Вставляємо ключ на правильну позицію 30 | return arr # Повертаємо відсортований масив 31 | 32 | # Використання рекурсивного сортування вставками 33 | array = [12, 11, 13, 5, 6, 7] 34 | print(f"Given array is {array}") 35 | print(f"Sorted array is {insertion_sort_recursive(array, len(array))}") 36 | -------------------------------------------------------------------------------- /sorting/merge_sort.py: -------------------------------------------------------------------------------- 1 | # Виконує сортування злиттям знизу вгору 2 | def merge_sort_bottom_up(array): 3 | width = 1 # Починаємо з мінімального розміру підмасиву, 2^0 = 1 4 | length = len(array) 5 | while width < length: # Поки розмір підмасиву менший за загальну довжину масиву 6 | left = 0 7 | while left < length: # Проходимо по масиву зліва направо 8 | right = min(left + (width * 2 - 1), length - 1) # Визначаємо праву межу для об'єднання 9 | middle = min(left + width - 1, length - 1) # Визначаємо середню межу для об'єднання 10 | merge(array, left, middle, right) # Злиття двох підмасивів 11 | left += width * 2 # Переходимо до наступного підмасиву 12 | width *= 2 # Збільшуємо розмір підмасиву вдвічі на кожній ітерації 13 | return array # Повертаємо відсортований масив 14 | 15 | 16 | # Функція злиття підмасивів 17 | def merge(array, left, middle, right): 18 | n1 = middle - left + 1 # Довжина першого підмасиву 19 | n2 = right - middle # Довжина другого підмасиву 20 | 21 | left_array = [0] * n1 # Тимчасовий масив для першого підмасиву 22 | right_array = [0] * n2 # Тимчасовий масив для другого підмасиву 23 | 24 | # Копіюємо дані в тимчасові масиви 25 | for i in range(0, n1): 26 | left_array[i] = array[left + i] 27 | for i in range(0, n2): 28 | right_array[i] = array[middle + i + 1] 29 | 30 | i, j, k = 0, 0, left 31 | # Злиття двох тимчасових масивів у основний масив 32 | while i < n1 and j < n2: 33 | if left_array[i] <= right_array[j]: 34 | array[k] = left_array[i] 35 | i += 1 36 | else: 37 | array[k] = right_array[j] 38 | j += 1 39 | k += 1 40 | 41 | # Додавання залишкових елементів з першого та другого підмасивів 42 | while i < n1: 43 | array[k] = left_array[i] 44 | i += 1 45 | k += 1 46 | while j < n2: 47 | array[k] = right_array[j] 48 | j += 1 49 | k += 1 50 | 51 | 52 | # Функція сортування злиттям рекурсивно 53 | def merge_sort_recursive(arr): 54 | if len(arr) <= 1: 55 | return arr 56 | 57 | mid = len(arr) // 2 58 | left_half = arr[:mid] 59 | right_half = arr[mid:] 60 | 61 | left_half = merge_sort_recursive(left_half) 62 | right_half = merge_sort_recursive(right_half) 63 | 64 | return merge(left_half, right_half) # Виклик функції злиття для двох підмасивів 65 | 66 | 67 | # Приклад використання 68 | array = [12, 11, 13, 5, 6, 7] 69 | print(f"Given array is {array}") 70 | print(f"Sorted array is {merge_sort_recursive(array)}") 71 | 72 | 73 | 74 | # Функція сортування злиттям 75 | def merge_sort(arr): 76 | # Якщо довжина масиву менше або дорівнює 1, повертаємо масив як є 77 | if len(arr) <= 1: 78 | return arr 79 | 80 | # Визначаємо середину масиву 81 | mid = len(arr) // 2 82 | # Ділимо масив на ліву і праву половини 83 | left_half = arr[:mid] 84 | right_half = arr[mid:] 85 | 86 | # Рекурсивно сортуємо обидві половини 87 | left_half = merge_sort(left_half) 88 | right_half = merge_sort(right_half) 89 | 90 | # Зливаємо дві відсортовані половини 91 | return merge(left_half, right_half) 92 | 93 | 94 | # Функція злиття двох відсортованих масивів 95 | def merge(left, right): 96 | result = [] # Результуючий масив 97 | left_index, right_index = 0, 0 # Індекси для лівого і правого масивів 98 | 99 | # Зливаємо масиви, порівнюючи елементи 100 | while left_index < len(left) and right_index < len(right): 101 | if left[left_index] < right[right_index]: 102 | result.append(left[left_index]) 103 | left_index += 1 104 | else: 105 | result.append(right[right_index]) 106 | right_index += 1 107 | 108 | # Додаємо залишкові елементи з обох масивів 109 | result.extend(left[left_index:]) 110 | result.extend(right[right_index:]) 111 | 112 | return result # Повертаємо злитий масив 113 | 114 | 115 | # Приклад використання 116 | array = [12, 11, 13, 5, 6, 7] 117 | 118 | print(f"Given array is {array}") 119 | print(f"Sorted array is {merge_sort(array)}") 120 | 121 | -------------------------------------------------------------------------------- /sorting/quick_sort.py: -------------------------------------------------------------------------------- 1 | # Ця функція однакова для обох ітеративної та рекурсивної реалізацій 2 | def partition(array, left, right): 3 | i = (left - 1) 4 | pivot = array[right] 5 | 6 | for j in range(left, right): 7 | if array[j] <= pivot: 8 | # збільшити індекс меншого елемента 9 | i = i + 1 10 | array[i], array[j] = array[j], array[i] 11 | 12 | array[i + 1], array[right] = array[right], array[i + 1] 13 | return (i + 1) 14 | 15 | 16 | # Функція для виконання ітеративного QuickSort 17 | def quick_sort_iterative(array, left, right): 18 | # Створити допоміжний стек 19 | size = right - left + 1 20 | stack = [0] * size 21 | 22 | # ініціалізувати верхівку стеку 23 | top = -1 24 | 25 | # додати початкові значення left та right до стеку 26 | top = top + 1 27 | stack[top] = left 28 | top = top + 1 29 | stack[top] = right 30 | 31 | # Продовжувати витягувати зі стеку, поки він не порожній 32 | while top >= 0: 33 | 34 | # Витягнути right і left 35 | right = stack[top] 36 | top = top - 1 37 | left = stack[top] 38 | top = top - 1 39 | 40 | # Встановити опорний елемент на його правильну позицію у відсортованому масиві 41 | pivot_position = partition(array, left, right) 42 | 43 | # Якщо є елементи з лівого боку від опорного елемента, 44 | # додати ліву частину до стеку 45 | if pivot_position - 1 > left: 46 | top = top + 1 47 | stack[top] = left 48 | top = top + 1 49 | stack[top] = pivot_position - 1 50 | 51 | # Якщо є елементи з правого боку від опорного елемента, 52 | # додати праву частину до стеку 53 | if pivot_position + 1 < right: 54 | top = top + 1 55 | stack[top] = pivot_position + 1 56 | top = top + 1 57 | stack[top] = right 58 | 59 | 60 | 61 | array = [12, 11, 13, 5, 6, 7] 62 | print(f"Given array is {array}") 63 | quick_sort_iterative(array, 0, len(array) - 1) 64 | print(f"Sorted array is {array}") 65 | 66 | 67 | def quicksort(arr): 68 | # Якщо довжина масиву менше або дорівнює 1, повертаємо масив як є (він вже відсортований) 69 | if len(arr) <= 1: 70 | return arr 71 | # Вибираємо опорний елемент (у цьому випадку середній елемент масиву) 72 | pivot = arr[len(arr) // 2] 73 | # Створюємо три підмасиви: лівий, середній та правий 74 | # Лівий підмасив містить елементи менші за опорний елемент 75 | left = [x for x in arr if x < pivot] 76 | # Середній підмасив містить елементи, рівні опорному елементу 77 | middle = [x for x in arr if x == pivot] 78 | # Правий підмасив містить елементи більші за опорний елемент 79 | right = [x for x in arr if x > pivot] 80 | # Рекурсивно сортуємо лівий та правий підмасиви, і об'єднуємо їх разом із середнім підмасивом 81 | return quicksort(left) + middle + quicksort(right) 82 | 83 | 84 | 85 | # def quicksort_verbose(arr): 86 | # print(f"Calling quicksort on {arr}") 87 | # if len(arr) <= 1: 88 | # print(f"returning {arr}") 89 | # return arr 90 | # pivot = arr[len(arr) // 2] 91 | # left = [x for x in arr if x < pivot] 92 | # print(f"left: {left}; ", end="") 93 | # middle = [x for x in arr if x == pivot] 94 | # print(f"middle: {middle}; ", end="") 95 | # right = [x for x in arr if x > pivot] 96 | # print(f"right: {right}") 97 | # to_return = quicksort_verbose(left) + middle + quicksort_verbose(right) 98 | # print(f"returning: {to_return}") 99 | # return to_return 100 | 101 | 102 | array = [12, 11, 13, 5, 6, 7] 103 | print(f"Given array is {array}") 104 | print(f"Sorted array is {quicksort(array)}") 105 | # print(f"Sorted array step by step is {quicksort_verbose(array)}") 106 | 107 | 108 | # data = [1, 6, 5, 5, 2, 6, 1] 109 | # print(quicksort(data)) 110 | # 111 | # # for challenge 112 | # data = [5, 4, 3, 2, 1] # print(quicksort_verbose(data)) 113 | -------------------------------------------------------------------------------- /sorting/radix_sort.py: -------------------------------------------------------------------------------- 1 | # Функція сортування Радікс (ітеративний підхід) 2 | def radix_sort(nums): 3 | RADIX = 10 # Базова система числення (десяткова система) 4 | placement = 1 # Початковий розряд 5 | max_digit = max(nums) # Максимальне число у масиві для визначення кількості розрядів 6 | 7 | # Продовжуємо сортувати, поки розряд менший за максимальне число 8 | while placement <= max_digit: 9 | # Створюємо "кошики" для кожного розряду 10 | buckets = [[] for _ in range(RADIX)] 11 | # Розподіляємо числа по кошикам відповідно до поточного розряду 12 | for i in nums: 13 | temp = (i // placement) % RADIX 14 | buckets[temp].append(i) 15 | # Оновлюємо масив, об'єднуючи числа з кошиків 16 | a = 0 17 | for b in range(RADIX): 18 | for i in buckets[b]: 19 | nums[a] = i 20 | a += 1 21 | # Переходимо до наступного розряду 22 | placement *= RADIX 23 | return nums 24 | 25 | # Приклад використання 26 | nums = [3, 89, 67, 254, 9, 21, 185, 4, 62] 27 | print("Відсортований масив:", radix_sort(nums)) 28 | 29 | 30 | # Допоміжна функція сортування за допомогою підрахунку для певного розряду 31 | def counting_sort(arr, position): 32 | size = len(arr) # Розмір масиву 33 | output = [0] * size # Вихідний масив, ініціалізований нулями 34 | count = [0] * 10 # Масив підрахунку для розрядів (0-9) 35 | 36 | # Підрахунок кількості входжень кожної цифри у поточному розряді 37 | for i in range(size): 38 | index = (arr[i] // position) % 10 39 | count[index] += 1 40 | 41 | # Оновлення count[i] для визначення позиції наступної цифри у вихідному масиві 42 | for i in range(1, 10): 43 | count[i] += count[i - 1] 44 | 45 | # Побудова вихідного масиву 46 | i = size - 1 47 | while i >= 0: 48 | index = (arr[i] // position) % 10 49 | output[count[index] - 1] = arr[i] 50 | count[index] -= 1 51 | i -= 1 52 | 53 | # Копіювання відсортованих чисел до початкового масиву 54 | for i in range(size): 55 | arr[i] = output[i] 56 | 57 | # Функція сортування Радікс (рекурсивний підхід) 58 | def radix_sort_recursive(arr): 59 | max_num = max(arr) # Визначення максимального числа у масиві 60 | position = 1 # Початковий розряд 61 | 62 | # Виконання сортування за допомогою підрахунку для кожного розряду 63 | while max_num // position > 0: 64 | counting_sort(arr, position) 65 | position *= 10 # Переходимо до наступного розряду 66 | 67 | # Приклад використання 68 | arr = [3, 89, 67, 254, 9, 21, 185, 4, 62] 69 | radix_sort_recursive(arr) 70 | print("Відсортований масив:", arr) # Відсортований масив: [3, 4, 9, 21, 62, 67, 89, 185, 254] 71 | -------------------------------------------------------------------------------- /sorting/selection_sort.py: -------------------------------------------------------------------------------- 1 | def selection_sort(arr): 2 | n = len(arr) # Дізнаємось довжину масиву 3 | for i in range(n): # Проходимо по всьому масиву 4 | min_idx = i # Припускаємо, що найменший елемент на поточній позиції 5 | for j in range(i + 1, n): # Шукаємо найменший елемент в невідсортованій частині 6 | if arr[j] < arr[min_idx]: # Якщо знайшли елемент менший за поточний мінімум 7 | min_idx = j # Оновлюємо індекс найменшого елемента 8 | arr[i], arr[min_idx] = arr[min_idx], arr[i] # Міняємо місцями поточний елемент і найменший елемент 9 | return arr # Повертаємо відсортований масив 10 | 11 | array = [12, 11, 13, 5, 6, 7] 12 | print(f"Given array is {array}") # Виводимо початковий масив 13 | print(f"Sorted array is {selection_sort(array)}") # Виводимо відсортований масив 14 | 15 | def test_selection(arr: list): 16 | for i in range(len(arr)): 17 | min_index = arr.index(min(arr[i:]), i) 18 | arr[i], arr[min_index] = arr[min_index], arr[i] 19 | return arr 20 | 21 | array = [12, 11, 13, 5, 6, 7] 22 | print(f"Test Given array is {array}") # Виводимо початковий масив 23 | print(f"Test Sorted array is {test_selection(array)}") # Виводимо відсортований масив 24 | 25 | 26 | 27 | def selection_sort_recursive(arr, index=0): 28 | if index == len(arr) - 1: # Базовий випадок: якщо дійшли до останнього елемента, масив відсортований 29 | return 30 | 31 | min_index = index # Припускаємо, що найменший елемент на поточній позиції 32 | for i in range(index + 1, len(arr)): # Шукаємо найменший елемент в невідсортованій частині 33 | if arr[i] < arr[min_index]: # Якщо знайшли елемент менший за поточний мінімум 34 | min_index = i # Оновлюємо індекс найменшого елемента 35 | 36 | arr[index], arr[min_index] = arr[min_index], arr[index] # Міняємо місцями поточний елемент і найменший елемент 37 | 38 | selection_sort_recursive(arr, index + 1) # Викликаємо функцію знову для наступного елемента 39 | return arr # Повертаємо відсортований масив 40 | 41 | # Використання рекурсивного сортування вибором 42 | array = [12, 11, 13, 5, 6, 7] 43 | print(f"Given array is {array}") # Виводимо початковий масив 44 | print(f"Sorted array is {selection_sort_recursive(array)}") # Виводимо відсортований масив 45 | 46 | def test_selection(arr: list): 47 | for i in range(len(arr)): 48 | min_index = arr.index(min(arr[i:]), i) 49 | arr[i], arr[min_index] = arr[min_index], arr[i] 50 | return arr 51 | 52 | # Використання сортування вибором за допомогою вбудованого функціоналу Python 53 | array = [12, 11, 13, 5, 6, 7] 54 | print(f"Test Given array is {array}") # Виводимо початковий масив 55 | print(f"Test Sorted array is {test_selection(array)}") # Виводимо відсортований масив 56 | -------------------------------------------------------------------------------- /sorting/shell_sort.py: -------------------------------------------------------------------------------- 1 | # Функція сортування Шелла (ітеративний підхід) 2 | def shell_sort(array): 3 | n = len(array) # Довжина масиву 4 | interval = n // 2 # Початковий інтервал 5 | 6 | # Продовжуємо сортувати поки інтервал більше 0 7 | while interval > 0: 8 | for i in range(interval, n): # Проходимо по елементах масиву 9 | temp = array[i] # Зберігаємо поточний елемент 10 | j = i 11 | # Переміщуємо елементи, що більше temp, на інтервал назад 12 | while j >= interval and array[j - interval] > temp: 13 | array[j] = array[j - interval] 14 | j -= interval 15 | array[j] = temp # Вставляємо temp на правильне місце 16 | interval //= 2 # Зменшуємо інтервал вдвічі 17 | return array # Повертаємо відсортований масив 18 | 19 | # Приклад використання 20 | array = [12, 11, 13, 5, 6, 7] 21 | print(f"Given array is {array}") 22 | print(f"Sorted array is {shell_sort(array)}") 23 | 24 | 25 | # Функція сортування Шелла (рекурсивний підхід) 26 | def shell_sort_recursive(arr, gap=None): 27 | if gap is None: 28 | gap = len(arr) // 2 # Початковий інтервал 29 | 30 | if gap > 0: 31 | for i in range(gap, len(arr)): # Проходимо по елементах масиву 32 | temp = arr[i] # Зберігаємо поточний елемент 33 | j = i 34 | # Переміщуємо елементи, що більше temp, на інтервал назад 35 | while j >= gap and arr[j - gap] > temp: 36 | arr[j] = arr[j - gap] 37 | j -= gap 38 | arr[j] = temp # Вставляємо temp на правильне місце 39 | shell_sort_recursive(arr, gap // 2) # Рекурсивний виклик із зменшеним інтервалом 40 | 41 | return arr # Повертаємо відсортований масив 42 | 43 | # Приклад використання 44 | array = [12, 11, 13, 5, 6, 7] 45 | print(f"Given array is {array}") 46 | print(f"Sorted array is {shell_sort_recursive(array)}") 47 | -------------------------------------------------------------------------------- /sorting/usefull_link.md: -------------------------------------------------------------------------------- 1 | When to use which Sorting Algorithm: 2 | https://medium.com/@kaustubhdwivedi1729/when-to-use-which-sorting-algorithm-125897e40295 3 | https://www.baeldung.com/cs/choose-sorting-algorithm 4 | https://blog.algorithmexamples.com/sorting-algorithm/11-everyday-life-uses-of-sorting-algorithms/ 5 | 6 | Additional algorithms explained: 7 | https://www.sitepoint.com/best-sorting-algorithms/#insertionsort 8 | 9 | Visual demonstrating 10 | https://visualgo.net/en/sorting 11 | https://www.sortvisualizer.com/ 12 | -------------------------------------------------------------------------------- /tree/avl_tree.py: -------------------------------------------------------------------------------- 1 | # AVL Tree 2 | # Під час кожного додавання перевіряється, чи дерево збалансоване за висотою 3 | # Якщо воно незбалансоване, виконується обертання для балансування 4 | 5 | from visual_tree import animate_traversal_avl 6 | 7 | class Node(object): 8 | def __init__(self, data): 9 | self.data = data 10 | self.left_child = None # Лівий нащадок поточного вузла 11 | self.right_child = None # Правий нащадок поточного вузла 12 | self.height = 0 # Висота дерева для перевірки балансування 13 | 14 | 15 | # Виконання операцій на AVL-дереві 16 | class AVL(object): 17 | def __init__(self): 18 | self.root = None # Кореневий вузол 19 | 20 | # --------------------------- Обчислення висоти AVL-дерева --------------------------------- 21 | def calc_height(self, node): 22 | if not node: # Якщо цей вузол нульовий 23 | return -1 # повертаємо -1; лівий і правий нащадки листових вузлів 24 | return node.height 25 | 26 | # ------------------------------- Додавання вузла до AVL-дерева ---------------------------- 27 | def insert(self, data): 28 | self.root = self.insert_node(data, self.root) 29 | 30 | def insert_node(self, data, node): 31 | if not node: # Якщо це кореневий вузол, створюємо новий вузол 32 | print(f'Вузол {data} був вставлений') 33 | return Node(data) 34 | if data < node.data: # Якщо дані менші за поточний вузол, йдемо ліворуч, інакше праворуч 35 | node.left_child = self.insert_node(data, node.left_child) 36 | else: 37 | node.right_child = self.insert_node(data, node.right_child) 38 | 39 | # Отримуємо висоту нового вузла 40 | node.height = max(self.calc_height(node.left_child), self.calc_height(node.right_child)) + 1 41 | 42 | # Вузол був вставлений. Тепер перевіряємо, чи були порушення AVL-дерева 43 | return self.settle_violation(data, node) 44 | 45 | # -------------------- Вирішення порушень при додаванні нового вузла -------------------------- 46 | def settle_violation(self, data, node): 47 | balance = self.calc_balance(node) 48 | 49 | # Випадок 1: Ліва ліва ситуація 50 | if balance > 1 and data < node.left_child.data: 51 | print("Ліва ліва ситуація...") 52 | return self.rotate_right(node) 53 | 54 | # Випадок 2: Права права ситуація 55 | if balance < -1 and data > node.right_child.data: 56 | print('Права права ситуація...') 57 | return self.rotate_left(node) 58 | 59 | # Випадок 3: Ліва права ситуація 60 | if balance > 1 and data > node.left_child.data: 61 | print('Ліва права ситуація...') 62 | node.left_child = self.rotate_left(node.left_child) 63 | return self.rotate_right(node) 64 | 65 | # Випадок 4: Права ліва ситуація 66 | if balance < -1 and data < node.right_child.data: 67 | print('Права ліва ситуація...') 68 | node.right_child = self.rotate_right(node.right_child) # Тут вузол є кореневим вузлом 69 | return self.rotate_left(node) 70 | 71 | return node 72 | 73 | # ------------------------------- Перевірка, чи дерево збалансоване ------------------------------- 74 | # Якщо повертає значення > 1, це означає, що дерево лівобічно важке 75 | # Виконуємо праве обертання для балансування 76 | # Якщо повертає значення < -1, це означає, що дерево правобічно важке 77 | # Виконуємо ліве обертання для балансування 78 | def calc_balance(self, node): 79 | if not node: 80 | return 0 81 | return self.calc_height(node.left_child) - self.calc_height(node.right_child) 82 | 83 | # Праве обертання вузлів для балансування AVL-дерева 84 | # Часова складність O(1) 85 | def rotate_right(self, node): 86 | print('Праве обертання на вузлі ', node.data) # C <- B <- D -> E; Кореневий вузол - "D" 87 | temp_left_child = node.left_child # temp_left_child => B 88 | t = temp_left_child.right_child # t = C 89 | 90 | # Праве обертання 91 | temp_left_child.right_child = node # "D" стає правим нащадком "B"; B -> D 92 | node.left_child = t # "C" стає лівим нащадком "D" 93 | 94 | # Обчислення висоти AVL-дерева 95 | node.height = max(self.calc_height(node.left_child), self.calc_height(node.right_child)) + 1 96 | temp_left_child.height = max(self.calc_height(temp_left_child.left_child), 97 | self.calc_height(temp_left_child.right_child)) + 1 98 | return temp_left_child # Кореневий вузол після обертання 99 | 100 | # Ліве обертання вузлів для балансування AVL-дерева 101 | # Часова складність O(1) 102 | def rotate_left(self, node): 103 | print('Ліве обертання на вузлі ', node.data) 104 | temp_right_child = node.right_child 105 | t = temp_right_child.left_child 106 | 107 | # Ліве обертання 108 | temp_right_child.left_child = node 109 | node.right_child = t 110 | 111 | # Обчислення висоти AVL-дерева 112 | node.height = max(self.calc_height(node.left_child), self.calc_height(node.right_child)) + 1 113 | temp_right_child.height = max(self.calc_height(temp_right_child.left_child), 114 | self.calc_height(temp_right_child.right_child)) + 1 115 | return temp_right_child # Кореневий вузол після обертання 116 | 117 | # ------------------------------ Видалення вузла з AVL-дерева ----------------------- 118 | # Видалення вузла [Deletion] 119 | def remove(self, data): 120 | if self.root: 121 | self.root = self.remove_node(data, self.root) 122 | 123 | def remove_node(self, data, node): 124 | if not node: 125 | return node 126 | if data < node.data: 127 | node.left_child = self.remove_node(data, node.left_child) 128 | elif data > node.data: 129 | node.right_child = self.remove_node(data, node.right_child) 130 | else: 131 | if not node.left_child and not node.right_child: 132 | print('Видалення листового вузла...') 133 | del node 134 | return None 135 | if not node.left_child: 136 | print('Видалення правого нащадка...') 137 | tempNode = node.right_child 138 | del node 139 | return tempNode 140 | if not node.right_child: 141 | print('Видалення лівого нащадка...') 142 | tempNode = node.left_child 143 | return tempNode 144 | print('Видалення вузла з двома нащадками...') 145 | tempNode = self.get_predecessor(node.left_child) 146 | node.data = tempNode.data 147 | node.left_child = self.remove_node(tempNode.data, node.left_child) 148 | 149 | if not node: 150 | return node 151 | 152 | node.height = max(self.calc_height(node.left_child), self.calc_height(node.right_child)) + 1 153 | balance = self.calc_balance(node) 154 | 155 | # Двічі ліва важка ситуація 156 | if balance > 1 and self.calc_balance(node.left_child) >= 0: 157 | return self.rotate_right(node) 158 | 159 | # Двічі права важка ситуація 160 | if balance < -1 and self.calc_balance(node.right_child) <= 0: 161 | return self.rotate_left(node) 162 | 163 | # Ліва права ситуація 164 | if balance > 1 and self.calc_balance(node.left_child) < 0: 165 | node.left_child = self.rotate_left(node.left_child) 166 | return self.rotate_right(node) 167 | 168 | # Права ліва ситуація 169 | if balance < -1 and self.calc_balance(node.right_child) > 0: 170 | node.right_child = self.rotate_right(node.right_child) 171 | return self.rotate_left(node) 172 | 173 | return node 174 | 175 | def get_predecessor(self, node): 176 | if node.right_child: 177 | return self.get_predecessor(node.right_child) 178 | return node 179 | 180 | # --------------------- Обхід AVL-дерева ------------------ 181 | def traverse(self, path): 182 | if self.root: 183 | self.pre_order_traversal(self.root, path) 184 | 185 | def pre_order_traversal(self, node, path): 186 | if node: 187 | path.append(node) 188 | self.pre_order_traversal(node.left_child, path) 189 | self.pre_order_traversal(node.right_child, path) 190 | 191 | # --------------------- Візуалізація AVL-дерева ------------------ 192 | 193 | def display(self, traversal_type=None): 194 | if traversal_type: 195 | animate_traversal_avl(self.root, lambda node, path: self.pre_order_traversal(node, path), traversal_type) 196 | else: 197 | animate_traversal_avl(self.root, lambda node, path: self.pre_order_traversal(node, path)) 198 | 199 | 200 | # ------------------- Тестування ----------------- 201 | if __name__ == '__main__': 202 | avl = AVL() 203 | avl.insert(6) 204 | avl.insert(10) 205 | avl.insert(20) 206 | avl.insert(5) 207 | avl.insert(13) 208 | avl.insert(14) 209 | avl.insert(3) 210 | avl.insert(2) 211 | avl.insert(7) 212 | avl.insert(8) 213 | avl.insert(4) 214 | avl.display() 215 | avl.insert(15) 216 | avl.remove(5) 217 | avl.remove(10) 218 | avl.display() 219 | avl.display('Pre-Order') 220 | -------------------------------------------------------------------------------- /tree/bst_tree.py: -------------------------------------------------------------------------------- 1 | # двійкове дерево пошуку 2 | from visual_tree import animate_traversal_avl 3 | class Node(object): # Вузли в дереві 4 | def __init__(self, data): 5 | self.data = data # Вхідні дані 6 | self.left_child = None # Лівий нащадок 7 | self.right_child = None # Правий нащадок 8 | 9 | 10 | # Операції на двійковому дереві пошуку 11 | class BinarySearchTree(object): 12 | def __init__(self): 13 | self.root = None # Ініціалізувати корінь як пустий 14 | 15 | # ------------------------- Додавання нового вузла до двійкового дерева пошуку ---------------------------- 16 | # Додати елемент до BST 17 | def insert(self, data): 18 | if not self.root: # Якщо корінь пустий, це перший елемент 19 | print(f'Вузол {data} був вставлений') 20 | self.root = Node(data) # Створити новий вузол дерева 21 | else: 22 | self.insert_node(data, self.root) # Якщо корінь існує, додати новий вузол 23 | 24 | # Додати новий вузол 25 | # Складність O(log N) якщо дерево збалансоване 26 | def insert_node(self, data, node): 27 | # Якщо нові дані менші за дані кореня 28 | if data < node.data: # Якщо новий елемент менший за дані поточного вузла 29 | if node.left_child: # Якщо поточний вузол[A] має лівого нащадка [B], то 30 | self.insert_node(data, node.left_child) # Додати новий вузол зліва від [B] 31 | else: 32 | print(f'Вузол {data} був вставлений') 33 | node.left_child = Node(data) # Якщо немає лівого нащадка у [A], створити новий вузол зліва від нього 34 | 35 | # Якщо нові дані більші за дані кореня 36 | else: 37 | if node.right_child: 38 | self.insert_node(data, node.right_child) 39 | else: 40 | print(f'Вузол {data} був вставлений') 41 | node.right_child = Node(data) 42 | 43 | # -------------------------- Видалення вузла з двоїчного дерева пошуку --------------------------- 44 | 45 | def remove(self, data): 46 | if self.root: 47 | self.root = self.remove_node(data, self.root) 48 | 49 | def remove_node(self, data, node): 50 | if not node: # Якщо вузол пустий, повернути вузол 51 | return node 52 | 53 | # Якщо дані < кореня, пройти через лівого нащадка поки не знайдемо вузол з "даними" 54 | # Шукати вузол, який хочемо видалити 55 | if data < node.data: # Якщо дані вузла, що потрібно видалити, менші за корінь, перейти наліво 56 | node.left_child = self.remove_node(data, node.left_child) # Сказати батьківському вузлу, що його лівий нащадок пустий 57 | elif data > node.data: 58 | node.right_child = self.remove_node(data, node.right_child) 59 | 60 | # Інакше стоїмо на вузлі, який хочемо видалити 61 | else: 62 | # Якщо поточний вузол є листовим, тобто немає лівого і правого нащадка 63 | if not node.left_child and not node.right_child: 64 | print('Видалення листового вузла...') 65 | del node 66 | return None # Повернення None повідомляє батьківський вузол, що його лівий або правий нащадок було видалено 67 | # Якщо вузол для видалення є батьком і має тільки одного нащадка (праворуч) 68 | # Вузол -> Правий нащадок 69 | if not node.left_child: 70 | print('Видалення вузла з одним правим нащадком...') 71 | temp_node = node.right_child # Помістити значення правого нащадка у тимчасову змінну 72 | del node # Видалити батьківський вузол 73 | return temp_node # Повернути тимчасовий вузол. Батько видаленого вузла --> Тимчасовий вузол 74 | 75 | # Якщо вузол для видалення є батьком і має тільки одного нащадка (ліворуч) 76 | # Вузол -> Лівий нащадок 77 | elif not node.right_child: 78 | print('Видалення вузла з одним лівим нащадком...') 79 | temp_node = node.left_child 80 | del node 81 | return temp_node 82 | 83 | # Якщо вузол для видалення є батьківським вузлом з двома нащадками 84 | print('Видалення вузла з двома нащадками...') 85 | # Знайти найбільший вузол у лівому піддереві кореня і помістити в тимчасовий вузол 86 | temp_node = self.get_predecessor(node.left_child) 87 | # Замінити дані вузла, що потрібно видалити, даними найбільшого вузла 88 | node.data = temp_node.data 89 | # Видалити вузол з найбільшим значенням у лівому піддереві до кореня/вузла, що потрібно видалити 90 | node.left_child = self.remove_node(temp_node.data, node.left_child) 91 | return node 92 | 93 | # Отримати найбільше значення у лівому піддереві кореня 94 | # Повернути вузол з найбільшим значенням 95 | def get_predecessor(self, node): 96 | if node.right_child: 97 | return self.get_predecessor(node.right_child) 98 | return node 99 | 100 | # -------------------------- Отримати мінімальне значення у двоїчному дереві пошуку --------------------------------- 101 | # Отримати мінімальне значення у двоїчному дереві пошуку 102 | # Ліве найвіддаленіше значення є найменшим значенням 103 | def get_min_value(self): 104 | if self.root: # Якщо дерево не пусте 105 | return self.get_min(self.root) # Перейти до функції getMin 106 | 107 | # Пройти до лівого найвіддаленішого вузла (найменше значення) 108 | def get_min(self, node): # Почати проходити від кореня 109 | if node.left_child: # Якщо лівий нащадок існує, продовжувати йти поки не досягнемо "NONE" 110 | return self.get_min(node.left_child) 111 | return node.data # Коли ми досягнемо None, отримаємо лівий найвіддаленіший вузол і повернемо його дані 112 | 113 | # ---------------------------- Отримати максимальне значення у двоїчному дереві пошуку ---------------------------------- 114 | # Отримати максимальне значення з двоїчного дерева пошуку (найправіше значення) 115 | def get_max_value(self): 116 | if self.root: # Якщо ми на корені, перейти до функції getMax 117 | return self.get_max(self.root) 118 | 119 | # Пройти до правого найвіддаленішого вузла (максимальне значення) 120 | def get_max(self, node): 121 | if node.right_child: # Якщо правий нащадок існує, пройти до найправішого вузла 122 | return self.get_max(node.right_child) 123 | return node.data # Повернути дані найправішого вузла 124 | 125 | # ------------------------------ Обхід двоїчного дерева пошуку -------------------------------------- 126 | # Пройти через двоїчне дерево пошуку 127 | 128 | def traversal(self, path): 129 | if self.root: 130 | self.pre_order_traversal(self.root, path) 131 | 132 | def pre_order_traversal(self, node, path): 133 | if node: 134 | path.append(node) 135 | self.pre_order_traversal(node.left_child, path) 136 | self.pre_order_traversal(node.right_child, path) 137 | 138 | # -------------------- Пошук вузла у двоїчному дереві пошуку ----------------------- 139 | def search(self, data): 140 | return self.search_node(data, self.root) 141 | 142 | def search_node(self, data, node): 143 | if node is None or node.data == data: 144 | return node 145 | if data < node.data: 146 | return self.search_node(data, node.left_child) 147 | return self.search_node(data, node.right_child) 148 | 149 | def display(self, traversal_type=None): 150 | if traversal_type: 151 | animate_traversal_avl(self.root, lambda node, path: self.pre_order_traversal(node, path), traversal_type) 152 | else: 153 | animate_traversal_avl(self.root, lambda node, path: self.pre_order_traversal(node, path)) 154 | 155 | 156 | # -------------------- Тестування ----------------------- 157 | if __name__ == '__main__': 158 | bst = BinarySearchTree() 159 | bst.insert(10) # Корінь 160 | bst.insert(13) 161 | bst.insert(14) 162 | bst.insert(12) 163 | bst.insert(5) 164 | bst.insert(3) 165 | bst.insert(2) 166 | bst.insert(4) 167 | # bst.insert(8) 168 | bst.insert(1) 169 | bst.display() 170 | bst.remove(10) 171 | bst.display() 172 | found_node = bst.search(90) 173 | if found_node: 174 | print(f"Вузол з числом {found_node.data} знайдено.") 175 | else: 176 | print("Вузол не знайдено.") 177 | 178 | -------------------------------------------------------------------------------- /tree/pre_in_post_order.py: -------------------------------------------------------------------------------- 1 | from visual_tree import animate_traversal 2 | 3 | # Binary Tree Node Definition 4 | class TreeNode: 5 | def __init__(self, value): 6 | self.value = value 7 | self.left = None 8 | self.right = None 9 | 10 | 11 | def build_sample_tree(): 12 | root = TreeNode(1) 13 | root.left = TreeNode(2) 14 | root.right = TreeNode(3) 15 | root.left.left = TreeNode(4) 16 | root.left.right = TreeNode(5) 17 | root.right.left = TreeNode(6) 18 | root.right.right = TreeNode(7) 19 | return root 20 | 21 | 22 | def pre_order_traversal(node, path): # Обхід дерева у передпорядку: Корінь -> Ліво -> Право 23 | if node: 24 | path.append(node) 25 | pre_order_traversal(node.left, path) 26 | pre_order_traversal(node.right, path) 27 | 28 | 29 | def in_order_traversal(node, path): # Обхід дерева у порядку: Ліво -> Корінь -> Право 30 | if node: 31 | in_order_traversal(node.left, path) 32 | path.append(node) 33 | in_order_traversal(node.right, path) 34 | 35 | 36 | def post_order_traversal(node, path): # Обхід дерева у післяпорядку: Ліво -> Право -> Корінь 37 | if node: 38 | post_order_traversal(node.left, path) 39 | post_order_traversal(node.right, path) 40 | path.append(node) 41 | 42 | 43 | # -------------------- Тестування ----------------------- 44 | if __name__ == "__main__": 45 | tree_root = build_sample_tree() 46 | 47 | # Анімований обхід дерева у передпорядку 48 | animate_traversal(tree_root, pre_order_traversal, 'Pre-Order') 49 | 50 | # Анімований обхід дерева у порядку: 51 | animate_traversal(tree_root, in_order_traversal, 'In-Order') 52 | 53 | # Анімований обхід дерева у післяпорядку: 54 | animate_traversal(tree_root, post_order_traversal, 'Post-Order') 55 | -------------------------------------------------------------------------------- /tree/usefull_link.md: -------------------------------------------------------------------------------- 1 | Tree Data Structure 2 | https://www.geeksforgeeks.org/tree-data-structure/ 3 | 4 | Heap vs Binary Search Tree (BST) 5 | https://stackoverflow.com/questions/6147242/heap-vs-binary-search-tree-bst 6 | 7 | Practical Use Case of Tree Data Structure: 8 | https://abhinavsingh007.medium.com/practical-use-case-of-tree-data-structure-a-java-developers-guide-4f3c6abd946d -------------------------------------------------------------------------------- /tree/visual_tree.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import networkx as nx 3 | from matplotlib.animation import FuncAnimation 4 | import random 5 | from time import sleep 6 | import warnings 7 | warnings.filterwarnings('ignore') 8 | 9 | def build_graph_edges(node, edges=None): 10 | if edges is None: 11 | edges = [] 12 | if node: 13 | if node.left: 14 | edges.append((node.value, node.left.value)) 15 | build_graph_edges(node.left, edges) 16 | if node.right: 17 | edges.append((node.value, node.right.value)) 18 | build_graph_edges(node.right, edges) 19 | return edges 20 | 21 | 22 | def hierarchy_pos(G, root=None, width=1., vert_gap=0.2, vert_loc=0, xcenter=0.5): 23 | if not nx.is_tree(G): 24 | raise TypeError('cannot use hierarchy_pos on a graph that is not a tree') 25 | 26 | if root is None: 27 | if isinstance(G, nx.DiGraph): 28 | root = next(iter(nx.topological_sort(G))) 29 | else: 30 | root = random.choice(list(G.nodes)) 31 | 32 | def _hierarchy_pos(G, root, width=1., vert_gap=0.2, vert_loc=0, xcenter=0.5, pos=None, parent=None): 33 | if pos is None: 34 | pos = {root: (xcenter, vert_loc)} 35 | else: 36 | pos[root] = (xcenter, vert_loc) 37 | children = list(G.neighbors(root)) 38 | if not isinstance(G, nx.DiGraph) and parent is not None: 39 | children.remove(parent) 40 | if len(children) != 0: 41 | dx = width / len(children) 42 | nextx = xcenter - width / 2 - dx / 2 43 | for child in children: 44 | nextx += dx 45 | pos = _hierarchy_pos(G, child, width=dx, vert_gap=vert_gap, 46 | vert_loc=vert_loc - vert_gap, xcenter=nextx, 47 | pos=pos, parent=root) 48 | return pos 49 | 50 | return _hierarchy_pos(G, root, width, vert_gap, vert_loc, xcenter) 51 | 52 | 53 | def animate_traversal(tree_root, traversal_func, traversal_type): 54 | path = [] 55 | traversal_func(tree_root, path) 56 | print(traversal_type, [x.value for x in path]) 57 | G = nx.DiGraph() 58 | G.add_edges_from(build_graph_edges(tree_root)) 59 | pos = hierarchy_pos(G, root=tree_root.value) 60 | 61 | fig, ax = plt.subplots() 62 | 63 | def update(frame): 64 | sleep(0.5) 65 | ax.clear() 66 | nodes = [node.value for node in path[:frame + 1]] 67 | nx.draw(G, pos=pos, with_labels=True, node_size=2000, node_color='lightblue', ax=ax, font_weight='bold') 68 | nx.draw_networkx_nodes(G, pos=pos, nodelist=nodes, node_color='orange', node_size=2000, ax=ax) 69 | ax.set_title(f'{traversal_type} Traversal - Step {frame + 1}/{len(path)}') 70 | 71 | ani = FuncAnimation(fig, update, frames=len(path), repeat=True) 72 | plt.show() 73 | 74 | 75 | # Visualization functions 76 | def build_graph_edges_avl(node, edges=None): 77 | if edges is None: 78 | edges = [] 79 | if node: 80 | if node.left_child: 81 | edges.append((node.data, node.left_child.data)) 82 | build_graph_edges_avl(node.left_child, edges) 83 | if node.right_child: 84 | edges.append((node.data, node.right_child.data)) 85 | build_graph_edges_avl(node.right_child, edges) 86 | return edges 87 | 88 | 89 | def hierarchy_pos(G, root=None, width=1., vert_gap=0.2, vert_loc=0, xcenter=0.5): 90 | if not nx.is_tree(G): 91 | raise TypeError('cannot use hierarchy_pos on a graph that is not a tree') 92 | 93 | if root is None: 94 | if isinstance(G, nx.DiGraph): 95 | root = next(iter(nx.topological_sort(G))) 96 | else: 97 | root = random.choice(list(G.nodes)) 98 | 99 | def _hierarchy_pos(G, root, width=1., vert_gap=0.2, vert_loc=0, xcenter=0.5, pos=None, parent=None): 100 | if pos is None: 101 | pos = {root: (xcenter, vert_loc)} 102 | else: 103 | pos[root] = (xcenter, vert_loc) 104 | children = list(G.neighbors(root)) 105 | if not isinstance(G, nx.DiGraph) and parent is not None: 106 | children.remove(parent) 107 | if len(children) != 0: 108 | dx = width / len(children) 109 | nextx = xcenter - width / 2 - dx / 2 110 | for child in children: 111 | nextx += dx 112 | pos = _hierarchy_pos(G, child, width=dx, vert_gap=vert_gap, 113 | vert_loc=vert_loc - vert_gap, xcenter=nextx, 114 | pos=pos, parent=root) 115 | return pos 116 | 117 | return _hierarchy_pos(G, root, width, vert_gap, vert_loc, xcenter) 118 | 119 | 120 | 121 | def animate_traversal_avl(tree_root, traversal_func, traversal_type=None): 122 | path = [] 123 | traversal_func(tree_root, path) 124 | 125 | G = nx.DiGraph() 126 | G.add_edges_from(build_graph_edges_avl(tree_root)) 127 | pos = hierarchy_pos(G, root=tree_root.data) 128 | fig, ax = plt.subplots() 129 | 130 | def update(frame): 131 | sleep(0.5) 132 | ax.clear() 133 | nodes = [node.data for node in path[:frame + 1]] 134 | nx.draw(G, pos=pos, with_labels=True, node_size=2000, node_color='lightblue', ax=ax, font_weight='bold') 135 | nx.draw_networkx_nodes(G, pos=pos, nodelist=nodes, node_color='orange', node_size=2000, ax=ax) 136 | ax.set_title(f'{traversal_type} Traversal - Step {frame + 1}/{len(path)}') 137 | 138 | if not traversal_type: 139 | nx.draw(G, pos=pos, with_labels=True, node_size=2000, node_color='lightblue', ax=ax, font_weight='bold') 140 | plt.show() 141 | else: 142 | print(traversal_type, [x.data for x in path]) 143 | ani = FuncAnimation(fig, update, frames=len(path), repeat=True) 144 | plt.show() --------------------------------------------------------------------------------