Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Программирование 2 курс / Программирование(4172,4173) / Лекции / Лекция 21.Деревья.Обход дерева.doc
Скачиваний:
86
Добавлен:
12.03.2015
Размер:
151.04 Кб
Скачать

Обход дерева в ширину

В некоторых задачах необходимо обойти дерево в определенном порядке с посещением один раз каждой вершины для выполнения некоторой операции, например поиска чего-либо.

а) Дерево б) Обход в ширину:

A A, B, C, D, E, F

B C D

в) Обход в глубину:

A, B, C, E, F, D

E F

Рис. 21.3. Обход дерева в ширину и в глубину

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

Обход дерева в ширину происходит по уровням: сначала посещается вершина уровня 0 (корень), затем вершины уровня 1, затем уровень 2 и т. д. Уровень i+1 включает сыновей вершин уровня i (рис. 21.3 а, 21.3 б; алг. 1).

Алгоритм 1. Обход дерева в ширину

Пустая Очередь <== корень;

while (Очередь != пусто)

{ Очередь ==> t; Посетить t;

for (x  сыновья(t))

Очередь <== x;

}

Обозначение: for (x  S) . . . - цикл, выполняемый для каждого элемента x из множества S ( - "принадлежит").

Трассировочная таблица обхода дерева из рис. 7.3 а:

Посещение:

A

B

C

D

E

F

Очередь=

A

B

C

D

E

F

C

D

E

F

D

F

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

Пока из очереди выбираются и посещаются все вершины уровня i, за ними в очереди расположатся все вершины уровня i+1. Первоначально в очередь помещается единственная вершина уровня 0 – корень дерева. На удивление простой и изящный алгоритм!

Подобный обход иногда называют “волновым алгоритмом”, т.к. последовательность обхода напоминает круговые волны от брошенного в воду камня.

Обход в ширину требует времени порядка O(n+m), т. к. каждая из n вершин один раз посещается, попадает в очередь и удаляется из нее. Дополнительно один раз просматривается каждая из m дуг дерева (при переборе сыновей ее начальной вершины).

Обход дерева в глубину

Деревья имеют рекурсивное строение: части дерева сами могут быть деревьями. Поэтому можно дать рекурсивное определение (корневого) дерева: дерево - либо пустое множество вершин, либо вершина (корень), связанная с конечным числом непересекающихся деревьев (называемых поддеревьями).

На рис. 21.3а, 21.3в показано дерево и последовательность его обхода в глубину по принципу: если можно – вперед (вглубь) к сыну, иначе - назад к отцу.

На основе рекурсивного определения удобно строить рекурсивные программы обработки деревьев. Примером является рекурсивный обход дерева в глубину (алг. 2).

Алгоритм 2. Рекурсивный обход в глубину дерева с корнем t

void obhod_der_gl (t)

{ if (дерево t != пусто)

{ Посетить корень t;

for (x  поддеревья(t)) /*Для каждого x из поддеревьев t */

obhod_der_gl (x); /* Обойти поддерево x */

}

} Возврат 2

Трассировочная таблица обхода дерева (рис. 16.3 а) по алгоритму 2:

Вызов: obhod_der_gl (A); /* Возврат 1 */

N вызова

1

2

1

3

4

3

5

3

1

6

1

t =

A

B

A

C

E

C

F

C

A

D

A

x =

B

C

E

F

D

Посещение:

A

B

C

E

F

D

Стек =

t, возврат

A 1

A 1

A 1

A 1

A 1

A 1

A 1

A 1

A 1

A 1

A 1

B 2

C 2

C 2

C 2

D 2

C 2

D 2

E 2

F 2

Алгоритм 2 удобен для представлений дерева с возможностью пустых ссылок на поддеревья (типа рис. 21.2в, 21.2д). Когда пустые поддеревья в явном виде отсутствуют (ссылки делаются только на непустые поддеревья, как на рис. 21.2б, или в нерегулярных сетях), удобнее алгоритм 2а, основанный на другом определении: (непустое корневое) дерево – это вершина (корень), связанная с корнями n деревьев (n ≥ 0).

Алгоритм 2а. Рекурсивный обход в глубину непустого дерева с корнем t

void obhod_der_gl (t)

{ Посетить t;

for (x  сыновья(t)) /* Для каждого x из сыновей t */

obhod_der_gl (x); /* Обойти поддерево с корнем x */

}

Рекурсивный алгоритм часто проще итеративного, но тратит больше времени и памяти на рекурсивные вызовы. Итеративный алгоритм предпочтительнее, если он не слишком сложен.

Алгоритм 3. Итеративный обход в глубину непустого дерева

Посетить корень; Пустой Стек <== корень;

while (Стек != пусто) Идея:

{ t = верх(Стек); 2

if (t имеет не посещенных сыновей, левый из них x)

{ Посетить x; Стек <== x; } /* Вглубь */

else

Стек==>; /* Назад */ 1

}

Строку /* Назад */ алгоритма 3 можно заменить на три строки /* Назад или вправо */ (см. алг. 4): если невозможно двигаться вглубь, делается попытка перейти к брату и только при его отсутствии - назад. Это несколько ускоряет обход (меньше итераций) за счет усложнения алгоритма.

Трассировочная таблица обхода дерева (рис. 21.3 а) по алгоритму 3:

t=

A

А

В

A

C

E

C

F

C

A

D

A

Посещение:

А

B

C

E

F

D

Стек=

A

A

A

A

A

A

A

A

A

A

A

B

C

C

C

C

C

D

E

F

Алгоритм 4. Итеративный обход в глубину непустого дерева

Посетить корень; Пустой Стек <== корень;

while (Стек != пусто)

{ t = верх(Стек); Идея:

if (t имеет не посещенных сыновей, левый из них x) 3

{ Посетить x; Стек <== x; } /* Вглубь */

else 2

{ Стек ==> t; /* Назад */

if (t имеет правого брата x) /* или */

{ Посетить x; Стек <== x; } /* вправо */ 1

}

}

Трассировочная таблица обхода дерева (рис. 20.3 а) по алгоритму 4:

t=

A

А

В

C

E

F

C

D

A

Посещение:

B

C

E

F

D

Стек=

A

A

A

A

A

A

A

A

B

C

C

C

C

D

E

F