- •Глава I Основные понятия
- •§1. Введение
- •§2. Алгоритмы и их сложности
- •§3. Запись алгоритмов
- •Глава II Графы и сети
- •§1. Графы, сети
- •§2. Машинное представление графов и сетей.
- •Глава III Сортировка данных
- •§1. Сложность задачи сортировки
- •§2. Алгоритм сортдерево
- •Глава IV Поиск в графе
- •§1. Поиск в глубину.
- •§2. Поиск в ширину.
- •§3. Цепи и пути в графах.
- •§4. Пути в лабиринте.
- •Глава V Задача о минимальном остове
- •Глава VI Пути в сетях
- •§1 Общий случай. Алгоритм Форда-Беллмана.
- •§2 Случай неотрицательных весов. Алгоритм Дейкстры.
- •§3. Случай бесконтурной сети.
- •§4. Задача о максимальном пути и сетевые графики.
- •§3. Задача о maxmin пути.
- •§6. Задача о кратчайших путях между всеми парами узлов.
§2. Алгоритм сортдерево
Оказывается, полученная оценка снизу для сложности алгоритмов сортировки не может быть улучшена, а именно, существуют алгоритмы сортировки, имеющие сложность O(nlogn). Один из таких алгоритмов, называемый СОРТДЕРЕВО, мы сейчас разберем. Помимо того, что это полезный алгоритм упорядочения, к тому же теоретически наилучший, в нем используется интересная структура данных, полезная и в других ситуациях.
Алгоритм СОРТДЕРЕВО был предложен в 1964 году независимо Уильямсом (под названием Heapsort) и Флойдом (Treesort). Иногда, например, в [6], этот алгоритм называют алгоритмом пирамидальной сортировки.
В массиве А[1..n] введем структуру корневого двоичного дерева. считая, что в корне находится элемент А[1], и элемент A[i] имеет двух сыновей: A[2i] (если 2i ≤ n) и A[2i+1] (если 2i+1 < n).- Такую структуру назовем двоичным деревом массива А.
Лемма 2.1. Двоичное дерево массива длины n имеет высоту .
□ Напомним (см. гл.2), что высота корневого дерева есть по определению длина самого длинного пути из корня до какого-нибудь листа. Например, на рис.3.2 это путь до листа, в котором находится число 7. Легко видеть, что длина этого пути равна 3.
Пусть k - высота двоичного дерена массива длины n. Для всех s < k в дереве имеется ровно 2s вершин глубины s. Пусть h — количество вершин глубины k. Тогда справедливо неравенство 1 < h < 2k. Поскольку число вершин равно n — длине массива, то справедливо равенство:
n = 1 + 2 + ... + 2k-1 + h = 2k - 1 + h.
Учитывая, что h ≤ 2k, получаем, что
n = 2k - 1 + h ≤ 2k - 1 + 2k < 2k+1.
Отсюда n < 2k+1. С другой стороны, так как h ≥ 1, то
n= 2k - 1 + h ≥ 2k.
Таким образом, справедливо неравенство 2k ≤ п ≤ 2k+1. Отсюда, k ≤ logn ≤ k+1, то есть k = .
Двоичное дерево массива А называется сортирующим деревом массива А, если выполняются условия:
А[k] ≥ А[2 k] и А[k] ≥ А[2 k +1] для к = l,2,....(*)
Непосредственно из определения вытекает, что наибольший элемент массива находится в корне его сортирующего дерева. Легко видеть также, что двоичное дерево, изображенное на рис. 3.2, не является сортирующим.
Именно на представлении массива сортирующим деревом основан один из теоретически наилучших алгоритмов сортировки данных — СОРТДЕРЕВО. Изложим вначале основные принципы работы этого алгоритма.
На первом этапе элементы массива А переставляются так, чтобы выполнялось свойство (*) сортирующего дерева, иначе говоря, двоичное дерево массива А преобразуется в сортирующее. Процедуру, осуществляющую это преобразование, будем называть ПСД.
Затем, поскольку по свойству сортирующего дерева, элемент в корне, а именно, А[1] является наибольшим в А, то, переставляя А[1] и А[n], и удаляя А[n] из дерева, получим массив А[1..п-1] и A[n] = max А[1..n]. Теперь преобразуем А[1..n-1] в сортирующее дерево, затем переставим А[1] и А[n-1] и удалим А[n-1]. Процесс продолжается до тех пор, пока в сортирующем дереве не останется один элемент. Тогда А[1],...,А[n] — упорядоченная по неубыванию последовательность.
Перейдем к формальному описанию алгоритма СОРТДЕРЕВО. Начнем с описания процедуры ПСД (построение сортирующего дерева). В его основе лежит рекурсивная процедура ПЕРЕСЫПК(i,j), имеющая параметры i и j. Они задают область ячеек массива А, которая в результате процедуры ПЕРЕСЫПКА будет обладать свойством сортирующего дерева, при этом его корень помещается в вершину i.
Неформально можно сказать, что построение сортирующего дерева идет снизу вверх, начиная с листьев, и справа налево, строя при этом все большие и большие сортирующие поддеревья.
Действительно, все листья уже являются сортирующими поддеревьями. В общем случае, когда мы находимся в вершине i, поддеревья с корнями 2i и 2i+l уже являются сортирующими. Если выполняются неравенства
A[i] ≥ A[2i] и A[i] ≥ A[2i+1],
то дерево с корнем в i является сортирующим, и его построение с корнем в вершине i закончено.
Если хотя бы одно из этих неравенств не выполнено, то переставляем A[i] и А[к], где к — тот сын i, который содержит наибольшего из элементов A[2i] и A[2i+1], Пусть, например, k = 2i, тогда поддерево с корнем в вершине 2i + 1 останется сортирующим, но может «испортиться» поддерево с корнем в вершине 2i. Для него мы повторяем эту процедуру, которая и называется ПЕРЕСЫПКА(i,j). Второй параметр j задает длину массива, в котором выполняется эта процедура. Разумеется, при перестройке двоичного дерева в сортирующее этот параметр равен n, так как работа выполняется для всего массива А. В дальнейшем, при упорядочении массива А второй параметр j будет меняться.
Ниже дано формальное описание процедур ПЕРЕСЫПКА и ПСД. Без формального описания используется функция MAXCЫH(i), значением которой является тот сын i, который содержит наибольший из элементов A[2i] и A[2i+1],
Структура этой процедуры следующая. В строке 2 осуществляется проверка, является ли узел с номером i листом или нет. В строке 4 проверяется условие (*) для дерева с корнем в узле с номером i, и если оно не выполнено, то переставляется элемент в корне дерева с наибольшим из элементов в его сыновьях, и вновь вызывается процедура ПЕРЕСЫПКА, но уже с другим параметром.
procedure ПЕРЕСЫПКА( i ,j);
begin
if i ≤ then (* если i — не лист *)
begin k := MAXCЫH(i);
if A[i] < A[k] then
begin
A[i]↔ A[k]; ПЕРЕСЫПКА(k,j)
end;
end;
end;
Процедура ПСД, строящая сортирующее дерево массива А, выглядит следующим образом.
procedure ПСД;
begin
for i := downto 1 do ПЕРЕСЫПКА(i,n)
end
Разберем пример, иллюстрирующий работу обеих этих процедур
Пусть задан числовой массив А[1…10]:
I |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
А[I] |
2 |
3 |
6 |
1 |
4 |
5 |
8 |
0 |
7 |
9 |
Все перестановки элементов массива, осуществляемые процедурой ПЕРЕСЫПКА, будем отображать в двоичном дереве изменяющегося массива. Для краткости, на схеме вместо ПЕРЕСЫПКА(i,10) будем писать П(i).
Обращаем внимание на работу процедур П(2) и П(1). В процедуре П(2) вначале были переставлены числа 3 и 9 в деревеd), затем была вызвана процедура 11(5), которая переставила 3 и 4. Процедура П( 1) в дереве е) переставила 9 и 2. затем вызвала процедуру П(2), которая переставила 2 и 7 и вызвала П(4), но П(4) уже ничего не переставляла.
Изобразим окончательный результат в виде таблицы:
i |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
A[I] |
9 |
7 |
8 |
2 |
4 |
5 |
6 |
0 |
1 |
3 |
Алгоритм упорядочения, основанный на понятии сортирующего дерева, может быть формально записан следующим образом:
АЛГОРИТМ 3.2. СОРТДЕРЕВО
Данные: массив элементов А[1..n].
Результат: массив элементов А[1..n], упорядоченный по неубыванию, то есть А[1] ≤ А[2] ≤ ... ≤ А[n].
begin
ПСД; (* вначале строится сортирующее дерево *)
for i := n downto 2 do
begin
A[l] ↔ A[i];
ПЕРЕСЫПКА( 1,i-1)
end;
end
Продолжим рассмотрение ранее начатого примера.
На рис.3.4 дерево f) является сортирующим деревом исходного массива А, полученным в результате работы процедуры ПСД. Затем происходит перестановка А[1] и А[10] (дерево g) ). К дереву g) применяется процедура ПЕРЕСЫПКА(1,9), в результате которой число 3 проходит путь по правым ветвям из корня дерева до листа. Затем вновь происходит перестановка корня с листом и т.д. В результате получится массив А, упорядоченный по возрастанию.
Теорема 3.3. Алгоритм упорядочения СОРТДЕРЕВО имеет вычислительную сложность O(nlogn).
□ Естественно считать размером задачи сортировки длину массива элементов — n. Найдем вначале сложность процедуры ПСД. Пусть k — высота двоичного дерева исходного массива А. По лемме 3.1 имеем равенство к =, в частности, отсюда следует, что 2k ≤ n.
Через T(k-i) обозначим сложность процедуры ПЕРЕСЫПКА, выполняемой из всех вершин глубины k-i. Тогда сумма Σ = Т(k-1) + Т(k-2) + ... +Т(0) равна сложности всей процедуры, строящей сортирующее дерево, поскольку ПЕРЕСЫПКА начнет выполняться с вершин глубины k-1.
Пусть вершина i имеет глубину k-1 и (возможно) двух сыновей: 2i и 2i+l. Пересыпка из вершины заключается в сравнении трех чисел: A[i], A[2i], A[2i+1] и, если это необходимо, перестановки A[i] с наибольшим из A[2i] и A[2i+1], Ясно, что сложность пересыпки из вершины i не зависит от п и оценивается сверху константой с. Тогда справедливо неравенство
Т(k — 1) ≤ с 2k-1,
так как имеется ровно 2k-1 вершина глубины k-1. Переходя на более высокий уровень, т.е. рассматривая вершины глубины k-2, заметим, что справедливо неравенство
Т(к - 2) ≤ с 2k-2 + 1/2 Т(k — 1),
так как после сравнения этого элемента в вершине глубины k-2 с элементами в ее сыновьях дальнейшая пересыпка пойдет только не более, чем из одного сына. Аналогично,
T(k - i) ≤ с 2k-1 + 1/2 Т(к - i + 1) для всех i ≤ k.
Отсюда, Σ = Т(k - 1) + Т(k - 2) +...+ Т(0) ≤ с 2k-1 + с 2k-2 +...+ с + +1 /2 Т(k - 1) + 1 /2 Т(k - 2)+...+1 /2 Т(1) ≤ с (2k-1 +2k-2+...+1)+1 /2Σ. Значит, Σ ≤ с (2k-1 + 2k-2 +...+1) < 2с 2k < 2 с n, так как 2k ≤ n.
Итак, доказано неравенство: Σ ≤ 2сn. Это означает, что сложность процедуры ПСД есть О(n), то есть она линейна.
Осталось оценить сложность основного цикла в строках 3-7 алгоритма СОРТДЕРЕВО. Для этого достаточно оценить сложность процедуры ПЕРЕСЫПКА(1 ,n-1). Элемент в корне А[1] сравнивается с элементами в его сыновьях А[2] и А[3]. Эту сложность мы ранее обозначали константой с. Затем (если произошла перестановка) нужно повторить операции сравнения, считая корнем того сына, в котором произошла перестановка. В худшем случае перестановки и сравнения будут происходить до тех пор, пока элемент не вытолкнется в лист. Путь, пройденный элементом, имеет длину ,равную высоте дерева. Итак, сложность процедуры ПЕРЕСЫПКА(1,n-1) есть O(logn).
Отсюда, сложность цикла в строках 3-7 есть O(nlogn). Окончательно, сложность алгоритма СОРТДЕРЕВО есть О(n) + O(logn) = O(nlogn). □
ЗАДАЧИ
(Алгоритм БЫСТРОСОРТ) Заданную последовательность элементов S = {а1,а2,...,аn} можно упорядочить следующим образом. Выбрать произвольный элемент а, разбить S на три последовательности S1, S2, соответственно меньших а, равных а и больших а. Затем повторить ту же процедуру к S1 и к S3 . Напишите на упрощенном Паскале рекурсивную и нерекурсивную версии этого алгоритма. Докажите, что его сложность есть O(n2). Несмотря на то, что сложность алгоритма БЫСТРОСОРТ есть O(n2), то есть хуже, чем у алгоритма СОРТДЕРЕВО, БЫСТРОСОРТ предпочтительней, вообще говоря, с точки зрения практики. Дело в том, что сложность вычисляется в худшем случае, однако в среднем БЫСТРОСОРТ имеет сложность O(nlogn). Понятие сложности в среднем изложено в книге [6], там же дано детальное описание как рекурсивной, так и нерекурсивной версий этого алгоритма.
а) (Сортировка обменами) Последовательным просмотром элементов а1,...,аn найти первое к такое, что аk > аk+1. Поменять аk и аk+1 возобновить просмотр последовательности, начиная с аk+1 и так далее. В результате наибольший элемент передвинется на последнее место. Следующие просмотры начинать с а1 уменьшая на единицу количество просматриваемых элементов. Последовательность будет упорядочена после просмотра, в котором либо не произошло ни одной перестановки, либо участвовали первый и второй элементы.
б) (Сортировка простыми вставками) Просматривать последовательно а2,...,аn, и каждый новый элемент аk вставлять на подходящее место в уже упорядоченную последовательность a1,...,ak-1. Это место определяется последовательным сравнением аk с элементами a1,...,ak-1.
Напишите на упрощенном Паскале алгоритмы сортировки обменами и простыми вставками. Докажите, что сложности этих алгоритмов — О(n2).
Исследовать число сравнений и число перестановок элементов a1,...,an для алгоритмов сортировки выбором, обменами и простыми вставками. Докажите, что число перестановок в алгоритме сортировки выбором есть О(n). В то же время и число сравнений для сортировки выбором, и обе указанные характеристики для сортировок обменами и простыми вставками есть O(n2).
В частности, это означает, что сортировка выбором предпочтительней сортировки обменами и сортировки простыми вставками в тех случаях, когда перестановка элементов является существенно более сложным делом, чем их сравнение.
Например, такая ситуация имеет место в задаче: упорядочить строки заданной матрицы размером nm в порядке неубывания первых элементов.
4. Даны пять попарно различных чисел: а, b, с, d, е. Упорядочить их по возрастанию, используя для этого не более семи сравнений.
5. (Сортировка вычерпыванием). Дана последовательность целых чисел a1,a2,...,an, заключенных между 0 и m-1. Если т не очень велико, то ее можно упорядочить следующим образом:
1. Организуем m пустых очередей: по одной для каждого целого числа от 0 до m-1.
2. Последовательно просматривая исходную последовательность a1,a2,...,an, помещаем элемент аk в очередь с номером k.
3. Сцепим очереди (содержимое (k+1)-ой очереди припишем к концу k-ой очереди). В результате получим упорядоченную по возрастанию последовательность.
Докажите, что этот алгоритм имеет сложность О(п).
6. Пусть двоичное дерево массива А[1..n] является его сортирующим деревом, и все А[k] попарно различны. Как найти второй по величине элемент этого массива? А третий? Какую глубину может иметь в сортирующем дереве вершина, содержащая k-ый по величине элемент массива А?