#algo
Деревья в алгоритмах 1/2.
В алгоритмах часто используются различные деревья (как графовый термин) в виде различных структур данных. Стандартный пример это деревья поиска или кучи. Однако разновидностей структур данных, которые решают те или иные задачи, и в качестве базы (или хотя бы в названии) содержат в себе дерево очень много. Давайте на них и посмотрим.
Опустим подробные описания чего-то дефолтного вроде avl-деревьев, красно-чёрных, или бора (trie). Может только увидим их какие-то прокачаные версии. И не будем особо рассматривать персистентные сд. Там конечно всё очень круто, но для обзорного поста по многообразию ту мач.
Мне идейно очень нравится декартово дерево (пусть оно и довольно известно). Тут и все свойства обычного bst, и это в некотором роде вероятностная структура данных. А если писать неявное, то появляется возможность делать много разных операций как у дерева отрезков, но с большей вариативностью: перемещать части массива данных, менять местами чёт/нечёт и при желании даже копировать отрезки (с персистентной версией).
Splay-деревья тоже bst но обладают замечательным свойством: элементы, которые ищут чаще, со временем оказываются выше, что позволяет тратить меньше времени на их поиск в будущем. Хз, как часто они используются, но в вакууме свойство очень приятное.
Scapegoat tree в отличие от других bst старается не хранить в нодах ничего кроме key-value и указателей на детей и вместо небольшой перебалансировки на каждую операцию делает большие перебалансировки поддеревьев, но редко.
Рядом с обычными bst есть различные версии B-деревьев, которые активно используются в базах данных и файловых системах. Стандартное B-дерево это просто обобщение bst, где разрешается иметь более двух детей/элементов в ноде, что позволяет лучше работать с кешами + ускорить операции работы с большими блоками данных. Следующая версия B-дерева это B+-дерево, которое имеет немного другую структуру и быстрее работает при последовательном обходе. Тут же можно найти B*-tree (которое пытается плотнее упаковывать данные) и B*+-tree (которое, неожиданно, комбинирует фичи двух прошлых). Известными частными случаями B+-tree являются 2-3 и 2-3-4 деревья. Есть ещё какие-то чуть-чуть апдейченные версии вроде counted b-tree.
Далее идут различные боры (или префиксные деревья). Из интересного тут есть цифровые боры, которые позволяют работать с множеством чисел асимптотически эффективнее, чем со стандартными bst (в основном используя факт о максимально значении в множестве). Например это Van Emde Boas tree, X-fast trie и Y-fast trie (эти три умеют делать стандартные операции bst с лучшей асимптотикой, хотя может с памятью похуже), Fusion tree (а это в несколько новых операций). А есть вообще упоротые штуки вроде Hash Array Mapped Trie. Видел ещё ternary search tree, которое бор с не более чем тремя детьми. Это достигается введением нод разного типа и особых правил с ними. При использовании вы иногда просто юзаете ноды как ребро, опуская символ из неё. Ещё есть сжатые боры вроде Radix trie/Patricia tree.
Ещё есть всякие сд, позволяющие эффективно работать со строками Например стандартное суффиксное дерево. Идея у него очень простая: выписываем в бор все суффиксы строк вашего множества, после чего можно очень быстро искать различные подстроки. Даже можно искать строки с опечатками, правда с некоторыми ограничениями (если опечатка в начале, всё ломается). Потому есть специализированные структуры данных вроде BK-tree. Ещё довольно частым кейсом является код Хаффмана.
Деревья в алгоритмах 1/2.
В алгоритмах часто используются различные деревья (как графовый термин) в виде различных структур данных. Стандартный пример это деревья поиска или кучи. Однако разновидностей структур данных, которые решают те или иные задачи, и в качестве базы (или хотя бы в названии) содержат в себе дерево очень много. Давайте на них и посмотрим.
Опустим подробные описания чего-то дефолтного вроде avl-деревьев, красно-чёрных, или бора (trie). Может только увидим их какие-то прокачаные версии. И не будем особо рассматривать персистентные сд. Там конечно всё очень круто, но для обзорного поста по многообразию ту мач.
Мне идейно очень нравится декартово дерево (пусть оно и довольно известно). Тут и все свойства обычного bst, и это в некотором роде вероятностная структура данных. А если писать неявное, то появляется возможность делать много разных операций как у дерева отрезков, но с большей вариативностью: перемещать части массива данных, менять местами чёт/нечёт и при желании даже копировать отрезки (с персистентной версией).
Splay-деревья тоже bst но обладают замечательным свойством: элементы, которые ищут чаще, со временем оказываются выше, что позволяет тратить меньше времени на их поиск в будущем. Хз, как часто они используются, но в вакууме свойство очень приятное.
Scapegoat tree в отличие от других bst старается не хранить в нодах ничего кроме key-value и указателей на детей и вместо небольшой перебалансировки на каждую операцию делает большие перебалансировки поддеревьев, но редко.
Рядом с обычными bst есть различные версии B-деревьев, которые активно используются в базах данных и файловых системах. Стандартное B-дерево это просто обобщение bst, где разрешается иметь более двух детей/элементов в ноде, что позволяет лучше работать с кешами + ускорить операции работы с большими блоками данных. Следующая версия B-дерева это B+-дерево, которое имеет немного другую структуру и быстрее работает при последовательном обходе. Тут же можно найти B*-tree (которое пытается плотнее упаковывать данные) и B*+-tree (которое, неожиданно, комбинирует фичи двух прошлых). Известными частными случаями B+-tree являются 2-3 и 2-3-4 деревья. Есть ещё какие-то чуть-чуть апдейченные версии вроде counted b-tree.
Далее идут различные боры (или префиксные деревья). Из интересного тут есть цифровые боры, которые позволяют работать с множеством чисел асимптотически эффективнее, чем со стандартными bst (в основном используя факт о максимально значении в множестве). Например это Van Emde Boas tree, X-fast trie и Y-fast trie (эти три умеют делать стандартные операции bst с лучшей асимптотикой, хотя может с памятью похуже), Fusion tree (а это в несколько новых операций). А есть вообще упоротые штуки вроде Hash Array Mapped Trie. Видел ещё ternary search tree, которое бор с не более чем тремя детьми. Это достигается введением нод разного типа и особых правил с ними. При использовании вы иногда просто юзаете ноды как ребро, опуская символ из неё. Ещё есть сжатые боры вроде Radix trie/Patricia tree.
Ещё есть всякие сд, позволяющие эффективно работать со строками Например стандартное суффиксное дерево. Идея у него очень простая: выписываем в бор все суффиксы строк вашего множества, после чего можно очень быстро искать различные подстроки. Даже можно искать строки с опечатками, правда с некоторыми ограничениями (если опечатка в начале, всё ломается). Потому есть специализированные структуры данных вроде BK-tree. Ещё довольно частым кейсом является код Хаффмана.
👍3🤯2 1
#algo
Деревья в алгоритмах 2/2.
Очень часто деревья используются во всяких геометрических штучках. Самым простым примером кмк является binary space partitioning: вся плоскость это корень; по мере разделения областей к вершине, представляющей какую-то часть плоскости, добавляется два ребёнка (т.е. дерево всегда полное). Примерно таким же, но в терминах множеств объектов в мультиразмерном пространстве, занимаются ball trees.
Ещё сд:
- Rope -- это сд, позволяющая эффективно (ну +-) работать со строками; хотя олимпиадники любят использовать не по назначению, когда надо уметь обращаться по индексам во множестве;
- Zipper -- идиома из функционального программирования. Тут рядом есть Finger tree;
- дерево интервалов (не путать с деревом отрезков);
- или совсем молодое дерево палиндромов.
Пачка фактов:
1. Дерево отрезков можно писать за 2n памяти просто перенумеровав вершины.
2. Есть упрощённая версия реализации красно-чёрных деревьев — left-leaning reb-black trees (левое или может левацкое кчд). Аналогично есть левацкая куча. Ещё как вариация кчд есть AA-tree.
3. Слышал, что в STL любят кчд, потому что у тебя не более двух поворотов на каждую операцию, в то время как условное авл-дерево может сильно измениться.
4. Большинство индексов в базах данных построены на той или иной структуре данных. Тут можно посмотреть на небольшой лес.
5. По факту кучи тоже деревья (в графовой терминологии), но писать ещё миллион букав не хотелось, потому можете сами глянуть тут.
Если вы знаете ещё что-нибудь из этой оперы, смело накидывайте в комментарии.
Деревья в алгоритмах 2/2.
Очень часто деревья используются во всяких геометрических штучках. Самым простым примером кмк является binary space partitioning: вся плоскость это корень; по мере разделения областей к вершине, представляющей какую-то часть плоскости, добавляется два ребёнка (т.е. дерево всегда полное). Примерно таким же, но в терминах множеств объектов в мультиразмерном пространстве, занимаются ball trees.
Ещё сд:
- Rope -- это сд, позволяющая эффективно (ну +-) работать со строками; хотя олимпиадники любят использовать не по назначению, когда надо уметь обращаться по индексам во множестве;
- Zipper -- идиома из функционального программирования. Тут рядом есть Finger tree;
- дерево интервалов (не путать с деревом отрезков);
- или совсем молодое дерево палиндромов.
Пачка фактов:
1. Дерево отрезков можно писать за 2n памяти просто перенумеровав вершины.
2. Есть упрощённая версия реализации красно-чёрных деревьев — left-leaning reb-black trees (левое или может левацкое кчд). Аналогично есть левацкая куча. Ещё как вариация кчд есть AA-tree.
3. Слышал, что в STL любят кчд, потому что у тебя не более двух поворотов на каждую операцию, в то время как условное авл-дерево может сильно измениться.
4. Большинство индексов в базах данных построены на той или иной структуре данных. Тут можно посмотреть на небольшой лес.
5. По факту кучи тоже деревья (в графовой терминологии), но писать ещё миллион букав не хотелось, потому можете сами глянуть тут.
Если вы знаете ещё что-нибудь из этой оперы, смело накидывайте в комментарии.
👍2😢1
#common #algo
Сегодня немножко математики.
Я думаю, что многие, кто занимался олимпиадами по информатике/спортивным программированием про это знают. Но когда-то мне очень сильно понравилась тема с ускорением подсчёта некоторых дп, если они являются линейной комбинацией прошлых вычисленных значений.
Предположим у нас есть следующее дп (не будем расписывать в общем виде):
с какой-то базой. Например
Предположим, мы хотим вычислить несколько конкретных значений (
Давайте посмотрим на это в виде матриц. Базовая матрица у нас имеет вид (это не определитель, я просто скобки не умею нормально рисовать):
где
Если мы перемножим матрицу ниже на столбец выше
мы получим вектор
в котором мы по факту получили сдвиг в нашей дп-шной последовательности на 1 вперёд (то есть вычислили
А теперь предположим, мы хотим найти
То есть по факту вы можете считать любую подобную “линейную” последовательность быстро для любого адекватного значения
Сегодня немножко математики.
Я думаю, что многие, кто занимался олимпиадами по информатике/спортивным программированием про это знают. Но когда-то мне очень сильно понравилась тема с ускорением подсчёта некоторых дп, если они являются линейной комбинацией прошлых вычисленных значений.
Предположим у нас есть следующее дп (не будем расписывать в общем виде):
dp[i] = 2dp[i-1] + 3dp[i-2] - dp[i-3]с какой-то базой. Например
dp[0, 1, 2] = {1, 2, 3}Предположим, мы хотим вычислить несколько конкретных значений (
dp[10^3], dp[10^15], то есть значения с очень большими номером), а не все (обычно так и бывает). Давайте посмотрим на это в виде матриц. Базовая матрица у нас имеет вид (это не определитель, я просто скобки не умею нормально рисовать):
| 1 |
a = | 2 |
| 3 |где
a_i это соответственно dp_i, i \in [0; 2]. Если мы перемножим матрицу ниже на столбец выше
| 0 1 0|
M = | 0 0 1|
|-1 3 2|мы получим вектор
| 2 |
| 3 |
| 11 |в котором мы по факту получили сдвиг в нашей дп-шной последовательности на 1 вперёд (то есть вычислили
dp[2], dp[3] и dp[4]). А теперь предположим, мы хотим найти
dp[n]. Конечно, эту последовательность умножений можно повторять раз за разом n - 3 раза, пока мы не получим нужный элемент. Но в чём тогда был смысл подобных движений? Давайте сначала нашу матрицу перехода бинарно возведём в (n-3)-ю степень и только потом умножим вектор a. Чтобы получить ответ, достаточно будет взять последний элемент получившегося столбца:ans = (bin_pow(M, n - 3) * a)[2];То есть по факту вы можете считать любую подобную “линейную” последовательность быстро для любого адекватного значения
n. Я когда-то, например, писал нахождение n-ого числа Фибоначчи на компиляции. Тут правда надо быть аккуратным с глубиной рекурсии и возможно подкрутить флаг -ftemplate-depth.👍19🤯5❤2🔥2
#algo #cpp #highload
0. Продолжаю отсматривать CppCon 2023. Пока закину один доклад, про который вы наверняка знаете от самого автора: A Long Journey of Changing std::sort Implementation at Scale - Danila Kutenin.
1. My favourite memory leak.
Короткий видос (всего 4 минуты), но какая красота..
2. Интересная статья про succinct data structures.
3. Architecture antipatterns.
4. Scrambling Eggs for Spotify with Knuth's Fibonacci Hashing.
Прикольный пост про перемешивание треков и разные подходы к этому.
0. Продолжаю отсматривать CppCon 2023. Пока закину один доклад, про который вы наверняка знаете от самого автора: A Long Journey of Changing std::sort Implementation at Scale - Danila Kutenin.
1. My favourite memory leak.
Короткий видос (всего 4 минуты), но какая красота..
2. Интересная статья про succinct data structures.
3. Architecture antipatterns.
4. Scrambling Eggs for Spotify with Knuth's Fibonacci Hashing.
Прикольный пост про перемешивание треков и разные подходы к этому.
👍3 3 2❤1 1
#algo #cpp
🥳 Недавно понял, что в статье, где упоминал sparse set, не описал процесс удаления одного элемента из него.
Предположим, у нас есть такой sparse set:
Тут два случая:
- удаляемый элемент находится в конце. Тогда просто уменьшаем размер (удаляем 4):
Мб про sparse set как-нибудь статью на хабр накидаю.
😎 Дальше с CppCon 2023:
- Exceptionally Bad: The Misuse of Exceptions in C++ & How to Do Better;
- The Au C++ Units Library: Handling Physical Units Safely, Quickly, & Broadly;
- C++ Modules: Getting Started Today;
- std::linalg: Linear Algebra coming to Standard C++.
На последнем мы немножко остановимся и глянем, что же там такое есть. cppref с описанием того, что лежит в хедере (правда там все подстраницы пустые ещё).
Хедер включается так:
Докладчик рассказывает про несколько важных кусков, о которых думают, когда речь идёт о библиотеках для линала:
- многомерные массивы и итерация по ним (доступно сейчас в C++17 и расширяется с C++23 (mdspan) и C++26 (submdspan));
- работа и базовые операции с векторами (в математическом смысле) и матрицами (как раз то, что предполагается затянуть в новой либе);
- low-level math problems, такие как решение систем линейных уравнений, поиск собственных значений и прочее (сейчас нет пропозалов по этим кускам в язык, но есть различные third-party либы);
- higher-level math problems: statistical inference и другие штуки.
Единицей работы с объектами является std::mdspan. Из базовых операцией над матрицами вы можете делать:
- scale -- умножение на скаляр;
- conjugated -- получение сопряжённой;
- transposed -- неоижиданно, транспонирование;
- conjugate_transpose.
Для векторов: scale, add, dot, vector_sum_of_squares, vector_two_norm, vector_abs_sum и другие.
Для операций матрица-вектор: matrix_vector_product и другие умножения, а так же какие-то rank-1 update, которые я сходу не осознал ввиду скудности остаточных знаний в математике.
Для матрица-матрица кроме произведения аналогично много всяких других произведений и каких-то апдейтов. Только ещё есть произведение/решение систем для треугольных матриц.
В целом большая часть доклада это рассказ про "стандарт" BLAST, эффективные вычисления и прочее около. В конце чувак показывает, как можно сделать разложение Холецкого с помощью новой либы.
🙂 Хотел недавно на clang 16.xxx + libc++ поюзать
Круто, да? А вот хер там был! Почитайте пояснение. Так сказать, СМОТРЕТЬ ДО КОНЦА🤔 ❌ ❌
🤨 А ещё на днях не обнаружил у
Предположим, у нас есть такой sparse set:
dense: 0 1 2 3 4 |
sparse: 0 1 2 3 4
| показывает текущий конец сета. Тут два случая:
- удаляемый элемент находится в конце. Тогда просто уменьшаем размер (удаляем 4):
dense: 0 1 2 3 | 4
sparse: 0 1 2 3 4
- удаляемый элемент не в конце. Тогда меняем его местами с последним и опять уменьшаем размер (удаляем 1):dense: 0 3 2 | 1 4
sparse: 0 3 2 1 4
Готово! Сделал пр в folly. Мб про sparse set как-нибудь статью на хабр накидаю.
😎 Дальше с CppCon 2023:
- Exceptionally Bad: The Misuse of Exceptions in C++ & How to Do Better;
- The Au C++ Units Library: Handling Physical Units Safely, Quickly, & Broadly;
- C++ Modules: Getting Started Today;
- std::linalg: Linear Algebra coming to Standard C++.
На последнем мы немножко остановимся и глянем, что же там такое есть. cppref с описанием того, что лежит в хедере (правда там все подстраницы пустые ещё).
Хедер включается так:
#include <linalg>Докладчик рассказывает про несколько важных кусков, о которых думают, когда речь идёт о библиотеках для линала:
- многомерные массивы и итерация по ним (доступно сейчас в C++17 и расширяется с C++23 (mdspan) и C++26 (submdspan));
- работа и базовые операции с векторами (в математическом смысле) и матрицами (как раз то, что предполагается затянуть в новой либе);
- low-level math problems, такие как решение систем линейных уравнений, поиск собственных значений и прочее (сейчас нет пропозалов по этим кускам в язык, но есть различные third-party либы);
- higher-level math problems: statistical inference и другие штуки.
Единицей работы с объектами является std::mdspan. Из базовых операцией над матрицами вы можете делать:
- scale -- умножение на скаляр;
- conjugated -- получение сопряжённой;
- transposed -- неоижиданно, транспонирование;
- conjugate_transpose.
Для векторов: scale, add, dot, vector_sum_of_squares, vector_two_norm, vector_abs_sum и другие.
Для операций матрица-вектор: matrix_vector_product и другие умножения, а так же какие-то rank-1 update, которые я сходу не осознал ввиду скудности остаточных знаний в математике.
Для матрица-матрица кроме произведения аналогично много всяких других произведений и каких-то апдейтов. Только ещё есть произведение/решение систем для треугольных матриц.
В целом большая часть доклада это рассказ про "стандарт" BLAST, эффективные вычисления и прочее около. В конце чувак показывает, как можно сделать разложение Холецкого с помощью новой либы.
constexpr std::string. Хотелка в целом понятная и простая: завести constexpr переменную в global scope:constexpr std::string kSomeVal = "string";Круто, да? А вот хер там был! Почитайте пояснение. Так сказать, СМОТРЕТЬ ДО КОНЦА
std::stack метод clear. И что это!Please open Telegram to view this post
VIEW IN TELEGRAM
🔥7 2❤1👍1
#cpp #algo #pub
Написал пост про разреженные структуры данных в дополнение к инфе про sparse set.
https://habr.com/ru/articles/790844/
Написал пост про разреженные структуры данных в дополнение к инфе про sparse set.
https://habr.com/ru/articles/790844/
Хабр
Разреженные структуры данных
Разреженное небо над Дианой и Конём Боджеком Когда-то я писал пост про различные интересные структуры данных . Среди них был т.н. sparse set. Там мы описали его в общих чертах, опустив некоторые...
#cpp #algo #pub
Когда-то (ещё в августе 2023) я хотел написать про одну из возможных реализаций связного списка, а потом как-то разошёлся и написал про все мне известные. Ну как-то вот так и получилось.
https://habr.com/ru/articles/814955/
Когда-то (ещё в августе 2023) я хотел написать про одну из возможных реализаций связного списка, а потом как-то разошёлся и написал про все мне известные. Ну как-то вот так и получилось.
https://habr.com/ru/articles/814955/
Хабр
Многообразие связных списков
Связный список — классическая структура данных, которая позволяет быстрые вставки/удаления, но при этом просаживает другие операции (случайный доступ к элементу). Мы пройдёмся от базовой...
👍24⚡4❤2✍1🔥1🫡1
[неожиданно] #math #algo
Делюсь небольшой задачей из начала универа. Сильно мне тогда понравилась.
https://telegra.ph/Zadacha-iz-molodosti-05-30
Единственное что пришлось приложить немалые усилия (и попросить помощь у друзей), чтобы восстановить картинку в голове, потому что математик из меня так себе.
Делюсь небольшой задачей из начала универа. Сильно мне тогда понравилась.
https://telegra.ph/Zadacha-iz-molodosti-05-30
Единственное что пришлось приложить немалые усилия (и попросить помощь у друзей), чтобы восстановить картинку в голове, потому что математик из меня так себе.
Telegraph
Задача из молодости
Когда-то на первом курсе (уже 5 лет назад, ого!) мне показали очередную задачу на codeforces. Я её довольно сильно запомнил, потому что она не очень сложная с точки зрения математических рассуждений и красиво сворачивается в понятный ответ (который ещё и…
👍7❤4💅4
#algo
Люблю, когда кто-то пишет посты вместо меня. Точнее не вместо меня. Просто пишут крутые статьи, а я могу просто их вам показать вместо того, чтобы самому заниматься расписыванием. Вот так правильно.
Поиск подстроки в строке.
Есть такой чувак Андрей Гейн. У него есть канал на youtube и канал в тг: @andgein_notes. В конце декабря он написал про 2 текстовые версии о поиске подстроки в строке, которые он выложил в свой блог.
В первой он рассказывает о поиске конкретного паттерна в тексте:
- наивном подходе
- префикс функции и алгоритме Knuth–Morris–Pratt
- далее про Boyer–Moore алгоритм
- и в конце про Two-Way
Сначала прочитайте это замечательное полотно, а потом давайте посмотрим, как использовать продвинутые сёрчеры строк в C++.
В
Такой сёрчер будет использовать стандартный алгоритм поиска подстроки в строке.
Не дефолтными сёрчерами могут быть:
- std::boyer_moor_searcher
- std::boyer_moore_horspool_searcher
Пользуемся ими точно так же, как и стандартным.
Если посмотреть на реализацию, например, boyer_moor_search, то можно увидеть ровно описанные Андреем вещи (например, прекомпьют разных табличек).
Из интересного, в шаблоне так же можно передать хеш функцию, которая используется для описанной bas character эвристики: в общем случае мы работаем не только с
Если приглядеться на дефолтный сёрчер, то можно понять, что там много не надо и при желании можно и свой реализовать. Можно писать и общего вида алгоритмы, которые будут пробрасывать сёрчеры в
И пока и всё. Двигаемся ко второй лекции, где освещены продвинутые способы:
- Ахо-Корасик
- суффиксный бор
- суффиксное дерево
- суффиксный автомат
Читается конечно не за секунду. Информации много. Иногда надо вдумываться. Но зато есть вопросы на подумать с подсказками и ответами. Или вопросы, где можно себя проверить.
Прям качественный обучающий материал. Кайфуйте.
Люблю, когда кто-то пишет посты вместо меня. Точнее не вместо меня. Просто пишут крутые статьи, а я могу просто их вам показать вместо того, чтобы самому заниматься расписыванием. Вот так правильно.
Поиск подстроки в строке.
Есть такой чувак Андрей Гейн. У него есть канал на youtube и канал в тг: @andgein_notes. В конце декабря он написал про 2 текстовые версии о поиске подстроки в строке, которые он выложил в свой блог.
В первой он рассказывает о поиске конкретного паттерна в тексте:
- наивном подходе
- префикс функции и алгоритме Knuth–Morris–Pratt
- далее про Boyer–Moore алгоритм
- и в конце про Two-Way
Сначала прочитайте это замечательное полотно, а потом давайте посмотрим, как использовать продвинутые сёрчеры строк в C++.
В
<functional> есть 3 сёрчера, которые вы можете использовать для поиска строк. Первый -- std::default_searcher. Этот сёрчер, как и все другие, должен передаваться третьим аргументом в алгоритм std::search. Сам сёрчер принимает себе pattern, который ищется в тексте:
auto it = std::search(text.begin(), text.end(), std::default_searcher(pattern.begin(), pattern.end()));
Такой сёрчер будет использовать стандартный алгоритм поиска подстроки в строке.
Не дефолтными сёрчерами могут быть:
- std::boyer_moor_searcher
- std::boyer_moore_horspool_searcher
Пользуемся ими точно так же, как и стандартным.
Если посмотреть на реализацию, например, boyer_moor_search, то можно увидеть ровно описанные Андреем вещи (например, прекомпьют разных табличек).
Из интересного, в шаблоне так же можно передать хеш функцию, которая используется для описанной bas character эвристики: в общем случае мы работаем не только с
char, а с любым произвольным типом символов, так что нужно уметь складывать их в хеш-таблицу, чтобы понимать, сколько символов мы можем скипнуть при очередном мисматче.Если приглядеться на дефолтный сёрчер, то можно понять, что там много не надо и при желании можно и свой реализовать. Можно писать и общего вида алгоритмы, которые будут пробрасывать сёрчеры в
std::search. И пока и всё. Двигаемся ко второй лекции, где освещены продвинутые способы:
- Ахо-Корасик
- суффиксный бор
- суффиксное дерево
- суффиксный автомат
Читается конечно не за секунду. Информации много. Иногда надо вдумываться. Но зато есть вопросы на подумать с подсказками и ответами. Или вопросы, где можно себя проверить.
Прям качественный обучающий материал. Кайфуйте.
❤24🔥13
#algo
Бинарный поиск -- чуть ли не первый алгоритм, который мы узнаем.
Ну я по крайней мере.
Я его увидел когда-то Грокаем алгоритмы, а потом на спор угадывал число одноклассника от 1 до 4kkk за 32 попытки (не получилось: т.к. я считал в уме, погрешность вычислений в голове привела меня к отрезку длиной 8 на последнюю попытку; получил заслуженный фофан ).
Потом мне рассказали про бинарный поиск по ответу.
Это когда у вас ответ на задачу -- монотонная функция.
Можно попробовать разные значения и понять, в какой момент она меняет значение (поиск квадратного корня или 371C как хорошие примеры).
Потом я думал про условный тернарный поиск, чтобы отсекать сразу 2/3 ответов, а не половину. Ничего не придумал.
Он используется, но в других местах.
Недавно попался доклад на P99 про ускорение бинарного поиска, но перед ним надо посмотреть на другой.
Оптимизируем бинарный поиск Сергея Слотина с C++ Zero Cost Conf 2022 (english версия с CppCon того же года; рекомендую её, т.к. в неё чуть больше поместилось).
Оптимизировать бинпоиск по Сергею Слотину это:
👍 избавляться от сложнопредсказуемого бранча
👍 префетчить потенциальные куски памяти
😎 менять memory layout, например с помощью Eytzinger нумерации
🤓 уходить в B-tree like укладывание элементов (S-tree)
🤔 улучшать с помощью B+-tree like оптимизации (S+-tree)
А на P99 был доклад от Ragnar Groot про докручивание решения выше, только на Rust.
Отойдя в сторону, есть вариация бинарного поиска, известная под названием Shar's algorithm, когда вместо поддержания двух границ вы сдвигаете индекс на степень двойки.
Тут прикольная статья про то, как автор генерировал код на D для решения задачи на массивах статического размера.
Некоторое развитие и на C++ есть у другого автора.
Во всех ресурсах выше речь идёт о классическом бинпоиске, а не о решении задачи с такими условиями.
Как говорил Сергей в докладе, есть и другие варианты.
Например, для маленького множества ключей можно заранее предпосчитать ответ на каждый возможный запрос и хранить их в lookup table (LUT) (для 8-16 байтовых типов это делается быстро и не очень дорого по памяти).
Если типы пошире, можно изменить вид LUT и в старших битах хранить границы более узкого отрезка, что позволяет изначально сокращать задачу бинпоиска на несколько итераций.
Подробнее это описано тут.
Или если у вас есть все запросы заранее (решаем в офлайне), можно использовать этот факт и, зная отношения между числами, более эффективно узнавать ответ на новые запросы, исходя из предыдущих.
И не прям про бинпоиск, но под руку попался доклад, на который ссылаются разные бинпоиск статьи, хотя там рассказываются в целом общие идеи и корутины: Gor Nishanov «Nano-coroutines to the Rescue! (Using Coroutines TS, of Course)».
Рядом упомянем и другие вариации:
- exponential search для больших размеров
- fibonacci search для старых архитектур, т.к. там нужно только сложение и вычитание -> выполняется быстрее
- jump search, в котором минимизируется кол-во скачков назад (нужно только мамонтам имхо)
- fractional cascading позволяет оптимизировать задачу нахождения одного числа в нескольких массивах
И раз мы считаем среднее между двумя числами, вот доклад про std::midpoint. Там не всё так просто!
Я так и не научился за секунду писать бинописк правильно.
То единичку забуду, то про инвариант не подумаю.
Коварен.
Есть что-то добавить по теме?
Бинарный поиск -- чуть ли не первый алгоритм, который мы узнаем.
Ну я по крайней мере.
Я его увидел когда-то Грокаем алгоритмы, а потом на спор угадывал число одноклассника от 1 до 4kkk за 32 попытки (
Потом мне рассказали про бинарный поиск по ответу.
Это когда у вас ответ на задачу -- монотонная функция.
Можно попробовать разные значения и понять, в какой момент она меняет значение (поиск квадратного корня или 371C как хорошие примеры).
Потом я думал про условный тернарный поиск, чтобы отсекать сразу 2/3 ответов, а не половину. Ничего не придумал.
Он используется, но в других местах.
Недавно попался доклад на P99 про ускорение бинарного поиска, но перед ним надо посмотреть на другой.
Оптимизируем бинарный поиск Сергея Слотина с C++ Zero Cost Conf 2022 (english версия с CppCon того же года; рекомендую её, т.к. в неё чуть больше поместилось).
Оптимизировать бинпоиск по Сергею Слотину это:
👍 избавляться от сложнопредсказуемого бранча
👍 префетчить потенциальные куски памяти
😎 менять memory layout, например с помощью Eytzinger нумерации
🤓 уходить в B-tree like укладывание элементов (S-tree)
А на P99 был доклад от Ragnar Groot про докручивание решения выше, только на Rust.
Отойдя в сторону, есть вариация бинарного поиска, известная под названием Shar's algorithm, когда вместо поддержания двух границ вы сдвигаете индекс на степень двойки.
Тут прикольная статья про то, как автор генерировал код на D для решения задачи на массивах статического размера.
Некоторое развитие и на C++ есть у другого автора.
Во всех ресурсах выше речь идёт о классическом бинпоиске, а не о решении задачи с такими условиями.
Как говорил Сергей в докладе, есть и другие варианты.
Например, для маленького множества ключей можно заранее предпосчитать ответ на каждый возможный запрос и хранить их в lookup table (LUT) (для 8-16 байтовых типов это делается быстро и не очень дорого по памяти).
Если типы пошире, можно изменить вид LUT и в старших битах хранить границы более узкого отрезка, что позволяет изначально сокращать задачу бинпоиска на несколько итераций.
Подробнее это описано тут.
Или если у вас есть все запросы заранее (решаем в офлайне), можно использовать этот факт и, зная отношения между числами, более эффективно узнавать ответ на новые запросы, исходя из предыдущих.
И не прям про бинпоиск, но под руку попался доклад, на который ссылаются разные бинпоиск статьи, хотя там рассказываются в целом общие идеи и корутины: Gor Nishanov «Nano-coroutines to the Rescue! (Using Coroutines TS, of Course)».
Рядом упомянем и другие вариации:
- exponential search для больших размеров
- fibonacci search для старых архитектур, т.к. там нужно только сложение и вычитание -> выполняется быстрее
- jump search, в котором минимизируется кол-во скачков назад (нужно только мамонтам имхо)
- fractional cascading позволяет оптимизировать задачу нахождения одного числа в нескольких массивах
И раз мы считаем среднее между двумя числами, вот доклад про std::midpoint. Там не всё так просто!
Я так и не научился за секунду писать бинописк правильно.
То единичку забуду, то про инвариант не подумаю.
Коварен.
Есть что-то добавить по теме?
Please open Telegram to view this post
VIEW IN TELEGRAM
5🔥30❤4⚡4❤🔥1