- •1Введение
- •2Сортировки
- •2.1Сортировки массивов
- •2.2Сортировка простым включением
- •2.3Сортировка простым выбором
- •2.4Сортировка простым обменом
- •2.5Сравнение простых сортировок
- •2.6 Сортировка шелла
- •2.7Пирамидальная сортировка
- •2.8Быстрая сортировка
- •2.9Поиск медианы и квантилей
- •2.10Сравнение сортировок
- •3Поиск подстроки в строке
- •3.1Поиск в строке
- •3.2Простой поиск в строке
- •3.3Поиск в строке. Алгоритм боуера-мура
- •4Генерация перестановок
- •4.1Генерация перестановок последовательности натуральных чисел
- •4.2Генерация перестановок элементов в антилексикографическом порядке
- •4.3Генерация перестановок за одну транспозицию элементов
- •4.4Генерация перестановок за одну транспозицию соседних элементов
- •5Генерация подмножеств
- •5.1Генерация всех подмножеств множества
- •5.2Генерация m -элементных подмножеств множества натуральных чисел
- •Var m: integer; {Размер подмножества}
- •Var I, j, p: integer;
- •5.3Генрация k-компонентных выборок на множестве {0, 1}
- •Var k: integer; {Количество нулей в кортеже}
- •I: integer;
- •Var I, j, p: integer;
- •If Finish then Break {Exit};
- •6Генерация разбиений
- •6.1Разбиение целых чисел
- •Var I, j: integer;
- •Var j: integer;
- •Var I, j, k: integer;
- •Var d, l, Sum: integer;
- •6.2Разбиение множеств
- •/1, 2, 3/ И /4/ затем /1, 2/ и /3, 4/ и т.Д. }
- •I, j, k, r, s: integer;
- •If not Flag2 then
- •If Flag1 then
- •If Forvd[j] then { j движется вперед}
- •7Обходы бинарных деревьев
- •7.1Процедуры прохождения бинарных деревьев
- •8Поиск на бинарных деревьях
- •8.1Процедуры поиска на бинарных деревьях
- •Рекомендованная литература
8Поиск на бинарных деревьях
Пусть дано БД вида. Рассмотрим задачи поиска узла, удовлетворяющего некоторому критерию. Фактически задача сводится к перебору всех вершин до тех пор, пока не будет найдена требуемая вершина. В принципе перебор узлов дерева можно было бы осуществить одним из четырех рассмотренных выше методов обхода вершин БД. Однако задачи поиска обычно требуют указания пути, по которому можно получить доступ к целевой вершине – узлу дерева, удовлетворяющему заданному критерию поиска. Но это крайне неэффективная стратегия. Поэтому применяют две специальные стратегии – поиск в глубину и поиск в ширь. При поиске в глубь последовательно рассматриваются все возможные пути от корня к листьям дерева, пока не будет найден путь, ведущий к целевой вершине. При поиске в ширину узлы дерева просматриваются слой за слоем, пока не будет найдена целевая вершина.
A
B C
D E F G
H I J K L M N O
Так для приведенного дерева в результате поиска в ширину получим следующие слои дерева:
A
B C
D E F G
H I J K L M N O
А в результате поиска в глубину получим следующие пути от корня дерева к его листьям:
A B D H
A B D I
A B E J
A B E K
A C F L
A C F M
A C G N
A C G M
Из приведенных примеров видно, что в случае горизонтального поиска все вершины становятся доступными за меньшее количество шагов (в примере их всего 4) чем в случае поиска в глубину (в примере их 6). Однако, также видно, что при поиске в ширину объем обрабатываемых на каждом шаге данных резко возрастает. В пределе от шага к шагу он увеличивается вдвое. При поиске в глубину на любом шаге объем обрабатываемых данных не превышает глубины дерева.
8.1Процедуры поиска на бинарных деревьях
Процедура поиска в ширину.
Процедуру PBDGP можно легко модифицировать к варианту, когда узлы дерева передаются на обработку по отдельным слоям дерева. Именно такой результат необходим в задачах поиска.
Для реализации поиска в ширину корень дерева помещается в очередь и она передается в процедуру. В процедуре очередь обрабатывается следующим образом. Отмечается начало и конец очереди. Для первого элемента очереди – узла дерева, раскрываются его левый и правый сыновья и соответствующие элементы последовательно помещаются в очередь вслед за признаком конца очереди. Первый элемент удаляется из очереди и все повторяется заново до тех пор, пока исходная очередь не будет исчерпана. Таким образом, если на вход процедуры передается очередь из узлов дерева уровня К, то после своего выполнения процедура возвращает очередь из узлов дерева уровня К + 1. Полученная очередь может подвергнуться обработке, после чего выполняется очередное обращение к процедуре и т.д.
Описанное решение предлагается в процедуре PBDGPm. Ниже приводится полное описание всех необходимых типов данных и вспомогательных процедур.
type TPNode= ^TNode;
TNode = record
Inf: array [1..10] Of Char;
Left, Right: PNode;
end;
TPointQueue = ^TQueue;
TQueue = record
PNTree: TPNode;
Next: TPointQueue;
end;
var Rut: TPNode;
BeginQueue, EndQueue, BeginList: TpointQueue;
(* процедура занесения элемента в очередь *)
procedure InsertQ (var BegQue, EndQue: TPointQueue; NewElm: PNode);
var WP: TpointQueue;
begin
New (WP);
WP ^.PNTree : = NewElm;
WP ^.Next: = Nil;
if EndQue = Nil then
begin
BegQue: = WP (* cлучай первого элемента *)
EndQue: = WP;
end;
else
begin
EndQue^.Next: = WP;
EndQue: = WP;
end;
end; {InsertQ}
(* функция проверки очереди на пустоту *)
function EmptyQ (BegQue: TPointQueue): boolean;
begin
if BegQue = Nil then Empty: = True
else Empty: = False
end; {EmptyQ}
{ RemoveQL - процедура извлекает элемент из очереди, но не удаляет его}
procedure RemoveQL (var BegQue, EndQue, BegQueL: TPointQueue);
begin
if Empty (BegQue) = False then
begin
{элемент удаляется из очереди}
BegQueL: = BegQue;
BegQue: = BegQue^.Next;
if BegQue = Nil then EndQue: = Nil;
(*если в очереди оставался один (последний) элемент, то его удаление приводит к пустой очереди, следовательно, BegQue = Nil и EndQue = Nil*)
end;
end; {RemoveQL}
{BreadthFirstSearch – модификация процедуры PBDGP. Процедура возвращает указатель на начало очереди, содержащей указатели на все узлы дерева, полученные в результате горизонтального обхода узлов дерева уровня К = 1 - .N}
procedure BreadthFirstSearch (var BegQ, EndQ: TPointQueue);
var WP: TPointQueue;
begin
WP:= EndQ;
repeat
{элементы дописываются в очередь до тех пор, пока не будут перебраны все узлы дерева уровня К.}
if BegQ^.PNTree^.Left <> Nil then
InsertQ (BegQ, EndQ, WPB^.PNTree^.Left); {занесение левого потомка узла дерева в очередь.}
if BegQ^.PNTree^.Right <> Nil then
InsertQ (BegQ, EndQ, WPB^.PNTree^.Right); {занесение правого потомка узла дерева в очередь.}
{изъятие элемента из очереди и его занесение в список}
RemoveQL (BegQ, EndQ, BegL: TPointQueue);
until (BegQ = WP);
end; { BreadthFirstSearch}
Обращение к этой процедуре имеет вид:
begin
• • •
BeginQueue := Nil;
EndQueue := Nil;
InsertQ (BeginQueue, EndQueue, Rut); {занесение корня дерева в очередь}
BreadthFirstSearch (BeginQueue, EndQueue); {Первый поиск}
• • •
BreadthFirstSearch (BeginQueue, EndQueue); {Очередной поиск}
end.
Процедура поиска в глубину.
Процедуру PBDPP можно легко модифицировать к варианту процедуры PBDPPm, которая возвращает путь от корня дерева (поддерева) к его самому левому листу. Именно такой результат необходим на каждом этапе в задачах поиска в глубину.
Реализацию поиска в глубину выполняет процедура DephtFirstSearch. Процедура в качестве параметров получает указатель на вершину дерева и указатели на начало и конец двунаправленной очереди, в которой размещаются указатели на соответствующие узлы дерева. Двунаправленная очередь выбрана для удобства занесения и удаления элементов в конце очереди.
На первом этапе в очередь заносится указатель на вершину дерева.
Для реализации первого поиска процедура DephtFirstSearch вызывает процедуру PBDPPm, которая в качестве параметров получает указатели на вершину дерева и на начало и конец очереди из одного элемента – указатель на вершину дерева. Процедура PBDPPm отыскивает путь от корня дерева к его самому левому листу и помещает этот путь в очередь, которая является результатом первого поиска и возвращается процедурой DephtFirstSearch в вызывающую программу для последующей обработки.
Все последующие поиски выполняются по следующей схеме. После очередной обработки очереди вызывается процедура DephtFirstSearch. В качестве параметров она получает указатели на вершину дерева и на начало и конец очереди, являющейся результатом предыдущего поиска. В процедуре очередь обрабатывается следующим образом. В соответствии с описанием пути, содержащимся в очереди, выполняется спуск по дереву. Когда достигается лист дерева, из конца очереди удаляется элемент, соответствующий листу дерева, и начинается откат по пути спуска.
В процессе отката возможны следующие случаи:
Если возврат к узлу выполняется из правого поддерева, то из конца очереди удаляется соответствующий этому узлу элемент и откат продолжается.
Если возврат к узлу выполняется из левого поддерева, то выполняется проверка – есть ли у него правый сын.
Если правого сына у узла нет, то из конца очереди удаляется соответствующий этому узлу элемент и откат продолжается.
Если правый сын у узла есть, то откат прекращается. Указатель на правого сына узла передается в процедуру PBDPPm как указатель на вершину поддерева. Процедура отыскивает путь от корня поддерева к его самому левому листу и дописывает соответствующие элементы в конец двунаправленной очереди, которая, является результатом очередного поиска. Результат очередного поиска возвращается процедурой DephtFirstSearch в вызывающую программу.
Ниже приводится полное описание всех необходимых типов данных и вспомогательных процедур.
type TPNode= ^TNode;
TNode = record
Inf: array [1..10] Of Char;
Left, Right: PNode;
end;
TPointDQueue = ^TDQueue; {Двунаправленный список}
TDQueue = record
PNTree: TPNode;
Next, Prev: TPointQueue;
end;
var Rut: TPNode;
BeginQueue, EndQueue: TPointDQueue;
{InsertDQ - процедура занесения элемента в двунаправленную очередь}
procedure InsertDQ (var BegQue, EndQue: TPointDQueue; NewElm: PNode);
var WP: TpointDQueue;
begin
New (WP);
WP^.PNTree : = NewElm;
WP^.Next: = Nil;
WP^.Prev: = Nil;
if EndQue = Nil then
begin
BegQue: = WP (* cлучай первого элемента *)
EndQue: = WP;
end;
else
begin
EndQue^.Next: = WP;
WP^.Prev := EndQue;
EndQue: = WP;
end;
end; {InsertDQ}
{ EmptyDQ - функция проверки очереди на пустоту}
function (BegQue: TPointDQueue): boolean;
begin
if BegQue = Nil then Empty: = True
else Empty: = False
end; {EmptyDQ}
{ RemoveDQEnd - процедура удаления последнего элемента двунаправленной очереди}
procedure RemoveDQEnd (var BegQue, EndQue: TpointQueue; PrevEndQue: TpointQueue);
var WP: TpointQueue;
begin
if Empty (BegQue) = True then Exit;
{элемент удаляется из конца очереди}
WP: = EndQue;
EndQue: = EndQue^.Prev;
Dispose (WP);
if EndQue = Nil then BegQue: = Nil; {если в очереди оставался один (последний) элемент, то его удаление приводит к пустой очереди, следовательно, BegQue = Nil и EndQue = Nil}
end; {RemoveQEnd}
{PBDPPm – модификация процедуры PBDPP. Выполняет поиск самого левого из возможных путей от вершины, переданной как корень дерева. Или иначе, процедура выполняет поиск пути от корня к самому левому листу дерева}
procedure PBDPPm (PTree: TPNode; var BegQ, EndQ: TPointQueue);
begin
InsertQ (BegQ, EndQ, PTree); {вставка очередного элемента в очередь}
if Ptree^.Left <> Nil then
PBDPPm (PTree^.Left, BegQ, EndQ,)
else
if Ptree^.Right <> Nil then
PBDPPm (PTree^.Right, BegQ, EndQ)
end; {PBDPPm}
{DephtFirstSearch - процедура возвращает указатель на начало двунаправленной очереди, содержащей указатели на все узлы дерева, полученные в результате очередного поиска в глубину.}
procedure DephtFirstSearch (var WRoot: TNode; var BegQ, EndQ: TPointQueue);
var Prev, Curr: TPointQueue;
begin
If BegQ = EndQ then {В очередь занесен только один элемент – корень дерева..}
PBDPPm (WRoot, BegQ, EndQ) {Поиск первого пути в дереве - пути от корня к самому левому листу дерева }
else {В очередь занесено несколько элементов. Поиск пути от корня к очередному листу дерева}
begin
Prev := BegQ;
Curr := BegQ^.Next;
if Curr <> Nil then {Спуск по предыдущему пути до листа дерева. Выполнять контроль на достижение листа дерева нет необходимости т.к. такой контроль выполняется автоматически по концу очереди}
if (WRoot^.Left^.Inf = Curr^.Inf) then
begin
DephtFirstSearch (WRoot^.Left, Curr, EndQ); {Спуск по левой ветви узла}
{Возврат из рекурсии из левого поддерева}
if (WRoot^.Right = nil) then
RemoveQEnd (BegQ, EndQ, PrevEndQue); {При возврате из левого поддерева, если у узла нет правого сына, то узел удаляется из пути поиска}
else
PBDPPm (WRoot^.Right, BegQ, EndQ) { При возврате из левого поддерева, если у узла есть правый сын, он становится корнем поддерева, для которого определяется путь к его самому левому листу. Этот путь дописывается в конец очереди}
end
else
begin
DephtFirstSearch (WRoot^.Right, Curr, EndQ); {Спуск по правой ветви узла}
{Возврат из рекурсии из правого поддерева}
RemoveQEnd (BegQ, EndQ, PrevEndQue); {При возврате из правого поддерева узел удаляется из пути поиска}
end
else
RemoveQEnd (BegQ, EndQ, PrevEndQue); {Лист дерева из предыдущего пути поиска автоматически удаляется из дерева и начинается откат по пути поиска}
end;
end; {DephtFirstSearch}
Обращение к этой процедуре имеет вид:
begin
• • •
BeginQueue := Nil;
EndQueue := Nil;
InsertQ (BeginQueue, EndQueue, Rut); {занесение корня дерева в очередь}
DephtFirstSearch (Rut, BeginQueue, EndQueue); {Первый поиск}
• • •
DephtFirstSearch (Rut, BeginQueue, EndQueue); {Очередной поиск}
end.
