Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
DO_Lektsia_4.doc
Скачиваний:
1
Добавлен:
24.11.2019
Размер:
344.58 Кб
Скачать

Обход вершин двоичного дерева при п-, о и в-обходе

Пример: Нахождение стягивающего дерева связного графа, используя рекурсивный обход графа в глубину (граф моделируется структурой Вирта).

PROGRAM O_s_t_o_v_G_r_a_p_h;

type Lref = ^Leader; { Тип: указатель на заголовочный узел }

Tref = ^Trailer; { Тип: указатель на дуговой узел }

{ Описание типа заголовочного узла }

Leader=Record

Key : Integer; { Имя заголовочного узла }

Count: Integer; { Количество предшественников }

Flag : Boolean; { Флаг посещения узла при обходе }

Trail: Tref; { Указатель на список смежности }

Next : Lref { Указатель на следующий узел в }

{ списке заголовочных узлов }

end;

{ Описание типа дугового узла }

Trailer = Record

Id : Lref;

Next: Tref

end;

var Head: Lref; { Указатель на голову списка }

{ заголовочных узлов }

Tail: Lref; { Указатель на фиктивный элемент }

{ в конце списка заголовочных узлов }

t : Lref; { Рабочий указатель для перемещения }

{ по списку заголовочных звеньев }

{ ---------------------------------------------------------- }

FUNCTION S_e_a_r_c_h_G_r_a_p_h (w: Integer; Head: Lref): Lref;

{ Функция возвращает указатель на заголовочный узел с ключом }

{ w в графе, заданном структурой Вирта с указателем Head }

var h: Lref;

BEGIN

h:=Head; Tail^.Key:=w;

While h^.Key<>w do h:=h^.Next;

If h=Tail

{ В списке заголовочных узлов нет узла с ключом w }

then begin

New (Tail); h^.Count:=0;

h^.Trail:=Nil; h^.Next:=Tail

end;

S_e_a_r_c_h_G_r_a_p_h:=h

END;

{ -------------------------------------------- }

PROCEDURE M_a_k_e_G_r_a_p_h (var Head: Lref);

{ Процедура возвращает указатель Head на структуру }

{ Вирта, соответствующую ориентированному графу }

var x,y: Integer;

p,q: Lref; { Рабочие указатели }

t,r: Tref; { Рабочие указатели }

Res: Boolean; { Флаг наличия дуги }

BEGIN

Write ('Вводите начальную вершину дуги: '); Read (x);

While x<>0 do

begin

Write ('Вводите конечную вершину дуги: ');

ReadLn (y);

{ Определим, существует ли в графе дуга (x,y)? }

p:=S_e_a_r_c_h_G_r_a_p_h (x,Head);

q:=S_e_a_r_c_h_G_r_a_p_h (y,Head);

r:=p^.Trail; Res:=FALSE;

While (r<>Nil) AND (NOT Res) do

If r^.Id=q then Res:=TRUE else r:=r^.Next;

If (NOT Res)

{Если дуга не существует, то поместим ее в граф}

then begin

New (t); t^.Id:=q; t^.Next:=p^.Trail;

p^.Trail:=t; q^.Count:=q^.Count+1

end;

Write ('Вводите начальную вершину дуги: '); Read (x)

end;

END;

{ ------------------------------- }

PROCEDURE Ostov_Depth (r: Lref);

{ Рекурсивный обход графа в глубину, соединенный }

{ с нахождением ребра остовного дерева. }

{ r - указатель на структуру Вирта }

var t: Tref;

s: Lref;

BEGIN

s:=r; t:=r^.Trail; r^.Flag:=FALSE;

While t<>Nil do

begin

If t^.Id^.Flag

then begin

Write ('(',s^.Key,',',t^.Id^.Key,') ');

Ostov_Depth (t^.Id)

end;

t:=t^.Next

end

END;

{ --- }

BEGIN

{ Инициализация списка заголовочных узлов }

New (Head); Tail:=Head;

{ Построение графа }

M_a_k_e_G_r_a_p_h (Head);

{ Построение стягивающего дерева (остова) }

Write ('Остов: '); t:=Head;

While t<>Tail do

begin t^.Flag:=TRUE; t:=t^.Next end;

Ostov_Depth (Head); WriteLn

END.

Остов графа

Пусть G — произвольный (n, m)-граф с k компонентами связности.

Если G — не дерево, то в нем (его компонентах связности) существуют циклы.

Алгоритм:

  1. из цикла удалим некоторое ребро,

  2. количество компонент связности не увеличится

  3. Если остались циклы, то рассмотрим следующий цикл и удалим ребро.

  4. Процесс продолжается до тех пор, пока не исчезнут все циклы.

Полученный подграф (является лесом) имеет столько же компонент связности, как и исходный граф G. Полученный подграф называется остовом графа G.

Для связного неориентированного графа G=(V,E) каждое дерево (V,T), где TE, будем называть стягивающим деревом (остовом, остовным деревом) графа G. Ребра такого дерева будем называть ветвями, а все остальные ребра графа будем называть хордами. Максимальный подграф без циклов произвольного графа G называется стягивающим лесом графа G.

Определение: Остовым (покрывающим) деревом графа G называется любое дерево T, являющееся суграфом графа G.

Связный граф имеет единственное покрывающее дерево тогда и только тогда, когда он сам является деревом.

Остов графа G является минимальным связным суграфом графа G, то есть содержит минимальное количество ребер, связывающих вершины графа.

Суграф T графа G называется каркасом этого графа, если является лесом, который на любом компоненте связности графа G образует дерево.

Теорема: Число ребер произвольного графа G с n вершинами и m ребрами, имеющего k компонент связности, которое необходимо удалить для получения остова, не зависит от последовательности их удаления и равно m-n+k.

Доказательство:

Пусть Hi, i -компоненты связности графа G, и пусть Hi-(ni,mi)-графы.

После удаления ребер из циклов компоненты Hi, получаем дерево, которое имеет ni−1 ребер.

Значит, из Hi необходимо удалить ребер.

Суммируя по всем компонентам, находим, что для получения остова из графа G необходимо удалить m-n+k ребер.

Определения

Число ν(G)=m-n+k называется цикломатическим числом (циклическим рангом) графа G.

Число ν*(G)=n-k, то есть число ребер, входящих в любой остов графа G называется его коциклическим рангом ν(G)+ν*(G)=m. 

Корневым деревом называется дерево T=(V,E) с выделенной вершиной ν, принадлежащая V, при этом вершина ν называется корнем дерева. Для каждой вершины корневого дерева её уровень — это длина цепи от корня до этой вершины.

Следствие 1.

Граф G является лесом тогда и только тогда, когда ν(G)=0.

Следствие 2.

Граф G имеет единственный цикл тогда и только тогда, когда ν(G)=1.

Следствие 3.

Граф G, в котором число ребер не меньше, чем число вершин, имеет цикл.

Теорема. Орграф сильно связен, если в нем существует остовной циклический маршрут.

Задачу нахождения стягивающих деревьев можно понимать как поиск экономных путей, обеспечивающих связь между точками заданного множества без ненужного дублирования. Поэтому она применима во многих областях, например, при исследовании электрических цепей или при анализе схем программ.

Процедуры обхода графа в глубину и в ширину можно простым способом использовать для нахождения остовов. В обоих случаях достижение новой вершины графа u из вершины v вызывает "включение" в остовное дерево ребра {u,v}.

Сравните две приведенные ниже процедуры:

PROCEDURE Depth_First_Search (r: Lref);

{ Рекурсивный обход графа в глубину }

{ r - указатель на структуру Вирта }

var t: Tref;

BEGIN

t:=r^.Trail; Write (r^.Key,' '); r^.Flag:=FALSE;

While t<>Nil do

begin

If t^.Id^.Flag then Depth_First_Search (t^.Id);

t:=t^.Next

end

END;

{ ------------------------------- }

PROCEDURE Ostov_Depth (r: Lref);

{ Рекурсивный обход графа в глубину, соединенный }

{ с нахождением ребра остовного дерева }

{ r - указатель на структуру Вирта }

var t: Tref;

s: Lref;

BEGIN

s:=r; t:=r^.Trail; r^.Flag:=FALSE;

While t<>Nil do

begin

If t^.Id^.Flag

then begin

Write ('(',s^.Key,',',t^.Id^.Key,') ');

Ostov_Depth (t^.Id)

end;

t:=t^.Next

end

END;

Для доказательства того, что процедура Ostov_Depth корректно строит остов связного графа, достаточно отметить следующие три факта [Липский,1988,с.95]: 1) в момент выдачи на экран новой ветви {s^.Key,t^.Id^.Key} оператором Write ('(',s^.Key,',',t^.Id^.Key,') ') в дереве уже существует путь из вершины Head^.Key в s^.Key, что легко доказывается по индукции (таким образом, процедура строит связный граф); 2) каждая новая ветвь, добавляемая к множеству T (конечно, в про-цедуре множества нет: его роль играет экран дисплея!), соединяет уже рассмотренную вершину s^.Key c новой вершиной t^.Id^.Key. Отсюда следует, что построенный граф не содержит циклов: действительно, последняя ветвь, "замыкающая" цикл, должна была бы соединить две уже рассмотренные вершины; 3) из свойства процедуры Depth_First_Search () поиска в глубину следует, что процедура Ostov_Depth () просматривает все вершины связного графа. Следовательно, граф (V,T), построенный процедурой Ostov_Depth, есть стягивающее дерево графа G.

Вычислительная сложность алгоритма есть, очевидно, O(n+m), т.е. того же порядка, что и поиск в глубину (здесь: n - количество вершин, а m - количество ребер графа).

Пример 2. Реализация алгоритма Краскала.

PROGRAM K_r_u_s_k_a_l;

type SubInt = 1..255;

Ref = ^Uzel;

Uzel = Record

X : SubInt; { Начало дуги }

Y : SubInt; { Конец дуги }

Pay : Integer; { Стоимость дуги }

Left : Ref; { Указатель на левого сына }

Right: Ref { Указатель на правого сына }

End;

{ ----------------------- }

TipElement = Set of SubInt;

svqz = ^zveno;

zveno = Record

Element: TipElement;

Sled : svqz

end;

var Root : Ref;

UkUzel : Ref; { Рабочий указатель на узел дерева }

UkUzel1: Ref; { Рабочий указатель на узел дерева }

A,B,C : Integer;

UkStr : svqz; { Указатель на список }

Res1,Res2: svqz;

T1,T2 : SubInt;

B1,B2 : Boolean;

{ ----------------------------------------------- }

PROCEDURE P_o_s_t_r_o_e_n_i_e (var UkStr: svqz);

{ Построение линейного однонаправленного списка }

{ с заглавным звеном, содержащего вершины графа }

var UkZv: svqz;

el : Integer;

BEGIN

New (UkStr); UkZv:=UkStr; UkZv^.Sled:=Nil;

Write ('Вводите вершины графа: '); Read (el);

While el<>0 do

begin

New (UkZv^.Sled); UkZv:=UkZv^.Sled;

UkZv^.Element:=[el]; UkZv^.Sled:=Nil; Read (el)

end

END;

{ ------------------------------------------ }

FUNCTION P_o_i_s_k (st: svqz; MENT: SubInt;

var Res: svqz): Boolean;

var q: svqz;

BEGIN

P_o_i_s_k:=FALSE; Res:=Nil; q:=st^.Sled;

While (q<>Nil) AND (Res=Nil) do

begin

If MENT IN q^.Element

then begin P_o_i_s_k:=TRUE; Res:=q end

else q:=q^.Sled

end

END;

{ ------------------------------------ }

PROCEDURE U_d_a_l_e_n_i_e (zv: svqz);

{ Удаление из однонаправленного списка с заглавным }

{ звеном элемента, на который указывает указатель zv }

var Z : svqz; { "Старый" указатель }

UkZv1: svqz; { "Новый" указатель }

BEGIN

If zv^.Sled<>Nil

then zv^:=zv^.Sled^

else begin

Z:=UkStr; UkZv1:=UkStr^.Sled;

While UkZv1<>zv do

begin Z:=UkZv1; UkZv1:=UkZv1^.Sled end;

Z^.Sled:=Nil; Dispose (UkZv1)

end

END;

{ --------------------------------------------------------- }

PROCEDURE S_e_a_r_c_h (A: Integer; B: Integer; C: Integer;

var p: Ref);

{ Добавление вершины, содержащей поля A,B,C, в дерево p }

BEGIN

If p=Nil

then begin

New (p); p^.X:=A; p^.Y:=B; p^.Pay:=C;

p^.Left:=Nil; p^.Right:=Nil

end

else If C<=p^.Pay

then S_e_a_r_c_h (A,B,C,p^.Left)

else If C>p^.Pay

then S_e_a_r_c_h (A,B,C,p^.Right)

END;

{ --- }

BEGIN

{ Построение дерева, содержащего все ребра графа }

Root:=Nil; { Вначале дерево пусто }

WriteLn ('Для каждого ребра вводите начальную, затем ко-');

WriteLn ('нечную вершины и стоимость соединяющего ребра:');

Read (A,B,C);

While A<>0 do

begin S_e_a_r_c_h (A,B,C,Root); Read (A,B,C) end;

{ Построение первоначальных множеств вершин графа }

P_o_s_t_r_o_e_n_i_e (UkStr);

Write ('Ребра остовного дерева наименьшей стоимости: ');

While UkStr^.Sled^.Sled<>Nil do

begin

UkUzel1:=Root; { "Отстающий" указатель }

UkUzel:=Root^.Left; { "Опережающий" указатель }

If UkUzel=Nil

then begin

{ Выбор в дереве ребра наименьшей стоимости и ... }

T1:=Root^.X; T2:=Root^.Y;

{ ... удаление этого ребра из дерева }

Root:=Root^.Right; Dispose (UkUzel1)

end

else begin

{ Выбор в дереве ребра наименьшей стоимости и ... }

While UkUzel^.Left<>Nil do

begin

UkUzel1:=UkUzel1^.Left; UkUzel:=UkUzel^.Left

end;

T1:=UkUzel^.X; T2:=UkUzel^.Y;

{ ... удаление этого ребра из дерева }

UkUzel1^.Left := UkUzel^.Right; Dispose (UkUzel)

end;

{ "Если v и w принадлежат различным }

{ множествам W1 и W2 из VS ..." }

B1:=P_o_i_s_k (UkStr,T1,Res1);

B2:=P_o_i_s_k (UkStr,T2,Res2);

If Res1<>Res2

then begin

Res1^.Element:=Res1^.Element+Res2^.Element;

U_d_a_l_e_n_i_e (Res2);

Write ('(',T1,' ',T2,') ')

end

end

END.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]