Академический Документы
Профессиональный Документы
Культура Документы
Из знакомых всем нам функций префиксный способ записи используется, например, для
sin(x), tg(x), f(x,y,z) и т.п.
Этот способ записи менее распространен, однако и с ним многим из нас приходилось
сталкиваться уже в школе: примером будет n! (факториал).
Иначе:
создать новую вершину дерева;
занести в нее этот символ.
Реализация
procedure infix(var p: ukaz);
var c: char;
begin
read(c);
if c = '('
then begin
new(p);
infix(p^.left);
read(p^.symbol); {'+', '-', '*', '/'}
infix(p^.right);
read(c); {')'}
end
else begin {'a'..'z','A'..'Z'}
new(p);
p^.symbol:= c;
p^.right:= nil;
p^.left:= nil
end;
end;
begin
...
infix(root);
...
end.
Построение из префиксной записи
Для простоты предположим, что правильное арифметическое выражение подается в
одной строке, без пробелов, а каждый операнд записан одной буквой. Кроме того, будем
считать, что из записи удалены все скобки: это вполне допустимо, так как операция всегда
предшествует своим операндам, следовательно, путаница в порядке выполнения
невозможна.
Алгоритм Prefix
Если не достигнут конец строки ввода, прочитать очередной символ.
Создать новую вершину дерева, записать в нее этот символ.
Если символ - операция, то:
вызвать алгоритм Prefix для левого поддерева;
вызвать алгоритм Prefix для правого поддерева.
Реализация
begin
...
prefix(root);
...
end.
Построение из постфиксной записи
По окончании работы этого алгоритма в стеке будет содержаться ровно один элемент -
указатель на корень построенного дерева.
Реализация
Обход в глубину "сверху вниз: название имеет смысл лишь в случае стандартного
расположения дерева корнем кверху.
Алгоритм PreOrder
Начать с корня дерева.
Пометить текущую вершину.
Совершить прямой обход левого поддерева.
Совершить прямой обход правого поддерева.
Замечание: Этот алгоритм может быть естественным образом распространен и на
случай произвольного корневого дерева.
Реализация
procedure preorder(p:ukaz; k:integer);
begin p^.mark:= k;
if p^.left<>nil then preorder(p^.left,k+1);
if p^.right<>nil then preorder(p^.right,k+1);
end;
begin
...
preorder(root,1); {Вызов из тела программы}
...
end.
Для простоты изложения будем считать, что граф задан матрицей смежности, которая
хранится в квадратном массиве sm. Дополнительный линейный массив mark хранит
информацию о последовательности посещения вершин:
procedure preorder_graph(v: byte);
var i: byte;
begin
k:= k+1;
mark[v]:= k; {текущей вершине v присвоен порядковый номер}
for i:= 1 to n do
if (mark[i]=0)and(sm[v,i]=1) {есть ребро из текущей вершины v
в еще не помеченную вершину i}
then preorder_graph(i);
end;
begin
...
k:= 0;
preorder_graph(start); {Вызов из тела программы}
...
end.
Обратный обход
Другие названия
Обход в глубину "снизу вверх": название имеет смысл лишь в случае стандартного
расположения дерева корнем кверху.
Алгоритм PostOrder
1. Начать с корня дерева.
begin
...
postorder(root,1); {Вызов из тела программы}
...
end.
Обратный обход произвольного связного графа
Для простоты изложения будем считать, что граф задан матрицей смежности, которая
хранится в квадратном массиве sm. Дополнительный линейный массив mark хранит
информацию о последовательности обхода вершин, а массив posesh - о фактах их
посещения:
procedure postorder_graph(v:byte);
var i: integer;
begin
posesh[v]:=1; {текущая вершина v стала посещенной}
for i:=1 to n do
if (posesh[i]=0)and(sm[v,i]=1) {есть ребро из текущей вершины v
в еще не помеченную вершину i}
then postorder_graph(i);
inc(k);
mark[v]:=k; {текущей вершине v
присвоен порядковый номер}
end;
begin
...
k:=0;
postorder_graph(start); {вызов из тела программы}
...
end.
Синтаксический обход
Другие названия
begin
...
syntorder(root,1); {Вызов из тела программы}
...
end.
Обход в ширину
Последовательность обхода
Пометить вершину 0-го уровня (корень дерева).
Пометить все вершины 1-го уровня.
Пометить все вершины 2-го уровня.
...
if x > p^.chislo
then if p^.right <> nil
then p:= p^.right
else begin new(p^.right);
p:= p^.right;
p^.chislo:= x;
p^.kol:= 1;
p^.left:= nil;
p^.right:= nil;
break
end
(* x < p^.chislo *)
else if p^.left <> nil
then p:= p^.left
else begin new(p^.left);
p:= p^.left;
p^.chislo:= x;
p^.kol:= 1;
p^.left:= nil;
p^.right:= nil;
break
end
end;
end;
Подсчет количества компонент связности
Рекурсивная процедура обхода в глубину (прямого или обратного обхода) переберет все
вершины, достижимые из начальной. Начальной вершиной для очередной компоненты
связности может стать любая вершина, еще не отнесенная ни к какой другой компоненте
связности (то есть еще не помеченная в массиве mark).
begin
...
for i:= 1 to N do mark[i]:=0;
k:= 0; {номер текущей компоненты связности}
for i:= 1 to N do
if mark[i]=0 then
begin inc(k);
step(i);
end;
...
end.
Итеративный алгоритм
Для этого алгоритма удобно, чтобы граф был представлен списком ребер.
Массив mark, как и прежде, будет хранить номера компонент связностей, к которым
принадлежат помеченные вершины графа.
Алгоритм КомпСвяз-Итер
В худшем случае (при полном графе) рекурсивный алгоритм, перебирая все возможные
ребра, будет вынужден вызвать основную процедуру (N-1)! раз. Велика вероятность, что
при достаточно большом N произойдет переполнение оперативной памяти, которое
вызовет аварийную остановку программы. Кроме того, размеры квадратной матрицы
смежности дают сильное ограничение на возможное количество вершин графа: не более
250 (см. лекцию 3).
Итеративный же алгоритм переберет все ребра графа, которых может быть не более чем
N*(N+1)/2. В половине этих случаев возможна ситуация объединения двух компонент
связности в одну, для чего потребуется еще N операций. Следовательно, общая сложность
алгоритма может быть приблизительно оценена значением N3/8. Возможное количество
вершин графа ограничено только максимальным размером линейного массива (32 000).
Нахождение минимального каркаса
begin
...
for i:= 1 to N do mark[i]:= 0;
min:= MaxLongInt;
for i:= 1 to N do
begin mark[i]:=1;
step(i,1,0);
mark[i]:=0;
end;
writeln(min);
...
end.
Для того чтобы помимо суммарного веса каркаса алгоритм также запоминал
включенные в каркас ребра, необходимо добавить дополнительный квадратный массив, в
котором будут храниться пометки включения ребер в каркас.
Итеративный алгоритм
Алгоритм Краскала
Упорядочить все ребра графа по возрастанию их весов.
Применить алгоритм КомпСвяз-Итер (см. пункт "Подсчет количества компонент
связности").
Замечание: Выполнение алгоритма Краскала можно завершить сразу же, как только в
каркас будет добавлено (N-1)-е ребро (поскольку в дереве с N вершинами должно быть
ровно N-1 ребро).
Реализация
Совершить обход графа в глубину, при каждом "шаге вперед" прибавляя длину ребра к
длине текущего пути, при каждом возврате - отнимая длину этого ребра от длины
текущего пути. При движении "вперед" пометки посещенности вершин ставятся, при
"откате" - снимаются. По достижении выделенной вершины t производится сравнение
длины текущего пути с ранее найденным минимумом.
Реализация
Пусть граф задан матрицей смежности sm, а массив mark хранит информацию о
посещениях вершин. Напомним, что уменьшение длины пути "на возврате" совершается
рекурсией автоматически, поскольку в ее заголовке использован параметр-значение, а вот
аналогичное обнуление соответствующих позиций массива mark приходится делать
вручную, поскольку задавать массив параметром-значением чересчур накладно:
procedure rasst(v: byte; r: longint);
var i: byte;
begin
if v = t
then if r< min then min:= r
else
else for i:= 1 to N do
if (mark[i]=0)and(sm[v,i]<>0)
then begin mark[i]:=1;
rasst(i,r+sm[v,i]);
mark[i]:=0
end
end;
begin
...
for i:= 1 to N do mark[i]:= 0;
min:= MaxLongInt;
mark[s]:= 1;
rasst(s,0);
mark[s]:= 0;
...
end.
Итеративный алгоритм
Линейный массив dist будет хранить длины текущих путей от вершины s до всех
остальных вершин. В начале этот массив будет инициирован числами MaxLongInt,
символизирующими "бесконечность". По окончании работы алгоритма в этом массиве
останутся только минимальные значения длин путей, которые и являются расстояниями.
Еще один линейный массив done потребуется нам для того, чтобы хранить информацию
о том, найден ли уже минимальный путь (он же расстояние) до соответствующей
вершины и можно ли исключить эту вершину из дальнейшего рассмотрения.
Отметим особо, что на каждом шаге Алгоритм Дейкстры находит длину кратчайшего
пути до очередной вершины графа. Именно поэтому достаточно сделать ровно N-1
итераций.
Алгоритм Дейкстры
1. Расстояние от s до s, конечно же, равно 0. Кроме того, это расстояние уже никогда не
сможет стать меньше - ведь веса всех ребер графа у нас положительны. Таким образом:
dist[s]:= 0; done[s]:= true; last:= s;
Мы надеемся, что функцию поиска меньшего из двух целых чисел min, использованную
в тексте программы, читатели смогут написать самостоятельно.
dist[s]:= 0;
done[s]:= true;
last:= s;
for i:= 1 to N-1 do
begin
for x:= 1 to N do
if (sm[last,x]<>0)and(not done[x])
then dist[x]:= min(dist[x],dist[last]+ sm[last,x]);
min_dist:= MaxLongInt;
for x:= 1 to N do
if (not done[x])and(min_dist>dist[x])
then begin min_dist:= dist[x];
last:= x;
end;
done[last]:= true;
end.