Выполнила Кулагина С.О. ОПРЕДЕЛЕНИЯ Будем рассматривать задачу в терминах ориентированных графов. Граф – конечное множество V, называемое множеством вершин, на котором задано симметричное антирефлексивное отношение R и выделено множество E двухэлементных подмножеств V, определяемое как {a,b}€ E тогда и только тогда, когда (a,b) € R и a ≠ b, и (b,a) не входит в Е. Вес – параметр дуги, длина дуги. Путь – сумма длин входящих в него дуг. Полустепенью исхода(захода) называется число исходящих (входящих) дуг. Две вершины – смежные, если они соединены дугой. Длина пути(во взвешенном графе) – ∑ весов дуг графа. Цикл – путь, который начинается и заканчивается в одной той же вершине и содержит как минимум 1 дугу. Ациклическим считается граф, не содержащий ни одного цикла. В разреженном графе m<<n2, где m – число дуг, n – число вершин. Иначе , m=c*n, где с – константа, с≤4. В насыщенном графе m≈n2 или m ≈ c*n2 , где с – константа, с<1. СТРУКТУРА БИБЛИОТЕКИ STL Библиотека содержит пять основных видов компонентов: * алгоритм (algorithm): определяет вычислительную процедуру. * контейнер (container): управляет набором объектов в памяти. * итератор (iterator): обеспечивает для алгоритма средство доступа к содержимому контейнера. * функциональный объект (function object): инкапсулирует функцию в объекте для использования другими компонентами. * адаптер (adaptor): адаптирует компонент для обеспечения различного интерфейса. 3 ВОЗМОЖНЫХ ПРЕДСТАВЛЕНИЯ • • Матрица смежности(vector<vector<int> >) – для насыщенного графа Структура смежности(vector< list<int> > ) – • Для разреженного графа Представление в форме map – Для представления при входе ИСПОЛЬЗУЕМЫЕ КЛАССЫ БИБЛИОТЕКИ. Для создания векторовструктур, аналогичных массивам, но имеющих большую функциональ ность Для вводавывода Для представления графа в виде матрицы Для создания строк Для создания списков Для определения функторов Для создания очередей(на пример, для хранения вершин) ПРЕДСТАВЛЕНИЕ ГРАФА: ВАРИАНТ1 Если граф взвешенный: Представление графа в виде массива дуг с помощью STL map. Дуге(из 2-х вершин) сопоставляется её вес. Пример: представление визуальное и в форме map Пример: map<string, list<int> >::iterator it; // Создание итератора для перехода по map Int i =10; it->first.push_back(i);// первая вершина Используем это представление для упорядочения графа на входе в программу ПРЕДСТАВЛЕНИЕ ГРАФА: ВАРИАНТ 2 Если граф насыщенный: Используется двумерная матрица смежности A. Для дуги (vi,wj) с весом ci,j A[i][j] = ci,j Несуществующие дуги – значение «бесконечность» В STL представляется как vector<vector<int> > ПРЕДСТАВЛЕНИЕ ГРАФА : ВАРИАНТ 3 Если граф разреженный и связный: № вершины Список смежных вершин Используется структура смежности: массив списков. Вес vector< list<int> > соответствующей дуги vector<list<int>>::iterator it = new interator; Хранится вектор вершин, которым сопоставлены списки номеров вершин, смежных с ними. Будем представлять граф именно так. ОБХОД В ШИРИНУ oАлгоритм: Выбираем непройденную вершину. Проверяем все рёбра и запоминаем непройденные вершины на их концах. Выбираем непройденную вершину и повторяем действия. ОБХОД В ШИРИНУ:КОД typedef pair<int, bool> vertex; typedef multimap<int, int>::iterator edgesIt; vector<vertex> vertices; multimap<int, int> edges; deque<vertex> q; while(1) { Вершина представлена парой значений: номер и посещение Очередь реализована как deque current = q.front(); Текущая вершина – первая в очереди кандидатов q.pop_front(); vector<vertex>::iterator vIt = find(vertices.begin(), vertices.end(), vertex(current.first, current.second)); (*vIt).second = true; cout << "Visited: " << (*vIt).first << endl; Проверка непустоты очереди pair<edgesIt, edgesIt> rangeIt = edges.equal_range(current.first); for(edgesIt eIt = rangeIt.first; eIt != rangeIt.second; eIt++) { if((find(vertices.begin(), vertices.end(), vertex((*eIt).second, true)) == vertices.end()) && (find(q.begin(), q.end(), vertex((*eIt).second, false)) == q.end())) { push_back(vertex((*eIt).second, false)); cout << "To deque: " << (*eIt).second << endl; }} if(q.empty()) break; } cout << "End." << endl; return EXIT_SUCCESS;} Выход из цикла, если все вершины просмотрены ОБХОД В ГЛУБИНУ Алгоритм: Выбираем непройденную вершину Выбираем 1 из непройденных смежных вершин Если нет смежных непройденных вершин, то помечаем вершину пройденной и возвращаемся в запомненную вершину. ОБХОД В ГЛУБИНУ:КОД Рекурсивная реализация: vector < vector<int> > g; // граф int n; // число вершин vector<char> used; void dfs (int v) { used[v] = true; for (vector<int>::iterator i=g[v].begin(); i!=g[v].end(); ++i) if (!used[*i]) dfs (*i); } Рекурсивный вызов АЛГОРИТМ ДЕЙКСТРЫ ЗАДАЧА В ориентированном графе с заданными длинами рёбер найти кратчайшие пути из одной вершины во все остальные. ОПРЕДЕЛЕНИЕ АЛГОРИТМА Алгоритм: Если – длина дуги (i,j). Шаг 0. Помечаем нулевую вершину индексом 0 Шаг i. Помечаем вершину i индексом λi = min(λi+ λij). НАЧАЛЬНЫЕ СТАДИИ АЛГОРИТМА Придаём всем вершинам бесконечные веса Отмечаем вершину - начало Первый по очереди сосед вершины 1 — вершина 2, потому что длина пути до нее минимальна. Длина пути в нее через вершину 1 равна кратчайшему расстоянию до вершины 1 + длина ребра, идущего из 1 в 2, то есть 0 + 7 = 7. Это меньше текущей метки вершины 2, поэтому новая метка 2-й вершины равна 7. Аналогичную операцию проделываем с двумя другими соседями 1-й вершины — 3-й и 6-й. Все соседи вершины 1 проверены. Текущее минимальное расстояние до вершины 1 считается окончательным и обсуждению не подлежит (то, что это действительно так, впервые доказал Дейкстра). Вычеркнем её из графа, чтобы отметить, что эта вершина посещена. Выберем следующую вершину Снова пытаемся уменьшить метки соседей выбранной вершины, пытаясь пройти в них через 2-ю. Соседями вершины 2 являются 1, 3, 4. Первый (по порядку) сосед вершины 2 — вершина 1. Но она уже посещена, поэтому с 1-й вершиной ничего не делаем. Следующий сосед вершины 2 — вершина 4. Если идти в неё через 2-ю, то длина такого пути будет = кратчайшее расстояние до 2 + расстояние между вершинами 2 и 4 = 7 + 15 = 22. Поскольку 22<, устанавливаем метку вершины 4 равной 22. ПОВТОРЯЕМ АНАЛОГИЧНЫЕ ВЕРШИН ДЕЙСТВИЯ ДЛЯ ОСТАЛЬНЫХ priority_queue<int, vector<int>, Cheapest> candidates; while(1) Очередь с приоритетом для кандидатов { for(list<int>::iterator p = outgoing_services[from].begin(); p!=outgoing_services[from].end();p++){ int b = cities[from]->total_distance+(*p)->distance; int c = cities[from]->from_city; if(cities[from]->visited==false){ if(cities[(*p)->destination]->total_fee==0) { cities[(*p)->destination]->total_fee = a; Перебор по спискам смежности cities[(*p)->destination]->from_city = c; } else if(cities[(*p)->destination]->total_fee>a) { cities[(*p)->destination]->total_fee=a; Присвоение min значений длины пути Для непосещённых вершин cities[(*p)->destination]->total_distance = b; КОД cities[(*p)->destination]->total_distance = b; cities[(*p)->destination]->from_city = from; candidates.push(cities[(*p)->destination]); cities[from]->visited = true; if (cities[to]->visited) { } }} return pair<int,int>(cities[to]->total_fee, cities[to]->total_distance);} Else { return pair<int,int>(INT_MAX, INT_MAX); } } Добавляем вершину – «кандидата» АЛГОРИТМ БЕЛЛМАНА-ФОРДА Алгоритм Дейкстры - только в графе с положительными весами дуг. Для графа с отрицательными весами : как он поступать с циклом с отрицательным весом? делает большинство путей неопределными. Так, путь от V2 до V5 не определён, потому что можно попасть в петлю V3-V4-V1. Эту проблему решает алгоритм БеллманаФорда РЕАЛИЗАЦИЯ АЛГОРИТМА БЕЛЛМАНА-ФОРДА Аналогична реализации алгоритма Дейкстры Отличие: If (элемент есть в очереди) then она не включается повторно. КОД. If(find(candidates.begin(), candidates.end(), cities[(*p)>destination)==candidates.end()) candidates.push(cities[(*p)->destination] Если очередь не содержит кандидата, то он добавляется АЦИКЛИЧЕСКИЕ ГРАФЫ Задача поиска кратчайшего пути связана с топологической сортировкой. Топологическая сортировка упорядочивает вершины в направленном ациклическом графе так, что If (имеется путь из u в v) then {v появляется после u в очерёдности.} Она невозможна, если граф имеет хотя бы 1 цикл. Описание: Находится вершина, не имеющая входных дуг. Она и все исходящие из неё дуги удаляются из графа. АЛГОРИТМ ТОПОЛОГИЧЕСКОЙ СОРТИРОВКИ Все непройденные вершины с полустепенью захода 0 добавляются в очередь. Чтобы получить следующую вершину, мы извлекаем первый элемент из очереди. Когда полустепень захода элемента понижается до 0, он помещается в очередь КОД. vector < vector<int> > g; // граф int n; // число вершин vector<bool> used; vector<int> ans; void dfs (int v) // процедура пометки непройденных вершин { used[v] = true; for (vector<int>::itetator i=g[v].begin(); i!=g[v].end(); ++i) if (!used[*i]) dfs (*i); ans.push_back (v); } void topological_sort (vector<int> & result)// сама сортировка { used.assign (n, false); for (int i=0; i<n; ++i) if (!used[i]) dfs (i); result = ans; } ИСПОЛЬЗОВАННАЯ ЛИТЕРАТУРА: Data Structures and Problem Solving with C++ [2nd Ed] [M. A. Weiss] [Addison Wesley] Полный справочник по С++, Г.Шилдт http://www.rsdn.ru/ http://e-maxx.ru/algo/ Дж. А. Андерсон, Дискретная математика и комбинаторика, М:2004.