
- •Структура данных и ее классификация.
- •«Дерево», его классификация, примеры, способы перемещения
- •«Хэш-таблицы»
- •Способы реализации «хэш-функций»
- •Разрешение коллизий
- •Алгоритмы и свойства
- •Рекурсия
- •«Разделяй и властвуй», «Ханойские башни»
- •Динамическое прогриммирование
- •Сортировка
- •Выбор, вставка, пузырьки и Шелла
- •По указателях и индексам, распределяющего подсчета
- •Быстрая сортировка
- •Слияния
- •Очередь по преоритету
- •Пирамидальная сортировка
- •Порязрядная сортировка
- •Алгоритм поиска
- •Суть последовательно, бинарного и интерполяционнного поиска
Суть последовательно, бинарного и интерполяционнного поиска
1. Алгоритм поиска по бинарному дереву.
Суть этого алгоритма достаточно проста. Представим себе, что у нас есть набор карточек с телефонными номерами и адресами людей. Карточки отсортированы в порядке возрастания телефонных номеров. Необходимо найти адрес человека, если мы знаем, что его номер телефона 222-22-22. Наши действия? Берем карточку из середины пачки, номер карточки 444-44-44. Сравнивая его с искомым номером, мы видим, что наш меньше и , значит, находится точно в первой половине пачки. Смело откладываем вторую часть пачки в сторону, она нам не нужна, массив поиска мы сузили ровно в два раза. Теперь берем карточку из середины оставшейся пачки, на ней номер 123-45-67. Из чего следует, что нужная нам карточка лежит во второй половине оставшейся пачки... Таким образом , при каждом сравнении , мы уменьшаем зону поиска в два раза. Отсюда и название метода - половинного деления или дихотомии.
Не буду приводить математического доказательства, так как это не является целью данной статьи, а просто отмечу, что скорость сходимости этого алгоритма пропорциональна Log(2)N ¹ . Это означает буквально то, что не более, чем через Log(2)N сравнений , мы либо найдем нужное значение, либо убедимся в его отсутствии.
Другое название этого алгоритма – «метод бинарного дерева» происходит из представления "пути" поиска в виде дерева (у которого каждая следующая ветвь разделяется на две, по одной из которых мы и движемся в дальнейшем)..
Способ очень распространенный в наше время, возможно по причине его эффективности вкупе с простотой программирования этого алгоритма. Именно бинарный поиск используется при поиске в индексах таблиц.
Есть еще один алгоритм, основанный на делении искомого массива на части, аналогичный предыдущему. Я упомяну о нем скорее из-за некоторой его экзотичности.
Последовательный поиск
Задача поиска. Пусть заданы линейные списки: список элементов В=<К1,К2,К3,...,Кn> и список ключей V= (в простейшем случае это целые числа). Требуется для каждого значения Vi из V найти множество всех совпадающих с ним элементов из В. Чаще всего встречается ситуация когда V содержит один элемент, а в В имеется не более одного такого элемента. Эффективность некоторого алгоритма поиска А оценивается максимальным Max{А} и средним Avg{А} количествами сравнений, необходимых для нахождения элемента V в В. Если Pi - относительная частота использования элемента Кi в В, а Si - количество сравнений, необходимое для его поиска, то n Max{А} = max{ Si, i=1,n } ; Avg{А} = Pi Si . i=1 Последовательный поиск предусматривает последовательный просмотр всех элементов списка В в порядке их расположения, пока не найдется элемент равный V. Если достоверно неизвестно, что такой элемент имеется в списке, то необходимо следить за тем, чтобы поиск не вышел за пределы списка, что достигается использованием стоппера. Очевидно, что Max последовательного поиска равен N. Если частота использования каждого элемента списка одинакова, т.е. P=1/N, то Avg последовательного поиска равно N/2. При различной частоте использования элементов Avg можно улучшить, если поместить часто встречаемые элементы в начало списка. Пусть во входном потоке задано 100 целых чисел К1,К2,... К100 и ключ V. Составим программу для последовательного хранения элементов Кi и поиска среди них элемента, равного V, причем такого элемента может и не быть в списке. Без использования стоппера программа может быть реализована следующим образом: /* последовательный поиск без стоппера */ #include main() { int k[100],v,i; for (i=0;i<100;i++) scanf("%d",&k[i]); scanf("%d",&v); i="0"; while(k[i]!="v" && i<100) i++; if (k[i]="=v)" printf("%d %d",v,i); else printf("%d не найден",v); } С использованием стоппера программу можно записать в виде: /* последовательный поиск со стоппером */ #include main() { int k[101],v,i; for (i=0;i<100;i++) scanf("%d",&k[i]);/* ввод данных*/ scanf("%d",&v); k[100]="v;" /* стоппер */ i="0;" while(k[i]!="v)" i++; if (i<100) printf ("%d %d",v,i); else printf ("%d не найден",v); }
Интерполяционный поиск элемента в массиве |
|
|
|
|
Представьте себе, что Вы ищете слово в словаре. Маловероятно, что Вы сначала загляните в середину словаря, затем отступите от начала на 1/4 или 3/4 и т.д, как в бинарном поиске. Если нужное слово начинается с буквы 'А', вы наверное начнете поиск где-то в начале словаря. Когда найдена отправная точка для поиска, ваши дальнейшие действия мало похожи на рассмотренные выше методы. Если Вы заметите, что искомое слово должно находиться гораздо дальше открытой страницы, вы пропустите порядочное их количество, прежде чем сделать новую попытку. Это в корне отличается от предыдущих алгоритмов, не делающих разницы между 'много больше' и 'чуть больше'. Мы приходим к алгоритму, называемому интерполяционным поиском: Если известно, что К лежит между Kl и Ku, то следующую пробу делаем на расстоянии (u-l)(K-Kl)/(Ku-Kl) от l, предполагая, что ключи являются числами, возрастающими приблизительно в арифметической прогрессии. // Поиск в массиве K[1..N] числа X интерполяционным поиском l=1; u=N; while(u>=l) { i=l+¦(u-l)*(X-K[l])/(K[u]-K[l]); if(X<K[i]] u=i-1; else if(X>K[i]] l=i+1; else НАШЛИ, X==K[i]. } Не нашли. Интерполяционный поиск работает за log log N операций, если данные распределены равномерно. Как правило, он используется лишь на очень больших таблицах, причем делается несколько шагов интерполяционного поиска, а затем на малом подмассиве используется бинарный или последовательный варианты. |
ВSТ-дерево
Бинарное (двоичное) дерево (binary tree) - это упорядоченное дерево, каждая вершина которого имеет не более двух поддеревьев, причем для каждого узла выполняется правило: в левом поддереве содержатся только ключи, имеющие значения, меньшие, чем значение данного узла, а в правом поддереве содержатся только ключи, имеющие значения, большие, чем значение данного узла. Бинарное дерево является рекурсивной структурой, поскольку каждое его поддерево само является бинарным деревом и, следовательно, каждый его узел в свою очередь является корнем дерева. Узел дерева, не имеющий потомков, называется листом. Схематичное изображение бинарного дерева: Бинарное дерево может представлять собой пустое множество. Бинарное дерево может выродиться в список:
DST-дерево и TST-дерево
Эмпирический анализ
Эмпирический анализ- вычисление времени выполнения путем запуска на исполнение реализаций алгоритмов.
Допущение при эмпирическом анализе:
1 категория(реализация)
Алгоритмы реализованы на одном и том же языке програмирования.
Реализация алгоритмов осуществлялась с одинаковой тщательностью.
Выполняемый код (программа) алгоритмов был получен с использованием одной и той же среды программирования.
Выполнение алгоритмов осуществлялась на одной и той же машине.
2 категория (исходные данные)
Использование реальных данных (точное измерение времени выполнения)
Использование случайных данных (гарантия анализа именно алгоритма, измерение среднего времени выполнения)
Использование необычных данных (измерение наихудшего случая)
Математический анализ
Математический анализ- вычисление относительного времени выполняется путем построения математического выражения.
Математический анализ применяется если:
Отсутсвует реализация алгоритма.
Необходимо предсказать время выполнения алгоритма в новой среде.
Сравниваються разные алгоритмы, предназначенны для одной задачи.
При выполнении мат. Анализа важно помнить, что все алгоритмы имеют параметры, которые влияют на время их выполнения(типы абстрактных операций).