1. 程式人生 > >演算法導論 所有節點對的最短路徑

演算法導論 所有節點對的最短路徑

本章主要講述:

1.Floyd-Warshall演算法:求解任意兩點間的最短距離,時間複雜度為O(n^3)。

(1)使用條件&範圍:通常可以在任何圖中使用,包括有向圖、帶負權邊的圖。

(2)弗洛伊德(Floyd)演算法過程:
、用D[v][w]記錄每一對頂點的最短距離。
         2、依次掃描每一個點,並以其為基點再遍歷所有每一對頂點D[][]的值,看看是否                  可用過該基點讓這對頂點間的距離更小。

     (3)程式碼實現如下:

#include <iostream>
#include <string>   
#include <stdio.h>   
using namespace std;
#define MaxVertexNum 100   
#define INF 99999   


/***
Floyd 
*/
typedef struct
{
	char vertex[MaxVertexNum];
	int edges[MaxVertexNum][MaxVertexNum];
	int n, e;
}MGraph;

void CreateMGraph(MGraph &G)
{
	int i, j, k, p;
	cout << "請輸入頂點數和邊數:\n";
	cin >> G.n >> G.e;
	cout << "請輸入頂點元素:\n";
	for (i = 0; i<G.n; i++)
	{
		cin >> G.vertex[i];
	}
	for (i = 0; i<G.n; i++)
	{
		for (j = 0; j<G.n; j++)
		{
			G.edges[i][j] = INF;
			if (i == j)
			{
				G.edges[i][j] = 0;
			}
		}
	}
	for (k = 0; k<G.e; k++)
	{
		cout << "請輸入第" << k + 1 << "條弧頭弧尾序號和相應的權值:\n";
		cin >> i >> j >> p;
		G.edges[i][j] = p;
	}
}
void Dispath(int A[][MaxVertexNum], int path[][MaxVertexNum], int n);

void Floyd(MGraph G)
{
	int A[MaxVertexNum][MaxVertexNum], path[MaxVertexNum][MaxVertexNum];
	int i, j, k;
	for (i = 0; i<G.n; i++)
	{
		for (j = 0; j<G.n; j++)
		{
			A[i][j] = G.edges[i][j];
			path[i][j] = -1;
		}
	}
	for (k = 0; k<G.n; k++)
	{
		for (i = 0; i<G.n; i++)
		{
			for (j = 0; j<G.n; j++)
			{
				if (A[i][j]>A[i][k] + A[k][j])
				{
					A[i][j] = A[i][k] + A[k][j];
					path[i][j] = k;
				}
			}
		}
	}
	Dispath(A, path, G.n);
}

void Ppath(int path[][MaxVertexNum], int i, int j)
{
	int k;
	k = path[i][j];
	if (k == -1)
	{
		return;
	}
	Ppath(path, i, k);
	printf("%d,", k);
	Ppath(path, k, j);
}

void Dispath(int A[][MaxVertexNum], int path[][MaxVertexNum], int n)
{
	int i, j;
	for (i = 0; i<n; i++)
	{
		for (j = 0; j<n; j++)
		{
			if (A[i][j] == INF)
			{
				if (i != j)
				{
					printf("從%d到%d沒有路徑\n", i, j);
				}
			}
			else
			{
				printf("  從%d到%d=>路徑長度:%d路徑:\n", i, j, A[i][j]);
				printf("%d,", i);
				Ppath(path, i, j);
				printf("%d\n", j);
			}
		}
	}
}

int main()
{
	freopen("input.txt", "r", stdin);
	MGraph G;
	CreateMGraph(G);
	Floyd(G);
	return 0;
}

/**
floyd演算法輸入如下:
4  8
A  B  C  D
0  1  6
0  3  3
1  0  5
1  2  1
2  0  3
2  3  2
3  0  8
3  1  2

*/
執行結果如下:


2.Johnson演算法(稀疏圖):在O(V*V lgV + VE)的時間內找到所有節點對之間的最短路徑

(1)使用條件&範圍:通常可以在任何圖中使用,包括有向圖、帶負權邊的圖。

(2)Johnson演算法過程:

演算法中運用Dijskra、BellmanFord演算法,使用的技術是重新賦予權重

   a.如果圖G = (V, E)中權值全為非負值,則通過對所有結點執行一次dijkstra演算法找出所有結點對的最短路徑,

 b.如果有非負值,但沒有權重為負值的環路,那麼只要計算出一組新的非負權重值,然後再用相同的方法即可。

  (3)程式碼實現如下:

/************************************************************
Johnson.h: Johnson演算法,儲存為鄰接表,
************************************************************/


#include <vector>  
#include <queue>  
#include <stack>  
#include <iostream>  
#include <algorithm>  
#include <functional>  

using namespace std;

//鄰接表的結構  
struct ArcNode          //表結點  
{
	int source;        //圖中該弧的源節點  
	int adjvex;        //該弧所指向的頂點的位置  
	ArcNode *nextarc;  //指向下一條弧的指標  
	int weight;         //每條邊的權重  
};

template <typename VertexType>
struct VertexNode           //頭結點  
{
	VertexType data;    //頂點資訊  
	ArcNode *firstarc;  //指向第一條依附於該頂點的弧的指標  
	int key;            //Prim:儲存連線該頂點和樹中結點的所有邊中最小邊的權重;   
	//BellmanFord:記錄從源結點到該結點的最短路徑權重的上界  
	VertexNode *p;      //指向在樹中的父節點  
	int indegree;       //記錄每個頂點的入度  
};

const int SIZE = 6;

//圖的操作  
template <typename VertexType>
class ALGraph
{
public:
	typedef VertexNode<VertexType> VNode;
	ALGraph(int verNum) : vexnum(verNum), arcnum(0)
	{
		for (int i = 0; i < MAX_VERTEX_NUM; i++)
		{
			vertices[i].firstarc = NULL;
			vertices[i].key = INT_MAX / 2;
			vertices[i].p = NULL;
			vertices[i].indegree = 0;
		}
	}

	//構造演算法導論410頁圖(帶權有向圖)  
	void createWDG()
	{
		cout << "構造演算法導論410頁圖(帶權有向圖)..." << endl;
		int i;
		for (i = 1; i < vexnum; i++)
			vertices[i].data = 'a' + i - 1;

		insertArc(1, 2, 3);
		insertArc(1, 3, 8);
		insertArc(1, 5, -4);
		insertArc(2, 4, 1);
		insertArc(2, 5, 7);
		insertArc(3, 2, 4);
		insertArc(4, 3, -5);
		insertArc(4, 1, 2);
		insertArc(5, 4, 6);
	}

	void createG()
	{
		cout << "構造圖G'...." << endl;
		vertices[0].data = 's';
		insertArc(0, 1, 0);
		insertArc(0, 2, 0);
		insertArc(0, 3, 0);
		insertArc(0, 4, 0);
		insertArc(0, 5, 0);
	}

	//Johnson演算法,先使用BellmanFord演算法,使所有的邊的權重變為非負值,  
	//然後運用dijkstra演算法求出結點對的最短路徑  
	int **Johnson()
	{
		createG();          //構造G’  
		displayGraph();

		if (!BellmanFord(1))
			cout << "the input graph contains a negative-weight cycle" << endl;
		else
		{
			int h[SIZE];
			int i, j, k;

			//將陣列h[]的值設為執行BellmanFord後取得的值,h[i]為結點s到其他點的最短路徑  
			for (i = 0; i < vexnum; i++)
				h[i] = vertices[i].key;
			//遍歷所有的邊,將邊的權值重新賦值,即將所有的邊的權值改為負值  
			for (i = 0; i < vexnum; i++)
			{
				ArcNode *arc = vertices[i].firstarc;
				for (; arc != NULL; arc = arc->nextarc)
					arc->weight = arc->weight + h[arc->source] - h[arc->adjvex];
			}
			//以下為程式碼:
			cout << "改變權重後的圖為:" << endl;                                                                                                                     displayGraph();
			int **d = new int *[SIZE];
			for (j = 0; j < SIZE; j++)
				d[j] = new int[SIZE];
			//對每個結點執行dijkstra演算法,求出每個點到其他點的最短路徑,儲存在key中  
			for (k = 1; k < SIZE; k++)
			{
				Dijkstra(k + 1);
				for (i = 1; i < SIZE; i++)
					d[k][i] = vertices[i].key + h[i] - h[k];
			}
			cout << "最後計算出的結點對的最短距離:" << endl;
			displayTwoDimArray(d);
			return d;
		}
	}

	//輸出一個二維陣列  
	void displayTwoDimArray(int **p)
	{
		for (int i = 0; i < SIZE; i++)
		{
			for (int j = 0; j < SIZE; j++)
				cout << p[i][j] << " ";
			cout << endl;
		}
		cout << "~~~~~~~~~~~~~~~" << endl;
	}

	//列印鄰接連結串列  
	virtual void displayGraph()
	{
		for (int i = 0; i < vexnum; i++)
		{
			cout << "第" << i + 1 << "個頂點是:" << vertices[i].data
				<< " 頂點的入度為:" << vertices[i].indegree << " 鄰接表為: ";
			ArcNode *arcNode = vertices[i].firstarc;
			while (arcNode != NULL)
			{
				cout << " -> " << vertices[arcNode->adjvex].data
					<< "(" << arcNode->weight << ")";
				arcNode = arcNode->nextarc;
			}
			cout << endl;
		}
		cout << "*******************************************************" << endl;
	}

	//PVnode排序準則  
	class PVNodeCompare
	{
	public:
		bool operator() (VNode *pvnode1, VNode *pvnode2)
		{
			return pvnode1->key > pvnode2->key;
		}
	};

	//對每個結點的最短路徑估計和前驅結點進行初始化,最短路徑初始化為INT_MAX, p初始化為NULL  
	//並將源節點的key初始化為0  
	void InitalizeSingleSource(int index)
	{
		for (int i = 0; i < MAX_VERTEX_NUM; i++)
		{
			vertices[i].key = INT_MAX >> 2;
			vertices[i].p = NULL;
		}
		vertices[index].key = 0;
	}

	//對邊(u, v)進行鬆弛,將目前s到v的最短路徑v.key與s到u的最短路徑加上w(u, v)的值進行比較  
	//如果比後面的值還大,則進行更新,將v.key縮短,並且將p置為u  
	void relax(ArcNode *arc)
	{
		//竟然溢位了!!  
		if (vertices[arc->adjvex].key > vertices[arc->source].key + arc->weight)
		{
			vertices[arc->adjvex].key = vertices[arc->source].key + arc->weight;
			vertices[arc->adjvex].p = &vertices[arc->source];
		}
	}

	//BellmanFord, index為實際第幾個點  
	bool BellmanFord(int index)
	{
		InitalizeSingleSource(index - 1);
		for (int i = 1; i < vexnum; i++)     //迴圈共進行vexnum-1次  
		{
			//遍歷所有的邊,並對每個邊進行一次鬆弛  
			for (int j = 0; j < vexnum; j++)
			{
				for (ArcNode *arc = vertices[j].firstarc; arc != NULL; arc = arc->nextarc)
					relax(arc);
			}
		}
		//再次遍歷所有的邊,檢查圖中是否存在權重為負值的環路,如果存在,則返回false  
		for (int j = 0; j < vexnum; j++)
		{
			for (ArcNode *arc = vertices[0].firstarc; arc != NULL; arc = arc->nextarc)
			{
				if (vertices[arc->adjvex].key > vertices[arc->source].key + arc->weight)
					return false;
			}
		}
		cout << "BellmanFord求出的單源最短路徑:" << endl;
		for (int i = 1; i < vexnum; i++)
		{
			printPath(index - 1, i);
		}
		cout << "**************************************************" << endl;
		return true;
	}

	void Dijkstra(int index)
	{
		InitalizeSingleSource(index - 1);
		vector<VNode> snode;       //儲存已經找到最短路徑的結點  
		vector<VNode *> que;       //儲存結點的指標的陣列,用這個陣列執行堆的演算法  

		//將結點指標進佇列,形成以key為關鍵值的最小堆  
		for (int i = 0; i < vexnum; i++)
			que.push_back(&(vertices[i]));
		//使que按照pvnodecompare準則構成一個最小堆  
		make_heap(que.begin(), que.end(), PVNodeCompare());

		while (que.empty() == false)
		{
			//將佇列中擁有最小key的結點出隊  
			VNode *node = que.front();
			pop_heap(que.begin(), que.end(), PVNodeCompare());   //從堆中刪除最小的結點,只是放到了vector的最後  
			que.pop_back();      //將vector中的這個結點徹底刪除,因為後面還要再排序一次,以免影響後面的堆排序,pop演算法。  
			snode.push_back(*node);
			for (ArcNode *arc = node->firstarc; arc != NULL; arc = arc->nextarc)
				relax(arc);
			make_heap(que.begin(), que.end(), PVNodeCompare());
		}
		cout << "Dijkstra求出的單源最短路徑:" << endl;
		for (int i = 1; i < vexnum; i++)
		{
			if (i != index - 1)
				printPath(index - 1, i);
		}
		cout << "**************************************************" << endl;
	}

protected:
	//插入一個表結點  
	void insertArc(int vHead, int vTail, int weight)
	{
		//構造一個表結點  
		ArcNode *newArcNode = new ArcNode;
		newArcNode->source = vHead;
		newArcNode->adjvex = vTail;
		newArcNode->nextarc = NULL;
		newArcNode->weight = weight;

		//arcNode 是vertics[vHead]的鄰接表  
		ArcNode *arcNode = vertices[vHead].firstarc;
		if (arcNode == NULL)
			vertices[vHead].firstarc = newArcNode;
		else
		{
			while (arcNode->nextarc != NULL)
			{
				arcNode = arcNode->nextarc;
			}
			arcNode->nextarc = newArcNode;
		}
		arcnum++;
		vertices[vTail].indegree++;         //對弧的尾結點的入度加1  
	}

	//列印源節點到i的最短路徑  
	void printPath(int i, int j)
	{
		cout << "從源節點 " << vertices[i].data << " 到目的結點 "
			<< vertices[j].data << " 的最短路徑是:" /*<< endl*/;
		__printPath(&vertices[i], &vertices[j]);
		cout << " 權重為:" << vertices[j].key << endl;
	}

	void __printPath(VNode* source, VNode* dest)
	{
		if (source == dest)
			cout << source->data << "->";
		else if (dest->p == NULL)
			cout << " no path!" << endl;
		else
		{
			__printPath(source, dest->p);
			cout << dest->data << "->";
		}
	}

private:
	//const資料成員必須在建構函式裡初始化  
	static const int MAX_VERTEX_NUM = 20;  //最大頂點個數  

	VNode vertices[MAX_VERTEX_NUM];      //存放結點的陣列  
	int vexnum;             //圖的當前頂點數  
	int arcnum;             //圖的弧數  
};
int main()
{
	ALGraph<char> wdgGraph(6);
	wdgGraph.createWDG();
	wdgGraph.Johnson();
	/*wdgGraph.displayGraph();*/

	system("pause");
	return 0;
}
執行結果如下: