├── .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 | 
18 |
19 | Линейное упорядочивание будет для этого графа вот таким:
20 |
21 | 
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 | 
160 |
161 | Пример:
162 |
163 | Здесь шаг по i символизирует удаление (D)
164 | из первой строки, по j — вставку (I)
165 | в первую строку, а шаг по обоим индексам
166 | символизирует замену символа (R) или отсутствие
167 | изменений (M).
168 |
169 | 
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 | 
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 | 
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 | 
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()))
--------------------------------------------------------------------------------