Решение задач на использование алгоритмов обработки данных
Алгоритмы на графах
Многие прикладные задачи и задачи повышенной сложности легко сформулировать в терминах такой структуры данных как граф. Для ряда подобных задач хорошо изучены эффективные (полиномиальные) алгоритмы их решения.
Для хранения графа в программе можно применить различные методы. Самым простым является хранение матрицы смежности, с помощью которой легко проверить, существует ли в графе ребро, соединяющее вершину одну с вершиной с другой. Основной же ее недостаток заключается в том, что матрица смежности требует, чтобы объем памяти был достаточен для хранения N2 значений, даже если ребер в графе существенно меньше, чем N2. Это не позволяет построить алгоритм со временем порядка O(N) для графов, имеющих O(N) ребер.
Данного недостатка лишены такие способы хранения графа, как одномерный массив длины N списков или множеств вершин. В таком массиве каждый элемент соответствует одной из вершин и содержит список или множество вершин, смежных ей.
Для реализации некоторых алгоритмов более удобным является описание графа путем перечисления его ребер. В этом случае хранить его можно в одномерном массиве длиной M, каждый элемент которого содержит запись о номерах начальной и конечной вершин ребра, а также его весе в случае взвешенного графа.
При решении многих задач, как для ориентированных, так и для неориентированных графов, необходим эффективный метод систематического обхода вершин графа. На практике применяется два принципиально различных порядка обхода, основанных на поиске в глубину и поиске в ширину соответственно.
Для определения и нахождения длины кратчайшего пути в графе в основном используют известные алгоритмы, такие как алгоритм Дейкстры, Флойда и переборные алгоритмы.
Графы широко используются в различных областях науки и техники для моделирования отношений между объектами. Объекты соответствуют вершинам графа, а ребра – отношениям между объектами.
Пример 2. Задача "Тетраэдр"
Дано треугольное поле в виде равностороннего треугольника. Оно разбито на одинаковые равносторонние треугольники со сторонами в М раз меньшими, чем сторона большого треугольника ( рис. 46.1).
Маленькие треугольники пронумерованы подряд с верхнего ряда вниз по рядам, начиная с 0. Числами показаны номера треугольников. I -му треугольнику приписана пометка Pi.
Имеется также тетраэдр (правильная треугольная пирамида) с ребром, равным длине стороны маленького треугольника. Тетраэдр установлен на S -м треугольнике. Все грани тетраэдра пронумерованы следующим образом:
- основание тетраэдра;
- правая грань тетраэдра, если смотреть сверху тетраэдра в направлении стороны АВ перпендикулярно ей;
- левая грань тетраэдра, если смотреть сверху тетраэдра в направлении стороны АВ перпендикулярно ей;
- оставшаяся грань.
Например, при S=2 жирной линией выделено нижнее ребро третьей грани, а при S=3 жирной линией выделено нижнее ребро второй грани. J -я грань тетраэдра имеет пометку Rj.
Имеется возможность перекатывать тетраэдр через ребро, но при каждом перекатывании взимается штраф, равный квадрату разности между пометками совмещаемой грани тетраэдра и треугольника. Требуется перекатить тетраэдр с треугольника S на D с наименьшим суммарным штрафом .
Входные данные находятся в текстовом файле INPUT.TXT. Первая строка содержит целые числа S, D и М (M<=90). Каждая из следующих M2 строк содержит пометку соответствующего треугольника. В последней строке записаны пометки граней тетраэдра. Пометки (как граней, так и треугольников) – целые неотрицательные числа, не превосходящие 300. Числа в одной строке разделены пробелами.
В выходной файл OUTPUT.TXT должно быть записано одно число – минимально возможный штраф.
Пример.
Описание решения.
Перейдем к графу следующим образом: вершина – маленький треугольник. Ребро – наличие возможности перекатить тетраэдр через ребро из одного треугольника в другой. Тогда, например, поле, изображенное на рис. 46.2, превратится в граф на рис. 46.3.
На этом графе требуется найти путь минимальной стоимости из одной вершины в другую. Поскольку веса ребер в этом графе зависят от того, какой именно гранью тетраэдр придет на соответствующий треугольник, то воспользуемся поиском в ширину. Но прежде проясним процесс перекатывания тетраэдра. В соответствии с условиями задачи начальное положение развертки тетраэдра показано на рис. 46.4.
Обозначим его 1u (в основании грань номер 1, повернутая вверх). Очевидно, что всего существует 8 возможных различных состояний тетраэдра: 1u, 2u, Зu, 4u, 1d, 2d, 3d, 4d. На рис. 46.5 и рис. 46.6 приведены соответствующие развертки:
Составим теперь таблицу, отображающую, в какое из состояний переходит тетраэдр при перекатывании его вниз, вверх, вправо, влево из текущего состояния.
Вниз | Вверх | Вправо | Влево | |
1u | 4d | x | 3d | 2d |
1d | x | 4u | 2u | 3u |
2u | 3d | x | 4d | 1d |
2d | x | 3u | 1u | 4u |
3u | 2d | x | 1d | 4d |
3d | x | 2u | 4u | 1u |
4u | 1d | x | 2d | 3d |
4d | x | 1u | 3u | 2u |
Для удобства использования этой информации введем следующее кодирование:
Получаем двумерный массив Т (8 строк, 4 столбца), который описывает все возможные перекатывания тетраэдра.
Основная идея решения заключается в следующем:
- заносим в очередь стартовую позицию S ;
- пока очередь не пуста, берем из очереди очередную позицию, ставим в очередь позиции, в которые тетраэдр может попасть за одно перекатывание.
При установке в очередь очередной элемент включает номер вершины на графе, тип прихода (1u...4d), текущий штраф после перехода в эту вершину. Элемент не нужно ставить в очередь, если текущий штраф больше ранее запомненного для этой вершины графа.
#include "stdafx.h" #include <iostream> #include <cmath> using namespace std; void InputData(); void OutResult(); void InitGraph(); void Put(long long v, long long tv, long long cv); void Get(long long *v, long long *tv, long long *cv); void PutAll(long long v, long long tv, long long cv); long long SQR(long long a); int MaxM = 10; int Table[8][4] = { 8, 0, 6, 4, 0, 7, 3, 5, 6, 0, 8, 2, 0, 5, 1, 7, 4, 0, 2, 8, 0, 3, 7, 1, 2, 0, 4, 6, 0, 1, 5, 3 }; int MaxQ = MaxM * MaxM * MaxM; int *p, *cp, *Pw, **g, **Q; long long *R; long long i, S, D, M, j, a, TS, QBegin, QEnd, V, TV, CV, Last; //V – номер вершины //TV – тип вершины //CV – текущее значение штрафа int _tmain(int argc, _TCHAR* argv[]){ p = new int[MaxM * MaxM]; cp = new int[MaxM * MaxM]; Pw = new int[MaxM * MaxM]; for (i = 0; i < MaxM * MaxM; i++) p[i] = cp[i] = Pw[i] = 0; g = new int*[MaxM * MaxM]; for (i = 0; i < MaxM * MaxM; i++ ){ g[i] = new int[4]; g[i][0] = g[i][1] = g[i][2] = g[i][3] = 0; } Q = new int*[MaxQ + 1]; for (i = 0; i < MaxQ + 1; i++ ){ Q[i] = new int[4]; Q[i][0] = Q[i][1] = Q[i][2] = Q[i][3] = Q[i][4] = 0; } R = new long long[5]; R[0] = R[1] = R[2] = R[3] = R[4] = 0; InputData(); InitGraph(); QEnd = 0; QBegin = 1; Put(S,TS,0); while (QBegin <= QEnd){ Get(&V,&TV,&CV); PutAll(V,TV,CV); } OutResult(); system("pause"); return 0; } //описание функции ввода исходных данных void InputData(){ FILE *f; f = fopen("input.txt","r"); fscanf(f,"%d %d %d",&S,&D,&M); for ( i = 0; i < M * M; i++ ) fscanf(f,"%d",p + i); for ( i = 1; i < 5; i++ ) fscanf(f,"%d",R + i); fclose(f); } //описание функции вывода результата void OutResult(){ FILE *f; f = fopen("output.txt","w"); fprintf(f,"%d",cp[D]); fclose(f); } //описание функции создания графа по исходным данным void InitGraph(){ Pw[0] = 1; g[0][1] = 2; for ( i = 1; i < M; i++) for ( j = i * i; j < (i + 1) * (i + 1) - 1; j++){ g[j][++Pw[j]] = j + 1; g[j + 1][++Pw[j + 1]] = j; } a = 4; TS = 1; for ( i = 1; i < M - 1; i++){ for ( j = i * i; j < (i + 1) * (i + 1); j += 2){ g[j][++Pw[j]] = j + a; g[j + a][++Pw[j + a]] = j; if ( S == j ) TS = 2; } a += 2; } for ( i = 0; i < M * M; i++) cp[i] = INT_MAX; } //описание функции постановки в очередь одной вершины графа void Put(long long v, long long tv, long long cv){ QEnd++; Q[QEnd][1] = v; Q[QEnd][2] = tv; Q[QEnd][3] = cv; cp[v] = cv; } //описание функции взятия из очереди очередной вершины графа void Get(long long *v, long long *tv, long long *cv){ *v = Q[QBegin][1]; *tv = Q[QBegin][2]; *cv = Q[QBegin][3]; QBegin++; } /*описание функции постановки в очередь всех вершин, смежных с текущей*/ void PutAll(long long v, long long tv, long long cv){ long nv, ntv, ncv, Dir, Base; for ( i = 1 ; i <= Pw[v]; i++ ){ nv = g[v][i]; if ( nv == v + 1 ) Dir = 2; else if ( nv == v - 1 ) Dir = 3; else if ( nv > v ) Dir = 0; else Dir = 1; ntv = Table[tv-1][Dir]; Base = (ntv + 1) / 2; if ( Base > 0 ) { ncv = cv + SQR(p[nv] - R[Base]); if ( ncv < cp[nv] ) Put(nv,ntv,ncv); } } } //описание функции возведения в квадрат long long SQR(long long a){ return a*a; }Листинг .