If Not node.Marked Then node.PreorderPrint
Next link
End Sub
Так как эта процедура не обращается ни к одному узлу дважды, то коллекция обходимых связей не содержит циклов и образует дерево.
Если сеть является связной, то дерево будет обходить все узлы сети. Так как это дерево охватывает все узлы сети, то оно называется остовным деревом (spanning tree). На рис. 12.4 показана небольшая сеть с остовным деревом с корнем в узле A, которое изображено жирными линиями.
Можно использовать похожий подход с пометкой узлов для преобразования обхода дерева в ширину в сетевой алгоритм. Алгоритм обхода дерева начинается с помещения корневого узла в очередь. Затем первый узел удаляется из очереди, происходит обращение к узлу, и затем в конце очереди помещаются его дочерние узлы. Затем этот процесс повторяется до тех пор, пока очередь не опустеет.
======318
@Рис. 12.4. Остовное дерево
В алгоритме обхода сети нужно вначале убедиться, что узел не проверялся раньше или он уже не находится в очереди. Для этого мы помечаем каждый узел, который помещается в очередь. Сетевая версия этого алгоритма выглядит так:
Пометить первый узел (который будет корнем остовного дерева) и добавить его в конец очереди.
Повторять следующие шаги до тех пор, пока очередь не опустеет:
a) Удалить из очереди первый узел и обратиться к нему.
b) Для каждого из непомеченных соседних узлов, пометить его и добавить в конец очереди.
Следующая процедура печатает список узлов сети в порядке обхода в ширину:
Public Sub BreadthFirstPrint(root As NetworkNode)
Dim queue As New Collection
Dim node As NetworkNode
Dim neighbor As NetworkNode
Dim link As NetworkLink
' Поместить корень в очередь.
root.Marked = True
queue.Add root
' Многократно помещать верхний элемент в очередь
' пока очередь не опустеет.
Do While queue.Count > 0
' Выбрать следующий узел из очереди.
Set node = queue.Item(1)
queue.Remove 1
' Обратиться к узлу.
Print node.Id
' Добавить в очередь все непомеченные соседние узлы.
For Each link In node.Links
' Найти соседний узел.
If link.Node1 Is Me Then
Set neighbor = link.Node2
Else
Set neighbor = link.Node1
End If
' Проверить, нужно ли обращение к соседнему узлу.
If Not neighbor.Marked Then queue.Add neighbor
Next link
Loop
End Sub
Если задана сеть с ценой связей, то наименьшим остовным деревом (minimal spanning tree) называется остовное дерево, в котором суммарная цена всех связей в дереве будет наименьшей. Наименьшее остовное дерево можно использовать, чтобы связать все узлы в сети путем с наименьшей ценой.
Например, предположим, что требуется разработать телефонную сеть, которая должна соединить шесть городов. Можно проложить магистральный кабель между всеми парами городов, но это будет неоправданно дорого. Меньшую стоимость будет иметь решение, при котором города будут соединены связями, которые содержатся в наименьшем остовном дереве. На рис. 12.5 показаны шесть городов, каждые два из которых соединены магистральным кабелем. Жирными линиями нарисовано наименьшее остовное дерево.
Заметьте, что сеть может иметь несколько наименьших остовных деревьев. На рис. 12.6 показаны два изображения сети с двумя различными наименьшими остовными деревьями, которые нарисованы жирными линиями. Полная цена обоих деревьев равна 32.
@Рис. 12.5. Магистральные телефонные кабели, связывающие шесть городов
========320
@Рис. 12.6. Два различных наименьших остовных дерева для одной сети
Существует простой алгоритм поиска наименьшего остовного дерева для сети. Вначале поместим в остовное дерево любой узел. Затем найдем связь с наименьшей ценой, которая соединяет узел в дереве с узлом, который еще не помещен в дерево. Добавим эту связь и соответствующий узел в дерево. Затем эта процедура повторяется до тех пор, пока все узлы не окажутся в дереве.
Этот алгоритм похож на эвристику восхождения на холм, описанную в 8 главе. На каждом шаге оба алгоритма изменяют решение, пытаясь его максимально улучшить. Алгоритм остовного дерева на каждом шаге выбирает связь с наименьшей ценой, которая добавляет новый узел в дерево. В отличие от эвристики восхождения на холм, которая не всегда находит наилучшее решение, этот алгоритм гарантированно находит наименьшее остовное дерево.
Подобные алгоритмы, которые находят глобальный оптимум, при помощи серии локально оптимальных приближений называются поглощающими алгоритмами[RV20] (greedy algorithms). Можно представлять себе поглощающие алгоритмы как алгоритмы типа восхождения на холм, не являющиеся при этом эвристиками — они гарантированно находят наилучшее возможное решение.
Алгоритм наименьшего остовного дерева использует коллекцию для хранения списка связей, которые могут быть добавлены к остовному дереву. Вначале алгоритм помещает в этот список связи корневого узла. Затем проводится поиск связи с наименьшей ценой в этом списке. Чтобы максимально ускорить поиск, программа может использовать приоритетную очередь типа описанной в 9 главе. Или наоборот, чтобы упростить реализацию, программа может использовать для хранения списка возможных связей коллекцию.
Если узел на другом конце связи еще не находится в остовном дереве, то программа добавляет его и соответствующую связь в дерево. Затем она добавляет связи, выходящие из нового узла, в список возможных узлов.
Алгоритм использует флаг Used в классе link, чтобы определить, попадала ли эта связь ранее в список возможных связей. Если да, то она не заносится в этот список снова.
Может оказаться, что список возможных связей опустеет до того, как все узлы будут добавлены в остовное дерево. В этом случае сеть является несвязной, и не существует путь, который связывает корневой узел со всеми остальными узлами сети.
=========321
Private Sub FindSpanningTree(root As SpanNode)
Dim candidates As New Collection
Dim to_node As SpanNode
Dim link As SpanLink
Dim i As Integer
Dim best_i As Integer
Dim best_cost As Integer
Dim best_to_node As SpanNode
If root Is Nothing Then Exit Sub
' Сбросить флаг Marked для всех узлов и флаги
' Used и InSpanningTree для всех связей.
ResetSpanningTree
' Начать с корня остовного дерева.
root.Marked = True
Set best_to_node = root
Do
' Добавить связи последнего узла в список
' возможных связей.
For Each link In best_to_node.Links
If Not link.Used Then
candidates.Add link
link.Used = True
End If
Next link
' Найти самую короткую связь в списке возможных
' связей, которая ведет к узлу, которого еще нет
' в дереве.
best_i = 0
best_cost = INFINITY
i = 1
Do While i <= candidates.Count
Set link = candidates(i)
If link.Node1.Marked Then
Set to_node = link.Node2
Else
Set to_node = link.Node1
End If
If to_node.Marked Then
' Связь соединяет два узла, которые
' оба находятся в дереве.
' Удалить ее из списка возможных связей.
candidates.Remove i
Else
If link.Cost < best_cost Then
best_i = i
best_cost = link.Cost
Set best_to_node = to_node
End If
i = i + 1
End If
Loop
' Если больше не осталось связей, которые можно
' было бы добавить, то мы сделали все, что могли.
If best_i < 1 Then Exit Do
' Добавить наилучшую связь и узел на ее конце в дерево.
Set link = candidates(best_i)
link.InSpanningTree = True
candidates.Remove best_i
best_to_node.Marked = True
Loop
GotSpanningTree = True
' Перерисовать сеть.
DrawNetwork
End Sub
Этот алгоритм проверяет каждую связь не более одного раза. При проверке каждой связи, она добавляется в список возможных связей, а затем удаляется из него. Если этот список находится в приоритетной очереди на основе пирамид, то для вставки или удаления элемента из очереди потребуется время порядка O(log(N)), где — число связей в сети. В этом случае полное время выполнения алгоритма будет порядка O(N * log(N)).
Если список возможных связей находится в коллекции, как в вышеприведенном коде, то для поиска в списке связи с наименьшей ценой потребуется время порядка O(N), при этом полное время выполнения алгоритма будет порядка O(N2). Для малых N производительность будет приемлемой. Если же число связей в сети достаточно велико, то список возможных связей следует хранить в приоритетной очереди, а не в коллекции.
Программа Span использует этот алгоритм для поиска наименьшего остовного дерева. Эта программа аналогична программе NetEdit. Она позволяет загружать, редактировать и сохранять на диске файлы, представляющие сеть. Если выбрать какой‑либо узел в программе двойным щелчком мыши, то программа найдет и выведет на экран наименьшее остовное дерево с корнем в этом узле. На рис. 12.7 показано окно программы Span, в котором показано наименьшее остовное дерево с корнем в узле 9.
======322-323
@Рис. 12.7. Программа Span
Алгоритмы поиска кратчайшего маршрута, которые обсуждаются в следующих разделах, находят все кратчайшие пути из заданной точки до всех остальных точек сети, при этом предполагается, что сеть является связанной. Набор связей, используемый всеми кратчайшими маршрутами, называется деревом кратчайшего маршрута (shortest path tree).
На рис. 12.8 показано дерево, в котором дерево кратчайшего маршрута с корнем в узле A нарисовано жирной линией. Это дерево изображает кратчайший маршрут из узла A до всех остальных узлов в сети. Например, кратчайший маршрут из узла A в узел F проходит через узлы A, C, E, F.
Многие алгоритмы поиска кратчайшего маршрута начинают с пустого дерева, к которому затем добавляется по одной связи до тех пор, пока дерево не будет заполнено. Эти алгоритмы можно разбить на две категории в соответствии со способом выбора следующей связи, которая должна быть добавлена к растущему дереву кратчайшего маршрута.
Алгоритмы установки меток (label setting) всегда выбирают связь, которая гарантированно окажется частью конечного кратчайшего маршрута. Этот метод работает аналогично методу поиска наименьшего остовного дерева. Если связь добавлена в дерево, то она не будет удалена позже.
Алгоритмы коррекции меток (label correcting) добавляют связи, которые могут быть или не быть частью конечного кратчайшего маршрута. В процессе рабы алгоритма он может определить, что на место уже находящейся в дереве связи нужно поместить другую связь. В этом случае алгоритм заменяет старую связь новой и продолжает работу. Замена связи в дереве может сделать возможными пути, которые не были возможны до этого. Чтобы проверить эти пути, алгоритму приходится снова проверить пути, которые были добавлены в дерево раньше и использовали удаленную связь.