├── .gitignore ├── img ├── hash-example.png ├── levin.png ├── levin_example.png ├── levin_final.png └── manaker.png ├── readme.md ├── temp.py ├── Бинарный поиск.py ├── Графы ├── __init__.py ├── img │ ├── graph.png │ └── linear.png ├── readme.md ├── Белман_Форд.py ├── Дейкстра (неявная очередь с приоритетами).py ├── Кратчайший_путь_в_ацикличном_графе.py ├── Поиск цикла с отрицательным весом.py ├── Топологическая_сортировка.py └── Флойд_Уоршел.py ├── Динамическое программирование ├── readme.md ├── Наибольшая возрастающая последовательность.py ├── Наибольшая общая последовательность.py ├── Наибольшая пилообразная последовательность.py ├── Расстояние Левенштейна.py └── С.py ├── Сортировки ├── readme.md ├── test.py ├── Быстрая сортировка.py ├── Сортировка вставкой.py ├── Сортировка выбором.py ├── Сортировка подсчетом.py └── Сортировка слиянием.py ├── Структуры данных ├── MaxHeap.py ├── Односвязный список.py └── Стек.py └── Суффиксы, префиксы и подстроки ├── Z-function.py ├── hashing.py ├── manaker.py ├── prefix-function.py ├── readme.md └── suffix-array.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | RuCode 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | .idea/ 108 | .idea 109 | env/ 110 | venv/ 111 | ENV/ 112 | env.bak/ 113 | venv.bak/ 114 | 115 | # Spyder project settings 116 | .spyderproject 117 | .spyproject 118 | 119 | # Rope project settings 120 | .ropeproject 121 | 122 | # mkdocs documentation 123 | /site 124 | 125 | # mypy 126 | .mypy_cache/ 127 | .dmypy.json 128 | dmypy.json 129 | 130 | # Pyre type checker 131 | .pyre/ 132 | 133 | .DS_Store 134 | -------------------------------------------------------------------------------- /img/hash-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gardiys/Algoritms/8b41f3fe695f506d9f316cc418c3e2452e9d8190/img/hash-example.png -------------------------------------------------------------------------------- /img/levin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gardiys/Algoritms/8b41f3fe695f506d9f316cc418c3e2452e9d8190/img/levin.png -------------------------------------------------------------------------------- /img/levin_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gardiys/Algoritms/8b41f3fe695f506d9f316cc418c3e2452e9d8190/img/levin_example.png -------------------------------------------------------------------------------- /img/levin_final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gardiys/Algoritms/8b41f3fe695f506d9f316cc418c3e2452e9d8190/img/levin_final.png -------------------------------------------------------------------------------- /img/manaker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gardiys/Algoritms/8b41f3fe695f506d9f316cc418c3e2452e9d8190/img/manaker.png -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Алгоритмы на Python 2 | Привет, уважаемый исследователь гитхаба в поиске интересных репозиториев. 3 | Это мои конспекты по алгоритмам и структурам данных, ничего от них не жди 4 | я просто люблю все **забывать** :) 5 | 6 | ## Оглавление 7 | 1. [Динамическое программирование](Динамическое%20программирование) 8 | 2. [Суффиксы, префиксы и подстроки](Суффиксы,%20префиксы%20и%20подстроки) 9 | 3. Структуры данных 10 | + [Куча с максимумом в вершине](#max-heap) 11 | 4. Поиск элементов 12 | + [Бинарный поиск](#binary-search) 13 | 5. [Векторная алгебра] 14 | 6. [Теория графов](Графы) 15 | 7. [Сортировки](Сортировки) 16 | 17 | [Источники](#sorces) 18 | 19 | 20 | ## Источники 21 | + https://e-maxx.ru/algo/prefix_function 22 | + https://e-maxx.ru/algo/z_function 23 | + https://algorithmica.org/ru/string-searching 24 | + https://algorithmica.org/ru/hashing 25 | + https://codeforces.com/edu/course/2 26 | + https://codeforces.com/blog/entry/9612 27 | + https://stepik.org/course/64454/syllabus?auth=registration 28 | + [Алгоритм Манакера](https://neerc.ifmo.ru/wiki/index.php?title=Алгоритм_Манакера) 29 | + [Томас Х. Кормен - Алгоритмы: Вводный курс]() -------------------------------------------------------------------------------- /temp.py: -------------------------------------------------------------------------------- 1 | def evclid_nod(a,b): 2 | 3 | while b != 0: 4 | a, b = b, a % b 5 | return a 6 | 7 | def nok(a,b): 8 | nod = evclid_nod(a, b) 9 | return a*b/nod 10 | 11 | 12 | def is_prostoe(n): 13 | d = 2 14 | while d < n ** 0.5: 15 | if n % d == 0: 16 | return False 17 | d += 1 18 | return True 19 | 20 | 21 | def all_prostoe(a): 22 | for i in range(2,a+1): 23 | if is_prostoe(i): # (O(√n * n)) 24 | print(i) 25 | 26 | def resheto(n): 27 | # 0 1 2 28 | # 2 3 4 29 | numbers = list(range(2, n+1)) 30 | 31 | for i in range(2, int(n ** 0.5)): 32 | 33 | t = 2 * i 34 | 35 | while t <= n: 36 | numbers[t-2] = 0 37 | t += i 38 | return numbers 39 | 40 | import time 41 | start = time.time() 42 | n = resheto(1_000_0000) 43 | print("resheto", time.time()-start) 44 | 45 | 46 | start = time.time() 47 | numbers = set(n) 48 | numbers.remove(0) 49 | 50 | sorted(numbers) # О(n * log(n)) 51 | print("#1", time.time()-start) 52 | 53 | start = time.time() 54 | 55 | mass = [] 56 | for i in n: 57 | if i != 0: 58 | mass.append(i) 59 | 60 | print("#2", time.time()-start) -------------------------------------------------------------------------------- /Бинарный поиск.py: -------------------------------------------------------------------------------- 1 | def binary_search(k, a): 2 | 3 | l, r = 0, len(a) - 1 4 | while l <= r: 5 | m = l + (r - l) // 2 6 | if k == a[m]: 7 | return m + 1 8 | elif k > a[m]: 9 | l = m + 1 10 | else: 11 | r = m - 1 12 | return -1 13 | 14 | def binary_search_with_counting(k, a, last:bool): 15 | n = len(k) 16 | l, r = 0, len(a) - 1 17 | while l <= r: 18 | m = l + (r - l) // 2 19 | if k > a[m] or last and k == a[m]: 20 | l = m + 1 21 | elif k < a[m] or (not last) and k == a[m]: 22 | r = m - 1 23 | return r if last else l 24 | 25 | 26 | def main(): 27 | result = "" 28 | a = tuple(map(int, input().split()[1:])) 29 | b = tuple(map(int, input().split()[1:])) 30 | for i in b: 31 | print(binary_search(i, a), end=' ') 32 | 33 | def main2(): 34 | a = input() 35 | pattern = input() 36 | right = binary_search(pattern,a,True) 37 | left = binary_search(pattern,a,False) 38 | print(right - left + 1) 39 | 40 | if __name__ == "__main__": 41 | main() -------------------------------------------------------------------------------- /Графы/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gardiys/Algoritms/8b41f3fe695f506d9f316cc418c3e2452e9d8190/Графы/__init__.py -------------------------------------------------------------------------------- /Графы/img/graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gardiys/Algoritms/8b41f3fe695f506d9f316cc418c3e2452e9d8190/Графы/img/graph.png -------------------------------------------------------------------------------- /Графы/img/linear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gardiys/Algoritms/8b41f3fe695f506d9f316cc418c3e2452e9d8190/Графы/img/linear.png -------------------------------------------------------------------------------- /Графы/readme.md: -------------------------------------------------------------------------------- 1 | # Графы 2 | 3 | ## Оглавление 4 | + [Топологическая сортировка](#topological_sort) 5 | + [Кратчайший путь в ацикличном ориентированном графе](#nearect_a_circle) 6 | + [Алгоритм Дейкстры](#dijkstra) 7 | 8 | ## Теория 9 | 10 | ### Топологическая сортировка 11 | **Топологическая сортировка** - линейное упорядочивание вершин, которое 12 | нужно, чтобы понять, как обходить вершины ориентированного графа. 13 | 14 | Пример: 15 | Например есть вот такой граф: 16 | 17 | ![Граф](img/graph.png) 18 | 19 | Линейное упорядочивание будет для этого графа вот таким: 20 | 21 | ![Линейное упорядочивание](img/linear.png) 22 | 23 | #### Для чего нужна топологическая сортировка: 24 | + Чтобы понять как обходить граф, если есть такая ситуация, что мы не можем 25 | выполнять некоторые действия до завершения других 26 | 27 | 28 | #### Алгоритм 29 | 1) Создаем массив для линейного упорядочивания, заполняем нулями 30 | 2) Рассчитываем количество родителей каждой вершины 31 | 3) Находим все родительские вершины, у них в полученном массиве будет 0 родителей 32 | 4) Закидываем родителя в конец массива линейного упорядочивания, и у всех 33 | детей этой вершины уменьшаем количество родителей на 1. 34 | Если появились вершины, у которых кол-во родителей стало = 0, тогда 35 | эта вершина стала родительской и нужно опять проделать те же операции. 36 | 37 | 5) Повторяем шаг 4 пока родители не закончатся, а потом фильтруем полученный массив от нулей 38 | 39 | Время работы топологической сортировки `О(n+m)` 40 | 41 | 42 | ### Кратчайший путь в ацикличном графе 43 | 44 | #### Алгоритм 45 | 1) Получаем линейное упорядочивание топологической сортировкой 46 | 2) Создаем два массива `shortest` и `pred` - кратчайшие расстояния и кратчайшие пути 47 | 3) Заполняем массив `shortest` бесконечностями, чтобы обозначить недосягаемые вершины, 48 | а массив `pred` значениями `None`. 49 | 50 | 4) `shortest[s] = 0`, где `s`- стартовая вершина 51 | 5) Перебираем все смежные вершины с вершинами из линейного упорядочивания и смотрим - 52 | нашелся ли путь короче, и заменяем. 53 | 54 | Время работы алгоритма `О(n+m)` 55 | 56 | ### Алгоритм Дейкстры 57 | Алгоритм нужен для поиска кратчайшего пути между двумя точками. 58 | 59 | #### Через неявную очередь с приоритетами 60 | 61 | Алгоритм: 62 | 1) Создаем два массива `shortest` и `pred` - кратчайшие расстояния и кратчайшие пути 63 | 2) Заполняем массив `shortest` бесконечностями, чтобы обозначить недосягаемые вершины, 64 | а массив `pred` значениями `None`. 65 | 3) Создаем множество `Q`, содержащее все вершины 66 | 4) Организуем цикл: пока `Q` не пустое находим во множестве Q вершину 67 | с наименьшим значением в массиве `shortest` и удаляем ее из множества 68 | 5) Перебираем все смежные вершины с удаленной из множества и меняем пути 69 | в массиве shortest на более короткие путем сравнения с начальным значением. -------------------------------------------------------------------------------- /Графы/Белман_Форд.py: -------------------------------------------------------------------------------- 1 | def relax(w, u, v, shortest, pred): 2 | """ 3 | :param w: Веса графа 4 | :param u: Вершина 1 5 | :param v: Вершина 2 6 | :param shortest: Массив минимальных расстояний до точки 7 | :param pred: Массив по которому можно восстановить кратчайший путь 8 | :return: Функция уменьшает значения для вершин, если нашелся такой путь, который короче чем был до этого 9 | """ 10 | # Если путь новой вершины короче, чем тот который был сохранен ранее, тогда 11 | # меняем значения 12 | if shortest[u] + w[(u, v)] < shortest[v]: 13 | shortest[v] = shortest[u] + w[(u, v)] 14 | pred[v] = u 15 | 16 | 17 | def belman_ford(g, w, n, s): 18 | shortest = [float('inf')] * (n + 1) 19 | shortest[s] = 0 # Расстояние от начальной вершины = 0 тк мы уже достигли ее 20 | pred = [None] * (n + 1) # Массив, в котором хранятся пути для того, чтобы обратно воссоздать маршрут 21 | 22 | for i in range(1, n): 23 | for v in g.get(i, []): 24 | relax(w, i, v, shortest, pred) 25 | 26 | return shortest, pred 27 | 28 | 29 | if __name__ == "__main__": 30 | graph = { 31 | 1: [2, 3], 32 | 2: [3], 33 | 3: [4, 5, 1] 34 | } 35 | 36 | weights = { 37 | (1, 2): 4, 38 | (1, 3): 6, 39 | (2, 3): 1, 40 | (3, 4): 2, 41 | (3, 5): 3, 42 | (3, 1): 1 43 | } 44 | print(belman_ford(graph, weights, 5, 3)) -------------------------------------------------------------------------------- /Графы/Дейкстра (неявная очередь с приоритетами).py: -------------------------------------------------------------------------------- 1 | def relax(w, u, v, shortest, pred): 2 | """ 3 | :param w: Веса графа 4 | :param u: Вершина 1 5 | :param v: Вершина 2 6 | :param shortest: Массив минимальных расстояний до точки 7 | :param pred: Массив по которому можно восстановить кратчайший путь 8 | :return: Функция уменьшает значения для вершин, если нашелся такой путь, который короче чем был до этого 9 | """ 10 | # Если путь новой вершины короче, чем тот который был сохранен ранее, тогда 11 | # меняем значения 12 | if shortest[u] + w[(u, v)] < shortest[v]: 13 | shortest[v] = shortest[u] + w[(u, v)] 14 | pred[v] = u 15 | 16 | 17 | def dijkstra(g, w, n, s): 18 | shortest = [float('inf')] * (n + 1) 19 | shortest[s] = 0 # Расстояние от начальной вершины = 0 тк мы уже достигли ее 20 | pred = [None] * (n + 1) # Массив, в котором хранятся пути для того, чтобы обратно воссоздать маршрут 21 | 22 | q = set(range(1, n+1)) # Заносим все вершины во множество q 23 | 24 | while len(q) > 0: # Пока множество q непустое выполняем следующие действия 25 | min_u = next(iter(q)) # Это короткий способ взять один элемент из множества 26 | 27 | # В цикле ищем вершину, у которой наименьшее значение кратчайшего расстояния 28 | for u in q: 29 | if shortest[u] < shortest[min_u]: 30 | min_u = u 31 | 32 | # Удаляем найденную вершину 33 | q.discard(min_u) 34 | 35 | # Для каждой вершины, которая смежная с минимальной, вызываем процедуру Relax 36 | for v in g.get(min_u, []): 37 | relax(w, min_u, v, shortest, pred) 38 | 39 | return shortest, pred 40 | 41 | 42 | if __name__ == "__main__": 43 | graph = { 44 | 1: [2, 3], 45 | 2: [3], 46 | 3: [4, 5, 1] 47 | } 48 | 49 | weights = { 50 | (1, 2): 4, 51 | (1, 3): 6, 52 | (2, 3): 1, 53 | (3, 4): 2, 54 | (3, 5): 3, 55 | (3, 1): 1 56 | } 57 | print(dijkstra(graph, weights, 5, 3)) -------------------------------------------------------------------------------- /Графы/Кратчайший_путь_в_ацикличном_графе.py: -------------------------------------------------------------------------------- 1 | def relax(w, u, v, shortest, pred): 2 | """ 3 | :param w: Веса графа 4 | :param u: Вершина 1 5 | :param v: Вершина 2 6 | :param shortest: Массив минимальных расстояний до точки 7 | :param pred: Массив по которому можно восстановить кратчайший путь 8 | :return: Функция уменьшает значения для вершин, если нашелся такой путь, который короче чем был до этого 9 | """ 10 | # Если путь новой вершины короче, чем тот который был сохранен ранее, тогда 11 | # меняем значения 12 | if shortest[u] + w[(u, v)] < shortest[v]: 13 | shortest[v] = shortest[u] + w[(u, v)] 14 | pred[v] = u 15 | 16 | 17 | def dag_shortest_paths(g, w, n, s): 18 | """ 19 | :param g: Граф, хранимый списком смежности 20 | :param w: Веса графа 21 | :param n: Количество вершин в графе 22 | :param s: Вершина от которой находятся расстояния до других 23 | :return: Кратчайшие пути до вершин 24 | """ 25 | from Топологическая_сортировка import topological_sort 26 | l = topological_sort(g, n) # Выполняем линейное упорядочивание топологической сортировкой 27 | # Создаем массив, состоящий из бесконечностей, чтобы обозначить недосягаемые вершины 28 | shortest = [float('inf')] * (n+1) 29 | shortest[s] = 0 # Расстояние от начальной вершины = 0 тк мы уже достигли ее 30 | pred = [None] * (n+1) # Массив, в котором хранятся пути для того, чтобы обратно воссоздать маршрут 31 | for u in l: # Перебираем линейно упорядоченные вершины 32 | for v in g.get(u, []): 33 | relax(w, u, v, shortest, pred) # перестраиваем пути для каждой смежной вершины 34 | 35 | return shortest, pred 36 | 37 | 38 | if __name__ == "__main__": 39 | graph = { 40 | 1: [2, 3], 41 | 2: [3], 42 | 3: [4, 5] 43 | } 44 | 45 | weights = { 46 | (1, 2): 4, 47 | (1, 3): 6, 48 | (2, 3): 1, 49 | (3, 4): 2, 50 | (3, 5): 3 51 | } 52 | 53 | print(dag_shortest_paths(graph, weights, 5, 1)) 54 | -------------------------------------------------------------------------------- /Графы/Поиск цикла с отрицательным весом.py: -------------------------------------------------------------------------------- 1 | from Белман_Форд import belman_ford 2 | 3 | 4 | def find_negative_weight_cycle(g, w, n, shortest, pred): 5 | # Обходим все ребра 6 | for u in range(1, n): 7 | for v in g.get(u, []): 8 | if shortest[u] + w[(u, v)] < shortest[v]: 9 | # Если мы попали в тело этого if, тогда мы точно знаем, что 10 | # цикл с отрицательными весами уже есть 11 | 12 | # находим этот цикл 13 | visited = [False] * (n + 1) 14 | x = v 15 | # восстанавливаем цикл 16 | while x is not None and not visited[x]: 17 | visited[x] = True 18 | x = pred[x] 19 | # Собираем вершины цикла 20 | v = pred[x] 21 | cycle = [x] 22 | while v != x: 23 | cycle.append(v) 24 | v = pred[v] 25 | return cycle[::-1] 26 | return [] 27 | 28 | 29 | if __name__ == "__main__": 30 | graph = { 31 | 1: [2, 3], 32 | 2: [3], 33 | 3: [4, 5, 1] 34 | } 35 | 36 | weights = { 37 | (1, 2): -4, 38 | (1, 3): 6, 39 | (2, 3): -1, 40 | (3, 4): 2, 41 | (3, 5): -3, 42 | (3, 1): 1 43 | } 44 | shortest, pred = belman_ford(graph, weights, 5, 1) 45 | print(find_negative_weight_cycle(graph, weights, 5, shortest, pred)) 46 | -------------------------------------------------------------------------------- /Графы/Топологическая_сортировка.py: -------------------------------------------------------------------------------- 1 | def topological_sort(g: {}, n: int): 2 | """ 3 | :param g: Ориентированный ацикличный граф с вершинами, пронумерованными от 1 до n. 4 | граф представлен в виде списка смежности 5 | :param n: Количество вершин в графе 6 | :return: Линейное упорядочивание вершин, такое, что вершина u находится в нем до вершины v, 7 | если (u, v) - ребро графа 8 | """ 9 | # Рассчитываем для каждой вершины количество путей в нее из других вершин 10 | in_degree = [0] * (n+1) # линейное упорядочивание вершин 11 | for u, value in g.items(): 12 | for v in value: 13 | in_degree[v] += 1 14 | 15 | # Находим вершины в которые не ведут другие 16 | next_ = [u for u in range(len(in_degree)) if in_degree[u] == 0 and u != 0] 17 | 18 | while len(next_) > 0: 19 | u = next_.pop() # Удаляем вершину - родителя, в которую не ведут другие 20 | in_degree.append(u) # добавляем ее в конец линейного упорядочивания 21 | # в каждой смежной вершине с родительской уменьшаем количество 22 | # связей, тк мы уже добавили вершину в линейное упорядочивание 23 | # Эта вершина является обработанной, и можно представить как будто ее уже нет в графе 24 | for v in g.get(u,[]): 25 | in_degree[v] -= 1 26 | # Если количество связей упало до нуля, то 27 | # вершина теперь родительская, и ее можно добавить следующей в словарь на обработку 28 | if in_degree[v] == 0: 29 | next_.append(v) 30 | return [i for i in in_degree if i != 0] 31 | 32 | 33 | if __name__ == "__main__": 34 | graph = { 35 | 1: [2,3], 36 | 2: [3], 37 | 3: [4,5] 38 | } 39 | print(topological_sort(graph, 5)) 40 | -------------------------------------------------------------------------------- /Графы/Флойд_Уоршел.py: -------------------------------------------------------------------------------- 1 | def floyd_warshall(g, n): 2 | shortest = [[float('inf') for _ in range(n)] for _ in range(n)] 3 | pred = [[None for _ in range(n)] for _ in range(n)] 4 | for u in range(n): 5 | for v in range(n): 6 | shortest[u][v] = g[u][v] 7 | if g[u][v] != float("inf"): 8 | pred[u][v] = u 9 | 10 | for x in range(n): 11 | for u in range(n): 12 | for v in range(n): 13 | if shortest[u][v] > shortest[u][x] + shortest[x][v]: 14 | shortest[u][v] = shortest[u][x] + shortest[x][v] 15 | pred[u][v] = pred[x][v] 16 | 17 | return shortest, pred 18 | 19 | 20 | if __name__ == "__main__": 21 | graph = [ 22 | [0, 3, 8, float("inf")], 23 | [float("inf"), 0, float('inf'), 1], 24 | [float("inf"), 4, 0, float('inf')], 25 | [2, float('inf'), -5, 0] 26 | ] 27 | n = 4 28 | print(floyd_warshall(graph, n)) 29 | 30 | -------------------------------------------------------------------------------- /Динамическое программирование/readme.md: -------------------------------------------------------------------------------- 1 | # Динамическое программирование 2 | ## Оглавление 3 | + [Набольшая общая подпоследовательность](#nop) 4 | + [Наибольшая возрастающая подпоследовательность](#nvp) 5 | + [Расстояние Левенштейна](#levinshtein) 6 | + [Пилообразная подпоследовательность](#saw-raw) 7 | + [Задача о рюкзаке] 8 | 9 | ## Теория 10 | 11 | Динамическое программирование - способ решения сложных задач путем 12 | сведения их к более простым такого же типа. 13 | 14 | **Плюсы:** 15 | + Увеличение скорости 16 | 17 | **Минусы:** 18 | + Дополнительные расходы памяти 19 | 20 | **План решения задачи на ДП:** 21 | 1. Определить целевую функцию *f*; 22 | 2. Решить задачу для маленьких ограничений; 23 | 3. Получить реккурентную формулу; 24 | 4. Определить ограничения и задать начальные значения; 25 | 5. Определить порядок вычислений; 26 | 6. Определить где будет ответ; 27 | 7. Восстановить ответ. 28 | 29 | ### Наибольшая общая подпоследовательность 30 | Задача нахождения наибольшей общей подпоследовательности 31 | (англ. longest common subsequence, LCS) — задача 32 | поиска последовательности, которая является 33 | подпоследовательностью нескольких последовательностей 34 | (обычно двух). 35 | 36 | Подпоследовательность можно получить из некоторой 37 | конечной последовательности, если удалить из 38 | последней некоторое множество её элементов 39 | (возможно пустое). Например, **BCDB** является 40 | подпоследовательностью последовательности **ABCDBAB**. 41 | 42 | #### Решение за O(n2) 43 | Обозначим через F(i) длину наибольшей возрастающей 44 | подпоследовательности, последним элементом которой 45 | будет элемент ai. 46 | Рассмотрим предпоследний элемент этой последовательности 47 | aj, тогда aj < ai и j < i. 48 | Необходимо найти такое подходящее j, что F(j) будет наибольшим. 49 | 50 | ```python 51 | a = input() 52 | b = input() 53 | 54 | n, m = len(a), len(b) 55 | 56 | f = [] 57 | for i in range(n+1): 58 | f.append([0]*(m+1)) 59 | 60 | for i in range(1,n+1): 61 | for j in range(1,m+1): 62 | if a[i-1] == b[j-1]: 63 | f[i][j] = f[i-1][j-1] + 1 64 | else: 65 | f[i][j] = max(f[i-1][j],f[i][j-1]) 66 | print(f[-1][-1]) 67 | ``` 68 | #### Восстановление ответа 69 | Выполним «обратный проход» по массиву 70 | начиная с последнего элемента. 71 | 72 | ```python 73 | i = n 74 | j = m 75 | ans = [] 76 | while i > 0 and j > 0: 77 | if a[i-1] == b[j-1]: 78 | ans.append(a[i-1]) 79 | i -= 1 80 | j -= 1 81 | elif f[i][j-1] == f[i][j]: 82 | j -= 1 83 | else: 84 | i -= 1 85 | print(*a[::-1]) 86 | ``` 87 | ### Наибольшая возрастающая подпоследовательность 88 | Задача поиска наибольшей возрастающей подпоследовательности 89 | состоит в нахождении наиболее длинной 90 | возрастающей подпоследовательности в 91 | данной последовательности элементов. 92 | 93 | #### Решение за O(n2) 94 | 95 | ##### Способ 1: 96 | 97 | + Отсортировать последовательность в порядке неубывания, 98 | + Удалим из нее повторяющиеся элементы (то есть получим строго возрастающую 99 | последовательность из элементов изначальной последовательности, 100 | + Для изначальной и отсортированной последовательностей и 101 | найдем наибольшую общую подпоследовательность. 102 | 103 | ##### Способ 2: 104 | 105 | + Обозначим через F(i) длину наибольшей возрастающей 106 | подпоследовательности, последним элементом которой 107 | будет элемент ai. 108 | + Рассмотрим предпоследний элемент этой 109 | последовательности aj, тогда 110 | aji и j f[i]: 121 | f[i] = f[j] 122 | p[i] = j 123 | f[i] += 1 124 | 125 | max_el = max(f) 126 | print(max_el) 127 | ``` 128 | #### Восстановление ответа 129 | Массив p содержит индексы элементов, из которых он пришел, поэтому 130 | можно пройтись от максимального элемента до начала. 131 | ```python 132 | i = f.index(max_el) 133 | ans = [] 134 | while i != -1: 135 | ans.append(a[i]) 136 | i = p[i] 137 | print(*ans[::-1]) 138 | ``` 139 | 140 | ### Расстояние Левенштейна 141 | 142 | Расстояние Левенштейна (редакционное расстояние, 143 | дистанция редактирования) — метрика, измеряющая 144 | разность между двумя последовательностями символов. 145 | 146 | Она определяется как минимальное количество 147 | односимвольных операций (а именно вставки, удаления, 148 | замены), необходимых для превращения одной последовательности символов в другую. 149 | 150 | В общем случае, операциям, используемым в этом 151 | преобразовании, можно назначить разные цены. 152 | 153 | Пусть S1, S2 — две строки (длиной M и N соответственно) 154 | над некоторым алфавитом, тогда редакционное расстояние 155 | (расстояние Левенштейна) d(S1,S2) можно подсчитать по 156 | следующей формуле (элементы строк нумеруются с первого) 157 | : d(S1, S2) = D(M, N), где 158 | 159 | ![Формула Левенштейна](../img/levin.png) 160 | 161 | Пример: 162 | 163 | Здесь шаг по i символизирует удаление (D) 164 | из первой строки, по j — вставку (I) 165 | в первую строку, а шаг по обоим индексам 166 | символизирует замену символа (R) или отсутствие 167 | изменений (M). 168 | 169 | ![Пример алгоритма левинштейна](../img/levin_example.png) 170 | 171 | #### Разные цены операций 172 | 173 | Цены операций могут зависеть от вида операции (вставка, удаление, замена) и/или от участвующих в ней символов, отражая разную вероятность мутаций в биологии, разную вероятность разных ошибок при вводе текста и т. д. В общем случае: 174 | + w(a, b) — цена замены символа a на символ b 175 | + w(ε, b) — цена вставки символа b 176 | + w(a, ε) — цена удаления символа a 177 | 178 | Необходимо найти последовательность замен, минимизирующую 179 | суммарную цену. Расстояние Левенштейна является частным 180 | случаем этой задачи при: 181 | w(a, а) = 0, w(a, b) = 1 при a≠b, w(ε, b) = 1, w(a, ε) = 1. 182 | 183 | ![Формула Левенштейна2](../img/levin_final.png) 184 | 185 | Листинг программы: 186 | 187 | ```python 188 | a = input() 189 | b = input() 190 | n, m = len(a), len(b) 191 | 192 | dp = [] 193 | for i in range(n+1): 194 | dp.append([0]*(m+1)) 195 | 196 | for i in range(1,n+1): 197 | dp[i][0] = i 198 | 199 | for j in range(1,m+1): 200 | dp[0][j] = j 201 | 202 | for i in range(1, n+1): 203 | for j in range(1, m+1): 204 | dp[i][j] = min(dp[i-1][j-1] + (a[i-1] != b[j-1]), 205 | dp[i][j-1]+1, dp[i-1][j] + 1) 206 | print(dp[-1][-1]) 207 | 208 | ``` 209 | 210 | ### Пилообразная подпоследовательность 211 | -------------------------------------------------------------------------------- /Динамическое программирование/Наибольшая возрастающая последовательность.py: -------------------------------------------------------------------------------- 1 | n = int(input()) 2 | a = [int(i) for i in input().split()] 3 | f = [0] * n 4 | p = [-1] * n 5 | for i in range(n): 6 | max_el = 0 7 | for j in range(i): 8 | if a[j] < a[i] and f[j] > f[i]: 9 | f[i] = f[j] 10 | p[i] = j 11 | f[i] += 1 12 | 13 | max_el = max(f) 14 | print(max_el) 15 | i = f.index(max_el) 16 | ans = [] 17 | while i != -1: 18 | ans.append(a[i]) 19 | i = p[i] 20 | print(*ans[::-1]) -------------------------------------------------------------------------------- /Динамическое программирование/Наибольшая общая последовательность.py: -------------------------------------------------------------------------------- 1 | a = input() 2 | b = input() 3 | 4 | n, m = len(a), len(b) 5 | 6 | f = [] 7 | for i in range(n+1): 8 | f.append([0]*(m+1)) 9 | 10 | for i in range(1,n+1): 11 | for j in range(1,m+1): 12 | if a[i-1] == b[j-1]: 13 | f[i][j] = f[i-1][j-1] + 1 14 | else: 15 | f[i][j] = max(f[i-1][j],f[i][j-1]) 16 | #print(f) 17 | i = n 18 | j = m 19 | ans_a = [] 20 | ans_b = [] 21 | while i > 0 and j > 0: 22 | if a[i-1] == b[j-1]: 23 | ans_a.append(i) 24 | ans_b.append(j) 25 | i -= 1 26 | j -= 1 27 | elif f[i][j-1] == f[i][j]: 28 | j -= 1 29 | else: 30 | i -= 1 31 | print(len(ans_a)) 32 | print(*ans_a[::-1]) 33 | print(*ans_b[::-1]) 34 | -------------------------------------------------------------------------------- /Динамическое программирование/Наибольшая пилообразная последовательность.py: -------------------------------------------------------------------------------- 1 | n = int(input()) 2 | a = [int(i) for i in input().split()] 3 | fp = [1] 4 | fs = [1] 5 | for i in range(1,n): 6 | max_fp = 1 7 | max_fs = 1 8 | for j in range(i): 9 | if a[i] > a[j] and (fs[j]+1) > max_fp: 10 | max_fp = fs[j] + 1 11 | if a[i] < a[j] and (fp[j]+1) > max_fs: 12 | max_fs = fp[j] + 1 13 | fp.append(max_fp) 14 | fs.append(max_fs) 15 | print(fp) 16 | print(fs) 17 | print(n - max(fp[-1], fs[-1])) -------------------------------------------------------------------------------- /Динамическое программирование/Расстояние Левенштейна.py: -------------------------------------------------------------------------------- 1 | a = input() 2 | b = input() 3 | n, m = len(a), len(b) 4 | 5 | dp = [] 6 | for i in range(n+1): 7 | dp.append([0]*(m+1)) 8 | 9 | for i in range(1,n+1): 10 | dp[i][0] = i 11 | 12 | for j in range(1,m+1): 13 | dp[0][j] = j 14 | 15 | for i in range(1, n+1): 16 | for j in range(1, m+1): 17 | dp[i][j] = min(dp[i-1][j-1] + (a[i-1] != b[j-1]), dp[i][j-1]+1, dp[i-1][j] + 1) 18 | print(dp) 19 | -------------------------------------------------------------------------------- /Динамическое программирование/С.py: -------------------------------------------------------------------------------- 1 | a = input() 2 | b = input() 3 | 4 | n, m = len(a), len(b) 5 | 6 | f = [] 7 | for i in range(n+1): 8 | f.append([0]*(m+1)) 9 | 10 | for i in range(1,n+1): 11 | for j in range(1,m+1): 12 | if a[i-1] == b[j-1] and i == j: 13 | f[i][j] = 1 14 | elif a[i-1] == "?" and i == j: 15 | f[i][j] = 1 16 | elif a[i-1] == "*" and j >= i-1: 17 | f[i][j] = 1 18 | else: 19 | f[i][j] = 0 20 | 21 | print(*f, sep="\n") -------------------------------------------------------------------------------- /Сортировки/readme.md: -------------------------------------------------------------------------------- 1 | # Сортировки 2 | 3 | ## Оглавление 4 | Общие сортировки: 5 | + [Сортировка выбором](#choice_sort) 6 | + [Сортировка вставкой](#insert_sort) 7 | + [Сортировка слиянием](#merge_sort) 8 | + [Быстрая сортировка](#quick_sort) 9 | 10 | Частные случаи: 11 | + [Сортировка подсчетом](#count_sort) 12 | 13 | ## Cводная таблица 14 | |Сортировка|Худший случай|Средний случай|Лучший случай|Доп память| 15 | |:----------:|-------------|:--------------:|:-------------:|:----------:| 16 | |Сортировка выбором| **О(n2)**| **О(n2)**|**О(n2)**|Нет| 17 | |Сортировка вставкой| **О(n2)**| **О(n)**|**О(n2)**|Нет| 18 | |Сортировка слиянием| **О(nlog(n))**| **О(nlog(n))**| **О(nlog(n))**|Да| 19 | |Быстрая сортировка| **О(n2)**| **О(nlog(n))**| **О(nlog(n))**|Нет| 20 | 21 | 22 | 23 | ## Теория 24 | 25 | 26 | ### Сортировка выбором 27 | 28 | #### Алгоритм: 29 | 1) Организуем цикл по массиву `range(n)` 30 | 2) На каждой итерации находим индекс самого маленького элемента в срезе от [i:n] 31 | 3) Меняем элемент i с найденным местами 32 | 33 | |Сортировка|Худший случай|Средний случай|Лучший случай|Доп память| 34 | |:----------:|-------------|:--------------:|:-------------:|:----------:| 35 | |Сортировка выбором| **О(n2)**| **О(n2)**|**О(n2)**|Нет| 36 | 37 | 38 | ### Сортировка вставкой 39 | 40 | #### Алгоритм 41 | 1) Организуем цикл по массиву `range(1,n)` 42 | 2) На каждой итерации идем в обратном порядке к началу массива и 43 | ищем элемент который меньше чем `a[i]` и меняем значение `a[j+1]=a[j]`, 44 | чтобы сделать смещение элементов при вставке нового. 45 | 46 | 3) Когда нашелся такой элемент или мы дошли до конца массива делаем 47 | `a[j+1] = a[i]` 48 | 49 | |Сортировка|Худший случай|Средний случай|Лучший случай|Доп память| 50 | |:----------:|-------------|:--------------:|:-------------:|:----------:| 51 | |Сортировка вставкой| **О(n2)**| **О(n)**|**О(n2)**|Нет| 52 | 53 | 54 | ### Сортировка слиянием 55 | 56 | #### Алгоритм 57 | Суть алгоритма заключается в реализации двух функций: 58 | `merge_sort(a,p,r)` и `merge(a,p,q,r)`, где `p`- левая граница сортируемого массива 59 | `q` - середина сортируемого отрезка, а `r`- правая граница. Все границы включительно. 60 | 61 | Алгоритм `merge_sort`: 62 | 0) Пишем условие выхода `if p >= r: return` (Если левая граница >= правой то длина массива меньше 1) 63 | 1) Находим середину сортируемого отрезка `q` = `(p+r) // 2` 64 | 2) Вызываем функции `merge_sort(a,p,q)` и `merge_sort(a, q+1,r)` 65 | 3) Вызываем функцию `merge(a,p,q,r)` 66 | 4) Возвращаем отсортированный массив 67 | 68 | Алгоритм `merge`: 69 | 1) Создаем два массива `b = a[p:q+1]` и `c = a[q+1:r+1]` - левая и правая часть отрезков 70 | 2) Закидываем им в конец бесконечности (нужно для удобства реализации) 71 | `b.append(float('inf'))` и `c.append(float('inf'))` 72 | 73 | 3) Заводим два индекса `i` и `j`. Они будут служить указателями на 74 | позиции в наших массивах. А далее итерируемся по массиву `a` (`range(p, r+1)`) и 75 | сравниваем элементы `b[i]` и `c[j]`. Если `b[i]` > `c[j]` тогда к `a[k]` присваиваем `b[i]` и увеличиваем i на единицу. 76 | Аналогично для `c[j]` и `j` в иной ситуации. 77 | 78 | 79 | |Сортировка|Худший случай|Средний случай|Лучший случай|Доп память| 80 | |:----------:|-------------|:--------------:|:-------------:|:----------:| 81 | |Сортировка слиянием| **О(nlog(n))**| **О(nlog(n))**| **О(nlog(n))**|Да| 82 | 83 | ### Быстрая сортировка 84 | 85 | #### Алгоритм 86 | Алгоритм заключается в реализации двух функций: `quick_sort(a,p,r)` и `partition(a,p,r)`, где a - сортируемый список 87 | p - левая граница, r - правая граница. 88 | 89 | Алгоритм `quick_sort(a,p,r)` 90 | 1. Находим опорный элемент при помощи функции `partition`. Этот элемент обладает некоторыми свойствами: 91 | + Он считается полностью отсортированным, то есть уже стоит на верной позиции 92 | + Элементы справа от него больше, элементы слева - меньше. То есть при нахождении 93 | опорного элемента удается разбить массив на 2 части. 94 | 95 | 2. Рекурсивно вызываем функции сортировки левого `quick_sort(a,p,q-1)` и правого `quick_sort(a,q+1,r)` отрезков. 96 | 3. Возвращаем отсортированный массив 97 | 98 | Алгоритм `partition(a,p,r)`: 99 | 1. Приравниваем `q` = `p`. 100 | 2. Итерируемся от начала до конца отрезка и ищем элементы которые меньше опорного и закидываем 101 | их в левую часть массива. 102 | 103 | 3. После завершения цикла меняем опорный элемент с элементом, стоящим после младших 104 | 105 | |Сортировка|Худший случай|Средний случай|Лучший случай|Доп память| 106 | |:----------:|-------------|:--------------:|:-------------:|:----------:| 107 | |Быстрая сортировка| **О(n2)**| **О(nlog(n))**| **О(nlog(n))**|Нет| 108 | 109 | ### Сортировка подсчетом 110 | 111 | #### Алгоритм 112 | 113 | Алгоритм сортировки подсчетом сводится к реализации трех функций: 114 | 1) `count_keys_equal(a, n, m)` - функция для подсчета количества различных цифр в списке. 115 | 2) `count_less_keys(equal, m)` - функция для расчета промежуточной суммы, где каждый элемент 116 | `less[j]` является суммой предыдущих элементов. 117 | 3) `rearrange(a, less, n)` - функция пересоздания массива с уже отсортированными значениями. 118 | 119 | -------------------------------------------------------------------------------- /Сортировки/test.py: -------------------------------------------------------------------------------- 1 | def get_random_mass(n): 2 | import random 3 | a = [0] * n 4 | for i in range(n): 5 | a[i] = random.randint(0, 1000) 6 | return a 7 | 8 | 9 | def test_correct(func): 10 | assert func([1, 2, 3, 4, 5]) == [1, 2, 3, 4, 5] 11 | assert func([5, 4, 3, 2, 1]) == [1, 2, 3, 4, 5] 12 | assert func([3, 2, 4, 5, 1]) == [1, 2, 3, 4, 5] 13 | assert func([1, 1, 3, 3, 2]) == [1, 1, 2, 3, 3] 14 | assert func([5, 5, 7, 7, 4]) == [4, 5, 5, 7, 7] 15 | mass = get_random_mass(1000) 16 | assert func(mass) == sorted(mass) 17 | print("Сортировка работает корректно") 18 | 19 | 20 | 21 | def test_time(func): 22 | import time 23 | a1 = get_random_mass(100) 24 | a2 = get_random_mass(1000) 25 | a3 = get_random_mass(10_000) 26 | a4 = get_random_mass(100_000) 27 | 28 | start = time.time() 29 | func(a1) 30 | print('10**2:', time.time() - start) 31 | 32 | start = time.time() 33 | func(a2) 34 | print('10**3:', time.time() - start) 35 | 36 | start = time.time() 37 | func(a3) 38 | print('10**4:', time.time() - start) 39 | 40 | start = time.time() 41 | func(a4) 42 | print('10**5:', time.time() - start) 43 | 44 | 45 | def tester(func): 46 | test_correct(func) 47 | test_time(func) 48 | -------------------------------------------------------------------------------- /Сортировки/Быстрая сортировка.py: -------------------------------------------------------------------------------- 1 | def quick_sort(a, p=None, r=None): 2 | """ 3 | :param a: Список чисел 4 | :param p: Левая граница списка 5 | :param r: Правая граница списка 6 | :return: Отсортированный список 7 | """ 8 | if p is None or r is None: 9 | p, r = 0, len(a)-1 10 | 11 | if p >= r: 12 | return 13 | 14 | q = partition(a,p,r) 15 | quick_sort(a,p,q-1) 16 | quick_sort(a,q+1,r) 17 | 18 | return a 19 | 20 | 21 | def partition(a,p,r): 22 | q = p 23 | # Суть цикла заключается в поиске элементов, которые меньше опорного. 24 | # За опорный элемент берется a[r]. 25 | # После такого разделения мы получим элемент, который будет полностью отсортирован. 26 | for u in range(p, r): 27 | if a[u] <= a[r]: 28 | a[q], a[u] = a[u], a[q] 29 | q += 1 30 | a[q], a[r] = a[r], a[q] 31 | 32 | return q 33 | 34 | 35 | if __name__ == "__main__": 36 | from test import tester 37 | 38 | tester(quick_sort) -------------------------------------------------------------------------------- /Сортировки/Сортировка вставкой.py: -------------------------------------------------------------------------------- 1 | def insertion_sort(a: []) -> []: 2 | """ 3 | :param a: Список чисел 4 | :param n: Количество элементов 5 | :return: Отсортированный список 6 | """ 7 | for i in range(1, len(a)): 8 | j = i - 1 9 | key = a[i] 10 | while j >= 0 and a[j] > key: 11 | a[j+1] = a[j] 12 | j -= 1 13 | a[j+1] = key 14 | return a 15 | 16 | 17 | if __name__ == "__main__": 18 | from test import tester 19 | 20 | tester(insertion_sort) -------------------------------------------------------------------------------- /Сортировки/Сортировка выбором.py: -------------------------------------------------------------------------------- 1 | def choice_sort(a: list) -> []: 2 | """ 3 | :param a: Сортируемый список 4 | :return: Отсортированный список 5 | 6 | Код работает быстрее за счет того, что используются стандартные С-функции 7 | """ 8 | for i in range(len(a)): 9 | smallest_i = a.index(min(a[i:]), i) 10 | a[smallest_i], a[i] = a[i], a[smallest_i] 11 | return a 12 | 13 | 14 | if __name__ == "__main__": 15 | from test import tester 16 | tester(choice_sort) 17 | -------------------------------------------------------------------------------- /Сортировки/Сортировка подсчетом.py: -------------------------------------------------------------------------------- 1 | def count_keys_equal(a, n, m): 2 | """ 3 | :param a: Список чисел 4 | :param n: Количество элементов в списке 5 | :param m: Диапазон значений 6 | :return: Массив, который содержит количество встречавшихся чисел 7 | """ 8 | equal = [0] * m 9 | 10 | for i in range(n): 11 | key = a[i] 12 | equal[key] += 1 13 | 14 | return equal 15 | 16 | 17 | def count_less_keys(equal, m): 18 | """ 19 | :param equal: Массив элементов, в которых посчитано количество 20 | :param m: Диапазон значений 21 | :return: Массив, в котором каждый элемент less[j] будет содержать сумму от 0 до j 22 | """ 23 | less = [0] * m 24 | for j in range(1, m): 25 | less[j] = less[j-1] + equal[j-1] 26 | return less 27 | 28 | 29 | def rearrange(a, less, n): 30 | """ 31 | :param a: Массив а 32 | :param less: Массив полученный функцией count_less_keys 33 | :param n: Количество элементов в массиве 34 | :return: Отсортированный массив 35 | """ 36 | b = [0] * n 37 | 38 | # Идем по массиву a и при встрече элемента смотрим на массив less. По ключу хранится позиция 39 | # на которую нужно поставить элемент в массиве B. 40 | for i in range(n): 41 | key = a[i] 42 | index = less[key] 43 | b[index] = a[i] 44 | less[key] += 1 45 | return b 46 | 47 | 48 | def count_sort(a): 49 | equal = count_keys_equal(a, len(a), max(a)+1) 50 | less = count_less_keys(equal, max(a)+1) 51 | b = rearrange(a, less, len(a)) 52 | return b 53 | 54 | 55 | if __name__ == "__main__": 56 | from test import tester 57 | 58 | tester(count_sort) -------------------------------------------------------------------------------- /Сортировки/Сортировка слиянием.py: -------------------------------------------------------------------------------- 1 | def merge_sort(a, p=None, r=None): 2 | """ 3 | :param a: Сортируемый участок 4 | :param p: Левая граница участка 5 | :param r: Правая граница участка 6 | :return: Отсортированный массив 7 | """ 8 | if p is None or r is None: 9 | p, r = 0, len(a)-1 # Начальные значения, по умолчанию r-везде включительно 10 | if p >= r: 11 | return # Если левая граница >= правой, тогда массив уже отсортирован, тк его длина <= 1 12 | q = (p + r) // 2 # Середина массива 13 | merge_sort(a, p, q) # Сортируем левую половину 14 | merge_sort(a, q+1, r) # Сортируем правую половину 15 | merge(a, p, q, r) # Склеиваем половины 16 | return a 17 | 18 | 19 | def merge(a, p, q, r): 20 | # Используем доп память для переупорядочивания 21 | b = a[p:q+1] + [float('inf')] # левая половина 22 | c = a[q+1:r+1] + [float('inf')] # правая половина 23 | 24 | i, j = 0, 0 25 | 26 | for k in range(p, r+1): 27 | if b[i] <= c[j]: 28 | a[k] = b[i] 29 | i += 1 30 | else: 31 | a[k] = c[j] 32 | j += 1 33 | 34 | 35 | if __name__ == "__main__": 36 | from test import tester 37 | 38 | tester(merge_sort) 39 | -------------------------------------------------------------------------------- /Структуры данных/MaxHeap.py: -------------------------------------------------------------------------------- 1 | class MaxHeap: 2 | 3 | def __init__(self, *args): 4 | self.heap = [] 5 | self.heap_size = 0 6 | for i in args: 7 | self.heap.append(i) 8 | self.heap_size += 1 9 | self.sift_up(self.heap_size - 1) 10 | 11 | def sift_down(self, i): 12 | while 2 * i + 1 < self.heap_size: 13 | left = 2 * i + 1 14 | right = 2 * i + 2 15 | j = left 16 | if right < self.heap_size and \ 17 | self.heap[right] > self.heap[left]: 18 | j = right 19 | if self.heap[i] >= self.heap[j]: 20 | break; 21 | 22 | self.heap[i], self.heap[j] = self.heap[j], self.heap[i] 23 | i = j 24 | 25 | def sift_up(self, i): 26 | while self.heap[i] > self.heap[(i - 1) // 2] and i > 0: 27 | self.heap[i], self.heap[(i - 1) // 2] = \ 28 | self.heap[(i - 1) // 2], self.heap[i] 29 | i = (i - 1) // 2 30 | 31 | def extract_max(self): 32 | max_elem = self.heap[0] 33 | self.heap[0] = self.heap[self.heap_size - 1] 34 | self.heap.pop() 35 | self.heap_size -= 1 36 | self.sift_down(0) 37 | return max_elem 38 | 39 | def insert(self, key): 40 | self.heap_size += 1 41 | self.heap.append(key) 42 | self.sift_up(self.heap_size - 1) 43 | 44 | def show(self): 45 | return self.heap 46 | 47 | 48 | def main(): 49 | n = int(input()) 50 | heap = MaxHeap() 51 | for i in range(n): 52 | s = input() 53 | if s[0] == "I": 54 | heap.insert(int(s.split()[1])) 55 | else: 56 | print(heap.extract_max()) 57 | 58 | 59 | if __name__ == '__main__': 60 | main() 61 | -------------------------------------------------------------------------------- /Структуры данных/Односвязный список.py: -------------------------------------------------------------------------------- 1 | class Node: 2 | 3 | def __init__(self, value, link=None): 4 | self.value = value 5 | self.link = link 6 | 7 | 8 | class OneLinkedList: 9 | 10 | def __init__(self): 11 | self.last = None 12 | 13 | def add(self, value): 14 | 15 | node = Node(value, self.last) 16 | self.last = node 17 | 18 | def get_items(self): 19 | last = self.last 20 | while last is not None: 21 | yield last.value 22 | last = last.link 23 | 24 | def pop(self): 25 | self.last = self.last.link 26 | 27 | l = OneLinkedList() 28 | l.add(1) 29 | l.add(2) 30 | l.add(5) 31 | l.add(3) 32 | l.add(4) 33 | 34 | 35 | #l.pop() 36 | 37 | print(*l.get_items()) -------------------------------------------------------------------------------- /Структуры данных/Стек.py: -------------------------------------------------------------------------------- 1 | class Node: 2 | 3 | def __init__(self, value, link=None): 4 | self.value = value 5 | self.link = link 6 | 7 | 8 | class OneLinkedList: 9 | 10 | def __init__(self): 11 | self.last = None 12 | 13 | def add(self, value): 14 | 15 | node = Node(value, self.last) 16 | self.last = node 17 | 18 | def get_items(self): 19 | last = self.last 20 | while last is not None: 21 | yield last.value 22 | last = last.link 23 | 24 | def pop(self): 25 | last = self.last 26 | self.last = self.last.link 27 | return last 28 | 29 | 30 | class Stack: 31 | 32 | def __init__(self): 33 | self.data = OneLinkedList() 34 | 35 | def append(self, value): 36 | self.data.add(value) 37 | 38 | def pop(self): 39 | return self.data.pop().value 40 | 41 | def peek(self): 42 | return self.data.last.value 43 | 44 | from collections import deque 45 | 46 | class NormalStack: 47 | 48 | def __init__(self): 49 | self.data = deque() 50 | 51 | def append(self, value): 52 | self.data.append(value) 53 | 54 | def pop(self): 55 | return self.data.pop() 56 | 57 | 58 | def peek(self): 59 | elem = self.data.pop() 60 | self.data.append(elem) 61 | return elem 62 | -------------------------------------------------------------------------------- /Суффиксы, префиксы и подстроки/Z-function.py: -------------------------------------------------------------------------------- 1 | def get_z(s): 2 | n = len(s) 3 | z = [0] * n 4 | l = r = 0 5 | 6 | for i in range(1, n): 7 | if r >= i: 8 | z[i] = min(z[i - l], r - i + 1) 9 | 10 | while z[i] + i < n and s[z[i]] == s[z[i] + i]: 11 | z[i] += 1 12 | 13 | if z[i] + i - 1 > r: 14 | l = i 15 | r = z[i] + i - 1 16 | return z 17 | 18 | if __name__ == "__main__": 19 | s = input() 20 | print(len(s),*get_z(s)[1:]) 21 | -------------------------------------------------------------------------------- /Суффиксы, префиксы и подстроки/hashing.py: -------------------------------------------------------------------------------- 1 | def hash_s(s): 2 | k = 31 3 | mod = 10 ** 9 + 7 4 | h = 0 5 | m = 1 6 | 7 | for letter in s: 8 | x = ord(letter) - ord("a") + 1 9 | h = (h + m * x) % mod 10 | m = (m * k) % mod 11 | 12 | return h 13 | -------------------------------------------------------------------------------- /Суффиксы, префиксы и подстроки/manaker.py: -------------------------------------------------------------------------------- 1 | def hash_s(s): 2 | k = 31 3 | mod = 10 ** 9 + 7 4 | h = 0 5 | m = 1 6 | 7 | for letter in s: 8 | x = ord(letter) - ord("a") + 1 9 | h = (h + m * x) % mod 10 | m = (m * k) % mod 11 | 12 | return h 13 | 14 | def binary_search(s:str,center:int,shift:int): 15 | # shift = 0 при поиске палиндрома нечетной длины, иначе shift = 1 16 | l = -1 17 | r = min(center, len(s)-center+shift) 18 | m = 0 19 | while r-l != 1: 20 | m = l + (r-l) // 2 21 | 22 | # reversed_hash возвращает хэш развернутой строки s 23 | if hash_s(s[center-m:center]) == hash_s(reversed(s[center+shift:center+shift+m])): 24 | l = m 25 | else: 26 | r = m 27 | return r 28 | 29 | def palindromes_count(s:str): 30 | ans = 0 31 | for i in range(len(s)): 32 | ans += binary_search(s,i,0) + binary_search(s,i,1) 33 | return ans 34 | 35 | -------------------------------------------------------------------------------- /Суффиксы, префиксы и подстроки/prefix-function.py: -------------------------------------------------------------------------------- 1 | def prefix_function(s): 2 | n = len(s) 3 | p = [0] * n 4 | for i in range(1, n): 5 | k = p[i-1] 6 | while (k > 0 and s[k] != s[i]): 7 | k = p[k - 1] 8 | if s[i] == s[k]: 9 | k += 1 10 | p[i] = k 11 | return p 12 | 13 | print(*prefix_function(input())) 14 | -------------------------------------------------------------------------------- /Суффиксы, префиксы и подстроки/readme.md: -------------------------------------------------------------------------------- 1 | # Суффиксы, префиксы и подстроки 2 | 3 | ## Оглавление 4 | 5 | + [Z-функция *(Z-function)*](#z-function) 6 | + [Нахождение самой длинной грани в строке] 7 | + [Нахождение периода строки] 8 | + [Задача о неточном поиске] 9 | + [Число различных подстрок в строке] 10 | + [Сжатие строки] 11 | + [Палиндромные префиксы] 12 | + [Префикс-функция](#prefix-function) 13 | + [Подсчет числа вхождений каждого префикса] 14 | + [Количество различных подстрок в строке] 15 | + [Сжатие строки] 16 | + [Построение автомата по префикс-функции] 17 | + [Алгоритм Кнута-Морриса-Пратта](#knut-morris-pratt) 18 | + [Переход от Z-функции к префикс-функции и обратно] 19 | + [Суффиксный массив *(Suffix-array)*](#suffix-array) 20 | + [Число различных подстрок] 21 | + [Наибольшая общая подстрока] 22 | + [Алгоритм Манакера](#manaker) 23 | + [Наивный алгоритм](#manaker-slow) 24 | + [Хэш-функция](#hash-function) 25 | + [Количество различных подстрок](#hash-substrings) 26 | + [Поиск подстроки в строке](#hash-search) 27 | + [Сравнение строк](#hash-string-compare) 28 | + [Палиндромность подстроки](#hash-palidrom) 29 | + [Количество палиндромов](#hash-palindroms-count) 30 | 31 | 32 | ## Теория 33 | ## 2 Суфффиксы, префиксы и подстроки 34 | 35 | ### Z - функция 36 | 37 | Z-функция от строки s определяется как массив z, такой, что zi 38 | равно длине максимальной подстроки, начинающейся с i-й позиции, которая равна 39 | префиксу s. 40 | 41 | Пример: 42 | ``` 43 | abacabadava 44 | 00103010101 45 | a = 0 (первый элемент всегда равен 0 или длине строки) 46 | ab = 0 (так как b не совпадает с префиксом) 47 | aba = 1 (так как a совпадает с префиксом) 48 | abac = 0 49 | abaca = 3 (так как aba=aba) 50 | и т.д. 51 | ``` 52 | #### Реализация за O(n) 53 | 54 | ```python 55 | def get_z(s): 56 | n = len(s) 57 | z = [0] * n 58 | l = r = 0 59 | 60 | for i in range(1, n): 61 | if r >= i: 62 | z[i] = min(z[i - l], r - i + 1) 63 | 64 | while z[i] + i < n and s[z[i]] == s[z[i] + i]: 65 | z[i] += 1 66 | 67 | if z[i] + i - 1 > r: 68 | l = i 69 | r = z[i] + i - 1 70 | return z 71 | ``` 72 | 73 | ### Префикс - функция 74 | 75 | Префикс-функцией от строки *s* называется массив *p* 76 | где *pi* равно длине самого большого префикса 77 | строки s0s1s2...sn. 78 | 79 | **Пример:** 80 | ``` 81 | abacabadava 82 | 00101230101 83 | 84 | a = 0 85 | ab = 0 86 | aba = 1, так как префикс и суффикс совпадают a=a 87 | abac = 0 88 | abaca = 1 89 | abacab = 2, ab=ab 90 | abacaba = 3, aba=aba 91 | abacabad = 0 92 | abacabada = 1 93 | abacabadav = 0 94 | abacabadava = 1 95 | ``` 96 | 97 | #### Реализация за O(n) 98 | ```python 99 | def prefix_function(s): 100 | n = len(s) 101 | p = [0] * n 102 | for i in range(1, n): 103 | k = p[i-1] 104 | while (k > 0 and s[k] != s[i]): 105 | k = p[k - 1] 106 | if s[i] == s[k]: 107 | k += 1 108 | p[i] = k 109 | return p 110 | 111 | print(*prefix_function(input())) 112 | ``` 113 | ### Алгоритм Кнута-Морриса-Пратта 114 | 115 | Дан текст *t* и строка *s*. В нем требуется найти и вывести позиции всех вхождений 116 | строки *s* в текст *t*. 117 | 118 | >n - длина s; m - длина t. 119 | 120 | **Алгоритм:** 121 | 122 | 1) Образуем строку *s + $ + t*; 123 | 2) Посчитаем для этой строки префикс-функцию или Z-функцию; 124 | 3) Рассмотрим её значения для всех позиций кроме n+1; 125 | 4) В тех местах где значения массива равны n - встретилось вхождение строки s. 126 | 127 | Алгоритм требует O(n) памяти и O(n+m) времени. 128 | 129 | #### Реализация 130 | ```python 131 | t = input() 132 | s = input() 133 | 134 | n = len(s) 135 | m = len(t) 136 | 137 | z = get_z(s+"$"+t) 138 | 139 | res = [] 140 | for i in range(n+1,n+m+1): 141 | if z[i] == n: 142 | res.append(i-n) 143 | print(len(res)) 144 | print(*res) 145 | ``` 146 | 147 | ### Суффиксный массив 148 | 149 | ### Алгоритм Манакера 150 | 151 | **Задача:** 152 | 153 | ``` 154 | Пусть дана строка s. 155 | Требуется найти количество подстрок s, являющихся палиндромами. 156 | Более формально: все такие пары (i,j), что s[i:j] - палиндром. 157 | ``` 158 | Легко увидеть, что в худшем случае таких подстрок будет n2. 159 | Значит пусть d1\[i\] - количество палиндромов нечетной длины 160 | с центром в позиции i, а d2\[i\] - аналогичная величина для 161 | палиндромов чётной длины. Вычислим значения. 162 | 163 | #### Наивный алгоритм 164 | Рассмотрим сначала задачу поиска палиндромов нечётной длины. 165 | Центром строки нечётной длины назовём символ под индексом: 166 | ```python 167 | center = len(t) // 2 168 | ``` 169 | Для каждой позиции в строке s найдем длину наибольшего 170 | палиндрома с центром в этой позиции. Очевидно, 171 | что если строка t является палиндромом, то строка 172 | полученная вычеркиванием первого и последнего символа 173 | из t также является палиндромом, поэтому длину палиндрома 174 | можно искать бинарным поиском. Проверить совпадение левой 175 | и правой половины можно выполнить за O(1), используя метод 176 | хеширования. 177 | 178 | Для палиндромов чётной длины алгоритм такой же. 179 | Центр строки чётной длины — некий мнимый элемент между 180 | center и center+1. 181 | Только требуется проверять вторую строку 182 | со сдвигом на единицу. Следует заметить, 183 | что мы не посчитаем никакой палиндром дважды 184 | из-за четности-нечетности длин палиндромов. 185 | 186 | #### Реализация алгоритма Манакера 187 | 188 | Будем поддерживать границы самого правого из найденных 189 | палиндромов - \[l,r\]. Итак, пусть мы хотим вычислить d1\[i\] - 190 | т.е. длину наибольшего палиндрома с центром в позиции i. 191 | При этом все возможные значения в массиве d уже посчитаны. 192 | 193 | Всего возможно 2 случая: 194 | 195 | 1) *i > r*, т.е. текущая позиция не совпадает в 196 | границы самого правого из найденных палиндромов. Тогда 197 | просто запустим наивный алгоритм для позиции *i*. 198 | 2) *i ≤ r*. Тогда попробуем воспользоваться значениями, посчитанными 199 | ранее. Отразим нашу текущую позицию внутри палиндрома 200 | \[l;r\] : j =(r-i) + l. Поскольку i и j симметричные позиции, то если d1\[j]=k, 201 | мы можем утверждать, что и d1\[i] = k. Это объясняется тем, что 202 | палиндром симметричен относительно своей центральной позиции - то есть имеем 203 | некоторый палиндром длины *k* с центром в позиции *l ≤ i ≤ r*, то в позиции 204 | j, симметричной i относительно отрезка \[l;r\] тоже может находиться 205 | палиндром длины k. 206 | 207 | ![Алгоритм Манакера](../img/manaker.png) 208 | 209 | Фигурными скобками обозначены равные подстроки. Однако стоит не 210 | забыть про один граничный случай: Что будет, если 211 | *i + d1\[j\] - 1* выходит за границы самого правого палиндрома? 212 | Так как информации о том, что происходит за границами этого 213 | палиндрома у нас нет (а значит мы не можем утверждать, что симметрия 214 | сохраняется), то необходимо ограничить значение d1\[i\] следующим образом: 215 | *d1\[i\] = min(r-i, d1\[j\])*. После этого запустим наивный алгоритм, 216 | который будет увеличивать значение d1\[i\], пока это возможно. 217 | 218 | После каждого шага важно не забывать обновлять значения \[l,r\]. 219 | 220 | Заметим, что массив *d2* 221 | 222 | ### Хэш-функция 223 | 224 | **Хэш** — это какая-то функция, сопоставляющая объектам какого-то 225 | множества числовые значения из ограниченного промежутка. 226 | 227 | **Свойства хорошей хэш-функции:** 228 | 1. Быстро считается за линейное время, зависящее от размера объекта; 229 | 2. Имеет не очень большиее значения, помещающиеся в 26 бит; 230 | 3. Детерменированно-случайная - если хэш может принимать *n* 231 | различных значений, то вероятность того, что хэши совпадут - *1/n*. 232 | 233 | *Сюрьективные хэши* - обычно хэш функция не является взаимно однозначной: одному хэшу могут 234 | соответствовать много объектов. 235 | 236 | Для некоторых задач удобнее работать с хэшами, чем с самими объектами. 237 | Пусть даны *n* строк длины *m*, и нас просят *q* раз проверить произвольные 238 | две на равенство. Вместо взаимной проверки *O(n\*m\*q)* мы можем посчитать 239 | хэши всех строк, сохранить, и во время ответа на запрос сравнивать два числа. 240 | 241 | ![Пример хэша](../img/hash-example.png) 242 | 243 | #### Применения хэш-функции: 244 | 245 | + *Чек-суммы* 246 | + *Хэш-таблица* 247 | + *Мемоизация* 248 | + *Проверка на изоморфизм* 249 | + *Криптография* 250 | + *Поиск в многомерных пространствах* 251 | 252 | Хэшируемые объекты могут быть самыми разными: строки, изображения, графы, 253 | шахматные позиции, просто битовые файлы. 254 | 255 | 256 | #### Полиномиальное хэширование 257 | Будем считать, что строка - последовательность чисел от 1 до *m* (размер 258 | алфавита). В языках программирования любой символ - это число, значит: 259 | ```python 260 | x = ord(letter) - ord("a") + 1 261 | ``` 262 | **Полиномиальный хэш строки** 263 | 264 | *hf = (s0 + s1k + s2k2 + ... + snkn) mod p* 265 | 266 | Здесь *k* - произвольное число больше размера алфавита, а *p* достаточно большой 267 | модуль, вообще говоря, не обязательно простой. 268 | 269 | Его можно посчитать за линейное время поддерживая переменную, равную нужной 270 | в данный момент степени *k*: 271 | ```python 272 | k = 31 273 | mod =10**9 + 7 274 | s = "abacabadaba" 275 | h = 0 276 | m = 1 277 | 278 | for letter in s: 279 | x = ord(letter) - ord("a") + 1 280 | h = (h + m * x) % mod 281 | m = (m * k) % mod 282 | 283 | print(h) 284 | ``` 285 | 286 | **Обратный полиномиальный хэш** 287 | 288 | *hb = (s0kn + s1kn-1 + s2kn-2 + ... + sn) mod p* 289 | ```python 290 | for letter in s: 291 | x = ord(letter) - ord("a") + 1 292 | h = (h * k + x) % mod 293 | print(h) 294 | 295 | ``` 296 | #### Применение хэширования 297 | 298 | Используя тот факт, что хэш это значение многочлена, можно быстро пересчитывать 299 | хэш от результата выполнения многих строковых операций. 300 | 301 | Например, если нужно посчитать **хэш от конкатенации строк** *a* и *b* (s = a+b), то 302 | можно просто хэш *b* домножить на k|a| и сложить с хэшом *a*: 303 | 304 | *h(ab) = h(a) + k|a| * h(b)* 305 | ```python 306 | a = "abacaba" 307 | b = "daba" 308 | h_ab = hash(a) + k**len(a) * hash(b) 309 | 310 | ``` 311 | **Удаление префикса строки:** 312 | 313 | *h(b) = (h(ab)-h(a))/k|a|* 314 | 315 | ```python 316 | h_b = (hash(ab)-hash(a))/(k**len(a)) 317 | ``` 318 | 319 | **Удаление суффикса:** 320 | 321 | *h(a) = h(ab)-k|a|\*h(b)* 322 | 323 | ```python 324 | h_a = hash(ab) - k**len(a) * hash(b) 325 | ``` 326 | 327 | В задачах часто бывает потребность в домножении на *k* в какой-то степени, 328 | поэтому можно их просчитать заранее: 329 | ```python 330 | max_n = 10**5+5 331 | 332 | p = [0]*max_n 333 | p[0] = 1 334 | 335 | for i in range(1,n): 336 | p[i] = (p[i-1]*k) % mod 337 | ``` 338 | Пусть нам надо отвечать на запросы проверки на равенство 339 | произвольных подстрок одной большой строки. Подсчитаем 340 | значение хэш-функции для каждого префикса: 341 | 342 | ```python 343 | h = [0] * max_n 344 | h[0] = 0 # h[k] - хэш префикса длины k 345 | # s - последовательность int-ов 346 | for i in range(n): 347 | h[i+1] = (h[i]+p[i]*s[i]) % mod 348 | ``` 349 | 350 | **Функция считающая хэш на произвольном подотрезке:** 351 | 352 | *h(s\[l:r\]) = (hr - hl) / kl* 353 | ```python 354 | def hash_substring1(l,r): 355 | return (hash(r) - hash(l))/(k**l) 356 | 357 | ``` 358 | Вместо приведения к нулевой степени приведём многочлен к 359 | какой-нибудь достаточно большой — например, к 360 | n-ной. Так проще — нужно будет домножать, а не делить. 361 | ```python 362 | def hash_substring2(l,r): 363 | return (h[r+1] - h[l]) * p[n-1] % mod 364 | ``` 365 | 366 | Теперь мы можем просто вызывать эту функцию от двух отрезков 367 | и сравнивать числовое значение, отвечая на запрос за O(n). 368 | 369 | #### Выбор констант 370 | 371 | Практическое правило: если вам нужно хранить 372 | n различных хэшей, то безопасный модуль — это число порядка 10 * n2. 373 | 374 | Не всегда такой можно выбрать один — если он будет слишком большой, 375 | будут происходить переполнения. Вместо этого можно брать два или даже 376 | три модуля и считать много хэшей параллельно. 377 | 378 | Так же можно брать модуль 264. У него есть несколько преимуществ: 379 | + Он большой - второй модуль точно не понадобится 380 | + Хэширование происходит быстрее 381 | 382 | Однако **есть тест** против такого модуля. Нужно использовать аккуратно. 383 | 384 | При выборе *k* ограничения не такие серьезные: 385 | + Она должна быть чуть больше словаря - иначе можно изменить две соседние 386 | буквы и получить коллизию. 387 | + Она должна быть взаимно проста с модулем - иначе в какой-то момент 388 | может все занулиться. 389 | 390 | #### Количество различных подстрок 391 | 392 | Подсчитаем хэши всех подстрок за *O(n2)* и добавим их в *set*. 393 | Ответ: len(set) 394 | 395 | #### Поиск подстроки в строке 396 | Можно посчитать хэши от шаблона (строки, которую ищем) и 397 | пройтись «окном» размера шаблона по тексту, поддерживая 398 | хэш текущей подстроки. Если хэш какой-то из этих подстрок 399 | совпал с хэшом шаблона, то мы нашли нужную подстроку. Это 400 | называется алгоритмом Рабина-Карпа. 401 | 402 | #### Сравнение строк 403 | У любых двух строк есть какой-то общий префикс (возможно, пустой). 404 | Сделаем бинпоиск по его длине, а дальше сравним два символа, 405 | идущие за ним. 406 | 407 | #### Палиндромность строки 408 | Можно посчитать два массива — обратные хэши и прямые. 409 | Проверка на палиндром будет заключаться в сравнении 410 | значений *hash_substring()* на первом массиве и на втором. 411 | 412 | #### Количество палиндромов 413 | Можно перебрать центр палиндрома, а для каждого центра — бинпоиском 414 | его размер. Проверять подстроку на палиндромность мы уже умеем. 415 | Как и всегда в задачах на палиндромы, случаи четных и нечетных 416 | палиндромов нужно обрабатывать отдельно. 417 | -------------------------------------------------------------------------------- /Суффиксы, префиксы и подстроки/suffix-array.py: -------------------------------------------------------------------------------- 1 | def count_sort(p:list, c:list): 2 | n = len(p) 3 | 4 | cnt = [0] * n 5 | for i in c: 6 | cnt[i] += 1 7 | 8 | pos = [0] * n 9 | p_new = [0] * n 10 | 11 | for i in range(1,n): 12 | pos[i] = pos[i-1] + cnt[i-1] 13 | 14 | for x in p: 15 | i = c[x] 16 | p_new[pos[i]] = x 17 | pos[i] += 1 18 | 19 | p = p_new 20 | 21 | return p, c 22 | 23 | 24 | def get_suffix_array(s:str): 25 | s += "$" 26 | n = len(s) 27 | c = [0]*n 28 | a = sorted([(s[i],i) for i in range(n)]) 29 | 30 | p = [a[i][1] for i in range(n)] 31 | 32 | for i in range(1,n): 33 | if a[i][0] == a[i-1][0]: 34 | c[p[i]] = c[p[i-1]] 35 | else: 36 | c[p[i]] = c[p[i-1]] + 1 37 | 38 | k = 1 39 | while k < n: 40 | a = [] 41 | # for i in range(n): 42 | # a.append(((c[i], c[(i + k) % n]),i)) 43 | for i in range(n): 44 | p[i] = (p[i] - k + n) % n 45 | p, c = count_sort(p, c) 46 | c_new = [0]*n 47 | 48 | for i in range(1, n): 49 | pre = (c[p[i - 1]], c[(p[i - 1] + k) % n]) 50 | now = (c[p[i]], c[(p[i] + k) % n]) 51 | if pre == now: 52 | c_new[p[i]] = c_new[p[i - 1]] 53 | else: 54 | c_new[p[i]] = c_new[p[i - 1]] + 1 55 | 56 | c = c_new 57 | k *= 2 58 | 59 | # for i in p: 60 | # print(s[i:]) 61 | 62 | return p 63 | 64 | 65 | if __name__ == "__main__": 66 | print(*get_suffix_array(input())) --------------------------------------------------------------------------------