Обход вершин двоичного дерева при п-, о и в-обходе
Пример: Нахождение стягивающего дерева связного графа, используя рекурсивный обход графа в глубину (граф моделируется структурой Вирта).
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 — не дерево, то в нем (его компонентах связности) существуют циклы.
Алгоритм:
из цикла удалим некоторое ребро,
количество компонент связности не увеличится
Если остались циклы, то рассмотрим следующий цикл и удалим ребро.
Процесс продолжается до тех пор, пока не исчезнут все циклы.
Полученный подграф (является лесом) имеет столько же компонент связности, как и исходный граф G. Полученный подграф называется остовом графа G.
Для связного неориентированного графа G=(V,E) каждое дерево (V,T), где TE, будем называть стягивающим деревом (остовом, остовным деревом) графа 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. |