- •Лекция 1. Введение
- •Лекция 2. Графы и деревья
- •Лекция 3. Машинное представление графов
- •Лекция 4. Поиск в глубину в графе
- •Лекция 5. Поиск в ширину в графе
- •Лекция 6. Стягивающие деревья (кАркасы)
- •Лекция 7. Фундаментальное множество циклов графа
- •Лекция 8. Нахождение компонент двуcвязности (блоков) графа
- •Лекция 9. Эйлеровы пути
- •Лекция 10. Классификация задач по степени сложности
- •Лекция 11. Алгоритмы с возвратом
- •Лекция 12. Модификации алгоритма с возвратом. Задачи на максимум и минимум
- •Лекция 13. Кратчайшие пути
- •Лекция 14. Алгоритм форда-беллмана
- •Лекция 15. Алгоритм дейкстры
- •Лекция 16. Пути в бесконтурном графе
- •Лекция 17. Алгоритм флойда
- •Лекция 18. Кратчайшие пути с фиксированными платежами
- •Лекция 19. Первые k кратчайших путей
- •Приложение 1. Список np-полных задач
- •9. Множество представителей
- •10. Упорядочение внутри интервалов
- •11. Составление учебного расписания
- •Библиографический список
Лекция 3. Машинное представление графов
Лучшее решение — списки инцидентности.
Самый приятный и полезный для человека способ представить граф — изобразить его на плоскости в виде точек и соединяющих их линий. Очевидно, что для ЭВМ он совершенно бесполезен… Поскольку выбор для этой цели определенной структуры данных очень сильно влияет на эффективность алгоритма, рассмотрим несколько способов машинного представления графов.
МАТРИЦЫ СМЕЖНОСТИ — самый худший с алгоритмической точки зрения способ. Это матрицы n*m: в строках перечисляются вершины, в столбцах ребра.
МАТРИЦА СМЕЖНОСТИ
|
1-2 |
1-3 |
1-4 |
2-3 |
3-4 |
1 |
1 |
1 |
1 |
0 |
0 |
2 |
1 |
0 |
0 |
1 |
0 |
3 |
0 |
1 |
0 |
1 |
1 |
4 |
0 |
0 |
1 |
0 |
1 |
Рис. 3
Этот способ требует n*m ячеек памяти, причем большинство из ячеек заняты нулями. Ответ на элементарный вопрос: «к каким вершинам ведут ребра из x?», требует перебора всех столбцов, т.е. m шагов.
МАТРИЦА ИНЦИДЕНЦИЙ имеет размер n*n. Если существует ребро x—y, то в клетках с координатами (x, y) и (y, x) ставится 1.
|
1 |
2 |
3 |
4 |
1 |
0 |
1 |
1 |
1 |
2 |
1 |
0 |
1 |
0 |
3 |
1 |
1 |
0 |
1 |
4 |
1 |
0 |
1 |
0 |
МАТРИЦА ИНЦИДЕНЦИЙ графа G (рис. 3)
Независимо от числа ребер требуется объем памяти n, но за один шаг можно ответить на вопрос: «существует ли ребро из x в y?».
Граф можно представить в виде списка пар, соответствующих ребрам:
1 |
1 |
1 |
2 |
3 |
2 |
3 |
4 |
3 |
4 |
СПИСОК РЕБЕР графа G (рис. 3)
Объем памяти равен 2m, но попробуйте ответить на вопрос: «к каким вершинам ведут ребра из вершины 4?».
Лучшее решение — структура, которую называют СПИСKИ ИНЦИДЕНТНОСТИ.
Каждой вершине v V ставим в соответствие список вершин, соединенных ребрами с вершиной v. Для неориентированного графа каждое ребро (v—u) будет представлено дважды: в списке для v и в списке для u. Начало каждого списка будет храниться в одномерном массиве НАЧАЛО длины n. По вершине v находим НАЧАЛО[v] — в этой ячейке хранится указатель на начало списка вершин, соединенных с v. Каждый элемент такого списка — запись, состоящая из двух полей:
Вершина |
указатель на следующую запись |
-
UZL
NEXT
Если вершина 1 соединена с 2 и 3, то ее список выглядит так:
Рис. 4
Весь список для вершины v будем называть ЗАПИСЬ[v]. Цикл, перебирающий все элементы этого списка, будем записывать «for u ЗАПИСЬ[v]»
Рис. 5
Число ячеек памяти,необходимое для представления графа списками инцидентности, имеет порядок O(n+m).
Опишем списки инцидентности на языке ПАСКАЛЬ во фрагменте 1:
ФРАГМЕНТ 1
CONST N=4; M=5; TYPE PT=^ZT; ZT= record Uzl : integer; Next : PT end; VAR START : array [1..N] of PT; u,v,uzel : integer; reb, e : integer; p, p1 : PT; |
for reb:=1 to M do begin write (' Начало и конец', reb, 'ребра'); read(u,v); {push — процедура вталкивания в стек} push(START[u], v); push(START[v], u); end; {Списки формируются по принципу стека, т.к. порядок элементов внутри списка не важен.} |
Создадим ЗАПИСЬ[1] для списков инцидентности рис. 5.
Указатели на начало каждой цепи объеденины в массив НАЧАЛО и инициализируются перед созданием цепи.
Рис. 6
Записи для ребер, выходящих из узла (1), создаются ДИНАМИЧЕСКИ. Рассмотрим подробно этот механизм.
Рис. 7
Рис. 8
Очевидно, что запись, подключенная первой, оказывается в конце цепи, а последняя окажется в начале. Программисты такую конструкцию называют СТЕК.
Рис. 9
?Вопрос 1. Опишите механизм выталкивания из стека, т.е.удаления из начала цепи последней подключенной записи.
Ясно, что формирование всех цепей списков инцидентности нужно делать в цикле. Сделаем это во фрагменте 2.
Ответы.
Ответ1. Набор стандартных подпрограмм для работы с динамическими списками включает в себя процедуру вталкивания в стек push, функцию выталкивания из стека pop и функцию выталкивания из очереди poptail.
ФРАГМЕНТ 2
Procedure Push(Var p: PT;k:integer); {p — указатель на начало стека} var p1 : PT; begin new(p1); {новая пустая запись} p1^.uzl:=k; {заполнение ее данными} p1^.next:=p; {помещение ее перед верхушкой стека} p:=p1 {указатель начала указывает на новую запись} end; |
Function Pop(Var p : PT):integer; {для непустого стека} {p — указатель на начало стека} var p1 : PT; begin pop:=p^.uzl; {вытолкнем верхушку стека} p1:=p; {укажем p1 обреченную запись} p:=p^.next; {верхушка стека — следующая запись} dispose(p1) {освобождение памяти} end; |
Function Poptail(Var p : PT):integer; {для непустой очереди} {p — указатель на начало очереди} begin if p^.next=nil then {очередь состоит ровно из одного элемента} poptail:=pop(p) else {будем сдвигать указатель вправо, пока не найдем последний элемент списка} poptail:=poptail(p^.next) {рекурсия} end; |
