- •Содержание
- •Глава 5. Алгоритмы сортировок 52
- •Глава 6. Алгоритмы поиска 63
- •Глава 1. Основные операции при работе с деревьями
- •1.1. Определение глубины дерева
- •1.2. Операции поиска и включения элемента в дерево
- •1.3. Оптимизация поиска в дереве
- •1.3.1. Обходы дерева с нумерацией вершин
- •1.4. Поиск и включение в двоичное дерево
- •Контрольные вопросы
- •Задания для практической работы
- •Глава 2. Сбалансированные двоичные деревья
- •2.1. Преобразование двоичного дерева в лозу.
- •2.2. Преобразование лозы в сбалансированное двоичное дерево.
- •Контрольные вопросы
- •Задания для практической работы
- •Глава 3. Жадные алгоритмы
- •3.1. Понятие жадного алгоритма
- •3.2. Алгоритм Хаффмана
- •Контрольные вопросы
- •Задания для практической работы
- •Глава 4. Графы
- •4.1. Алгоритмы представления графа
- •4.1.1. Представление графа в виде массива
- •4.1.2. Представление графа в виде матрицы смежности
- •4.1.3. Представление графа в виде связанного списка
- •4.1.4. Представление графа в виде списка дуг
- •4.1.5. Преобразования структур графа
- •4.2. Обходы в графах
- •4.3. Определение путей и контуров Эйлера
- •4.4. Поиск кратчайших путей
- •4.4.1. Алгоритм э. Дейкстры.
- •4.4.2. Алгоритм Флойда — Уоршалла
- •4.5. Определение остовных деревьев
- •4.5.1. Алгоритм Крускала
- •4.5.2. Алгоритм Прима
- •Контрольные вопросы
- •Определение путей и контуров Эйлера
- •Задания для практической работы
- •Глава 5. Алгоритмы сортировок
- •5.1. Сортировка выбором
- •5.2. Сортировка вставками
- •5.3. Пузырьковая сортировка
- •5.4. Быстрая сортировка
- •5.5. Сортировка слиянием
- •5.6. Пирамидальная сортировка
- •Контрольные вопросы
- •Задания для практической работы
- •Глава 6. Алгоритмы поиска
- •6.1. Последовательный поиск
- •6.2. Двоичный поиск
- •6.3. Работа со словарем. Иоиск и вставка. Хеширование.
- •6.4. Поиск подстроки в строке
- •6.4.1. Алгоритм прямого поиска подстроки в строке
- •Контрольные вопросы
- •Задания для практической работы
- •Литература
4.5.2. Алгоритм Прима
В этом алгоритме построение остовного дерева начинается с одной вершины, к которой затем добавляются ребра таким образом, чтобы каждое новое ребро одним своим концом опиралось в уже построенную часть дерева, а другой конец лежал бы в множестве еще не присоединенных к дереву вершин. Из всех таких ребер на каждом шаге выбирается ребро с наименьшим весом. Для того чтобы выбор ребра был бы наиболее эффективен, так же, как и в алгоритме Дейкстры, в алгоритме Прима используется промежуточная структура данных — массив, элементы которого содержат информацию о расстоянии от каждой из вершин до уже построенной части остовного дерева. Таким образом, с помощью однократного просмотра такого массива всегда можно выбрать ребро минимальной длины.
Будем считать, что вершины, не соединенные ребрами с уже построенной частью остовного дерева, находятся от этой части на бесконечно большом расстоянии. После того как очередная вершина будет выбрана и присоединена к остовному дереву, все инцидентные ей ребра просматриваются, чтобы скорректировать информацию о расстояниях. Эта часть алгоритма также очень похожа на соответствующую коррекцию массива расстоянии из алгоритма Дейкстры. Если оказывается, что очередная выбранная вершина находится на бесконечно большом расстоянии от уже построенной части дерева, то это означает, что завершено построение дерева для одной компоненты связности графа и, значит, одного остовного дерева для графа построить невозможно (в этом последнем случае стоит говорить об остовном лесе).
Если для графа, изображенного на рис. 10 а, начать поиск минимального остовного дерева с вершины 0, то к дереву будут последовательно присоединяться ребра 0—3 (длиной 1), 0—1 (2), 0—6 (2), 1—4 (3), 4—7 (4), вершина 2 (+∞), 2—5(1), 2—8 (2).
На рис. 11 показана последовательность построения минимального остовного дерева для графа, изображенного на рис. 10 а.
Реализация алгоритма Прима показана в листинге в виде определения метода minSkeletonPrim, который так же, как и метод minSkeleton, в качестве аргумента получает выходной поток для печати информации о найденных ребрах минимального остовного дерева, а в качестве результата выдает суммарный вес полученного остовного дерева.
Листинг : Алгоритм Прима нахождения минимального остовного дерева
double ListGraph::minSkeletonPrim(
// Выходной поток для вывода результирующей информации:
std::ostream & out,
// Нагрузка на ребра графа:
const GraphWeight & gw) {
// Множество непройденных вершин (сначала - все вершины)
Set notPassed(0, vertexNumber-1);
notPassed.addScale(0, vertexNumber-1);
// Массив расстояний от вершин до уже построенной части
double *dist - new double[vertexNumber];
// Массив направлений от новых вершин к уже построенной части
double *ends = new double[vertexNumber];
// Инициализация массивов расстояний и направлений
for (int i = 0; i < vertexNumber; i++) {
dist[i] = INFINITY;
ends[i] = -1;
}
// Суммарный вес построенной части дерева
double sumWeight =0;
// Основной цикл поиска новых вершин
while (!notPassed.empty()) {
// Поиск ближайшей вершины
double minDist = INFINITY;
Iterator<int> *iVertices = notPassed.iterator();
int minVertex = **iVertices;
while (iVertices->hasMoreElements()) {
int nextVertex = **iVertices;
if (dist[nextVertex] < minDist) {
minDist = dist[nextVertex];
minVertex = nextVertex;
} ++*iVertices; }
delete iVertices;
if (dist[minVertex] < INFINITY) {
// Присоединяем очередное ребро
out << "(" << ends[minVertex] << "," << minVertex <<”);”;
sumWeight += minDist;
} notPassed -= minVertex;
// Новая вершина присоединена;
// корректируем информацию о расстояниях
Iterator<int> *neighbors = graph[minVertex].iterator();
while (neighbors->hasMoreElements()) {
int next = **neighbors;
if (notPassed.has(next)&& gw.arcLength(minVertex, next) < dist[next]) {
dist[next] = gw.arcLength(minVertex, next);
ends[next] = minVertex;
} ++*neighbors; } delete neighbors; } return sumWeight; }
Рис. 11. Этапы построения остовного дерева согласно алгоритму Прима
Если применить метод minSkeietonPrim к графу, изображенному на рис. 10 а
cout « testGraph.minSkeleton(cout, arcsWeight);
то, разумеется, результат будет примерно тем же самым, что и при применении метода minskeieton, реализующего жадный алгоритм:
(0,3); (0,1); (0,6); (1,4); (4,7); (2,5); (2,8); 15
Алгоритмы имеют разную производительность на различных графах. Скорость работы алгоритма Крускала зависит, прежде всего, от количества ребер в графе и слабо зависит от количества вершин. Напротив, скорость работы алгоритма Прима определяется количеством вершин и слабо зависит от числа ребер. Следовательно, алгоритм Крускала следует применять в случае неплотных или разреженных графов, у которых количество ребер мало по сравнению с количеством ребер у полного графа. Если известно, что ребер в графе достаточно много, то для поиска минимального остовного дерева лучше применять алгоритм Прима.