- •1. Cтруктуры данных и алгоритмы
- •1.1. Понятие структур данных и алгоритмов
- •1.2. Информация и ее представление в памяти
- •1.3. Классификация структур данных
- •1.4. Операции над структурами данных
- •1.5. Структурность данных и технология программирования
- •2. Простые структуры данных
- •2.1. Числовые типы
- •2.1.1. Целые типы
- •2.1.2. Вещественные типы
- •1). Десятичное число 15.375;
- •2). Десятичное число -0.5;
- •3). Десятичное число -25.75;
- •4). Число 0.0;
- •5). Числа верхней и нижней границ положительного диапазона
- •2.1.3. Операции над числовыми типами
- •2.2. Битовые типы
- •2.3. Логический тип
- •2.4. Символьный тип
- •2.5. Перечислимый тип
- •2.6. Интервальный тип
- •2.7. Указатели
- •2.7.1. Физическая структура указателя
- •2.7.2. Представление указателей в языках программирования
- •2.7.3. Операции над указателями.
- •3. Статические структуры данных
- •3.1. Векторы
- •3.2. Массивы
- •3.2.1. Логическая структура
- •3.2.2. Физическая структура
- •3.2.3. Операции
- •3.3 Множества
- •3.3.1 Множества и математика
- •3.3.1.1 Способы задания множеств
- •3.3.1.2 Операции над множествами
- •3.3.1.3 Множественный тип и его свойства
- •3.3.1.4 Переменные и константы множественных типов
- •3.3.1.5. Выражения множественного типа
- •3.3.2. Представление множеств в памяти эвм
- •3.3.2.1. Числовые множества
- •3.3.2.2. Символьные множества
- •3.3.2.3. Множество из элементов перечислимого типа
- •3.3.2.4. Множество от интервального типа
- •3.3.2.5. Операции над множествами
- •3.4. Записи
- •3.4.1. Логическое и машинное представление записей
- •3.4.2. Операции над записями
- •3.5. Таблицы
- •3.6.2. О сложности алгоритмов
- •3.6.3. Сортировка подсчетом
- •3.6.4. Сортировка включением
- •3.6.4.1. Простое включение
- •3.6.4.2.Метод Шелла (модифицированный)
- •3.6.5. Сортировка извлечением
- •3.6.5.1.Простое извлечение
- •3.6.5.2.Древесная сортировка(модифицированный)
- •3.6.6. Сортировка обменами
- •3.6.6.1. Пузырьковая сортировка
- •3.6.6.2. Быстрая сортировка (Хоара) (модифицированный)
- •3.6.7. Сортировка слиянием
- •3.6.8. Сортировка распределением
- •3.6.8.1. Вырожденное распределение
- •3.6.8.2. Сортировка распределением
- •3.6.9. Сравнительный анализ
- •3.7. Операции логического уровня над статическими структурами. Поиск
- •3.7.1. Линейный (последовательный) поиск
- •3.7.2. Быстрый последовательный список
- •3.7.3. Дихотомический (бинарный) поиск
- •3.7.4. Интерполяционный поиск
3.7. Операции логического уровня над статическими структурами. Поиск
В этом и следующих разделах представлен ряд алгоритмов поиска данных и сортировок, выполняемых на статических структурах данных, так как это характерные операции логического уровня для таких структур. Однако, те же операции и те же алгоритмы применимы и к данным, имеющим логическую структуру таблицы, но физически размещенным в динамической памяти и на внешней памяти, а также к логическим таблицам любого физического представления, обладающим изменчивостью.
Объективным критерием, позволяющим оценить эффективность того или иного алгоритма, является, так называемый, порядок алгоритма. Порядком алгоритма называется функция O(N), позволяющая оценить зависимость времени выполнения алгоритма от объема перерабатываемых данных (N - количество элементов в массиве или таблице). Эффективность алгоритма тем выше, чем меньше время его выполнения зависит от объема данных. Большинство алгоритмов с точки зрения порядка сводятся к трем основным типам:
- степенные - O(N^a);
- линейные - O(N);
- логарифмические - O(logA(N)). (Здесь и далее запись вида "logА" обозначает "логарифм по основанию А").
Эффективность степенных алгоритмов обычно считается плохой, линейных - удовлетворительной, логарифмических - хорошей.
Аналитическое определение порядка алгоритма, хотя часто и сложно, но возможно в большинстве случаев. Возникает вопрос: зачем тогда нужно такое разнообразие алгоритмов, например, сортировок, если есть возможность раз и навсегда определить алгоритм с наилучшим аналитическим показателем эффективности и оставить "право на жизнь" исключительно за ним? Ответ прост: в реальных задачах имеются ограничения, определяемые как логикой задачи, так и свойствами конкретной вычислительной среды, которые могут помогать или мешать программисту, и которые могут существенно влиять на эффективность данной конкретной реализации алгоритма. Поэтому выбор того или иного алгоритма всегда остается за программистом.
3.7.1. Линейный (последовательный) поиск
Линейный поиск – самый простой из известных. Суть его заключается в следующем. Множество элементов просматривается последовательно в некотором порядке, гарантирующем, что будут просмотрены все элементы множества (например, слева направо). Если в ходе просмотра множества будет найден искомый элемент, просмотр прекращается с положительным результатом; если же будет просмотрено все множество, а элемент не будет найден, алгоритм должен выдать отрицательный результат. Именно так поступает человек, когда ищет что-то в неупорядоченном множестве. Например, при поиске нужной визитки в некотором неупорядоченном множестве визиток человек просто перебирает все визитки в поисках нужной.
Оформим описанный алгоритм в виде кусочка программы на Паскале. Пусть множество хранится в первых n элементах массива A. При этом не важен тип элементов множества, важна лишь возможность проверки эквивалентности элемента множества искомому элементу Key.
i := 1;
while (i<=n)and(A[i]<>Key) do Inc(i);
if A[i]=Key then <элемент найден> else <элемент не найден>;
В среднем этот алгоритм требует n/2 итераций цикла. Это означает сложность алгоритма поиска T(n). Единственное требование к способу хранения множества – существование некоторого порядка на множестве: чтобы можно было указать, что один элемент предшествует другому. Здесь можно хранить множество как в массиве (рассмотренный выше кусок программы), так и в обычном однонаправленном списке:
Type
PListItem = ^TListItem;
TListItem = record
Item: TItem;
Next: PListItem;
end;
Рассмотрим добавление элемента Key во множество, хранимое в виде массива. Здесь достаточно просто дописать новый элемент в «конец» множества:
Inc(n); A[n] := Key;
Удалению элемента всегда предшествует успешный поиск. Это значит, что сложность операции удаления не может быть меньше сложности операции поиска. В данном случае при удалении нам придется сначала найти положение удаляемого элемента, затем все элементы, следующие за ним, сдвинуть на одну позицию к началу массива и уменьшить n. То есть в любом случае надо пробежать весь массив от начала до конца (это означает сложность T(n)):
i := 1;
while (i<=n)and(A[i]<>Key) do Inc(i); {поиск}
if A[i]=Key then
begin
while i<n do
begin
A[i] := A[i+1]; {сдвигаем}
Inc(i);
end;
Dec(n);
end;
Теперь положим, что множество хранится в виде списка. Добавление элемента сведется к добавлению элемента в начало списка (при добавлении в конец списка пришлось бы сначала искать конец, либо специально хранить указатель на последний элемент списка). Удаление элемента будет состоять из поиска и собственно удаления (никаких сдвигов последующих элементов производить не придется.).
