图的最短路径

     以下内容主要参考了严蔚敏版的数据结构教材。
     最短路径算法用来计算网络中两个节点之间的最短路径。在现实生活中网络可以是全国的交通网络,网络的节点是各个城市,节点之间的边的权值是城市之间的距离。这时最短路径算法可以用来规划由出发城市到目的城市的距离最短的路线。下面要介绍的是 D i j k s t r a Dijkstra Dijkstra算法(计算有向带权图中某一节点到其它所有节点的最短路径)和 F l o y d Floyd Floyd算法(计算有向带权图中所有节点对的最短路径)。
     假设现在要求从顶点 V 0 V_0 V0到其余各顶点(顶点 V 1 V_1 V1到顶点 V n − 1 V_{n-1} Vn1)的最短路径。求得之后一共有 n − 1 n-1 n1条路径,分别是从顶点 V 0 V_0 V0到其余各顶点的最短路径。求得之后可以对这 n − 1 n-1 n1条路径的长度进行一个排序(路径上弧上权值的和较小的排在前面,路径上弧上权值的和较大的排在后面)。 D i j k s t r a Dijkstra Dijkstra算法在求从顶点 V 0 V_0 V0到其余各顶点的最短路径时,首先求得的是顶点 V 0 V_0 V0到某个顶点的路径,顶点 V 0 V_0 V0到该顶点的路径的长度最后将是顶点 V 0 V_0 V0到其余各顶点的 n − 1 n-1 n1条最短路径中最短的一条,接下来求得的到其它顶点的最短路径将会按路径长度递增的顺序进行。也就是第二次求得的到某个顶点的最短路径将是顶点 V 0 V_0 V0到其余各顶点的 n − 1 n-1 n1条最短路径中第二短的一条,最后求得的到某个顶点的最短路径将是顶点 V 0 V_0 V0到其余各顶点的 n − 1 n-1 n1条最短路径中最长的一条。
     假设数组 A r r a y [ n ] Array[n] Array[n]用来存储顶点 V 0 V_0 V0到其余各顶点的 n − 1 n-1 n1条最短路径的路径长度, A r r a y [ V 0 ] = 0 Array[V_0]=0 Array[V0]=0,数组 A r r a y [ n ] Array[n] Array[n]的其它 n − 1 n-1 n1个元素的初始值为顶点 V 0 V_0 V0在图的邻接矩阵中对应的哪一行中值。集合S为已经求得从顶点 V 0 V_0 V0出发的最短路径的节点的集合,现在将顶点 V 0 V_0 V0加入到集合S中。 D i j k s t r a Dijkstra Dijkstra算法的算法流程如下:

  1. 找到数组 A r r a y [ n ] Array[n] Array[n]中元素值最小的哪一个元素的元素值 m i n min min以及其对应的节点 i i i。这是第一次求得的最短路径。将顶点 i i i加入到集合S中。
  2. 更新数组 A r r a y [ n ] Array[n] Array[n]中对应不在集合S中的所有元素 j j j的值。如果 ( m i n + w e i g h t < i , j > ) < A r r a y [ j ] (min+weight<i,j>)<Array[j] (min+weight<i,j>)<Array[j] A r r a y [ j ] = ( m i n + w e i g h t < i , j > ) Array[j]=(min+weight<i,j>) Array[j]=(min+weight<i,j>) w e i g h t < i , j > weight<i,j> weight<i,j>为弧 i − > j i->j i>j的权值
  3. 再次重复操作1和2一共 n − 2 n-2 n2次就可以得到顶点 V 0 V_0 V0到其余各顶点的 n − 1 n-1 n1条最短路径。

     以上原理的证明如下:假设当前求得到节点 x x x的最短路径,下一条求得的最短路径设其终点为 y y y,则这条路径或者是弧 V 0 − > V y V_0->V_y V0>Vy或者是中间节点只存在于集合S中的路径 V 0 − > . . . . . . . . − > V y V_0->........->V_y V0>........>Vy(中间节点只存在于集合S中)。这可以用反证法来证明,如果当前求得的最短路径不是弧 V 0 − > V y V_0->V_y V0>Vy,而是有中间节点的路径 V 0 − > . . . . . . . . − > V y V_0->........->V_y V0>........>Vy,如果中间节点中有不在集合S中的节点 k k k,比如 V 0 − > . . . . . − > V k − > . . . − > V y V_0->.....->V_k->...->V_y V0>.....>Vk>...>Vy,则说明有比当前求得的最短路径更短的路径 V 0 − > . . . . . − > V k V_0->.....->V_k V0>.....>Vk且终点不在S中,但是这是不可能的因为我们是按照路径长度递增的顺序来求最短路径的。故长度比路径 V 0 − > . . . . . . . . − > V y V_0->........->V_y V0>........>Vy更短的路径都已经产生,它们的终点必定都在集合S中。
     针对图1的例子的算法过程如下面的组图所示。从推导过程我们可以看到,在每次求出到某个顶点的最短路径时,我们都会更新不在集合S里面的顶点对应的 d i s t a n c e M a t r i x distanceMatrix distanceMatrix数组元素的值,这一步的操作保证了不在集合S里面的顶点对应的 d i s t a n c e M a t r i x distanceMatrix distanceMatrix数组元素的值都是当前从顶点 V 0 V_0 V0到不在集合S里面的顶点的路径的最小值(如果这些路径有中间节点的话,中间节点只允许在集合S中)。这样也就保证了按照路径长度递增的顺序来求到各个顶点的最短路径。

 
图1.
 
图2.
 
图3.
 
图4.
 
图5.
 
图6.

     数据结构 s h o r t e s t P a t h R e s u l t shortestPathResult shortestPathResult用来存储 D i j k s t r a Dijkstra Dijkstra算法结束后顶点 V 0 V_0 V0到其余各顶点的最短路径的路径长度( d i s t a n c e M a t r i x distanceMatrix distanceMatrix)以及对应的最短路径所经过的节点( p a t h M a t r i x pathMatrix pathMatrix)。 p a t h M a t r i x pathMatrix pathMatrix是一个二维数组( r o w row row, c o l u m n column column),如果顶点 V 0 V_0 V0到顶点 V j V_j Vj的最短路径中包含索引为k的顶点则二维素组的元素( r o w = k row=k row=k, c o l u m n = j column=j column=j)值为1否则为0(这里有向带权图中的顶点从0开始编号)。

#define INFINITY 1000000
class shortestPathResult
{
private:
	vector<vector<int>> pathMatrix;
	vector<int> distanceMatrix;
public:
	shortestPathResult(int pathNodeNum)
	{
		pathMatrix = vector<vector<int>>(pathNodeNum, vector<int>(pathNodeNum, 0));
		distanceMatrix = vector<int>(pathNodeNum, 0);
	}
	void setpathMatrix(int row, int column, int data)
	{
		pathMatrix[row][column] = data;
	}

	int getpathMatrix(int row, int column)
	{
		return pathMatrix[row][column];
	}

	void setdistanceMatrix(int index, int data)
	{
		distanceMatrix[index] = data;
	}

	int getdistanceMatrix(int index)
	{
		return distanceMatrix[index];
	}

	void printDistanceMatrix()
	{
		for (int i = 0; i < distanceMatrix.size(); i++)
		{

			cout << "distanceMatrix[" << i << "]=" << distanceMatrix[i] << "  "<<endl;
		}
	}

	void printPath()
	{
		for (int i = 0; i < pathMatrix.size(); i++)
		{
			cout << "The shortest path to node " << i << " is:";
			for (int j = 0; j < pathMatrix.size(); j++)
			{

			    if (pathMatrix[j][i])
			    {
			    	cout << " " << j << " ";
			    }	
			}
			cout << endl;
		}
	}
};


//图的数据结构
class MGraph
{
private:
	vector<vector<int>> adjMatrix;
	vector<int> nodeData;
	int nodeNum;

public:
	MGraph(int num)
	{
		nodeNum = num;
		nodeData = vector<int>(num, 0);
		adjMatrix = vector<vector<int>>(num, vector<int>(num));
		for (int i = 0; i < num; i++)
		{
			for (int j = 0; j < num; j++)
			{
				if (i == j)
				{
					adjMatrix[i][j] = 0;
				}
				else
				{
					adjMatrix[i][j] = INFINITY;
				}
			}
		}
	}
	int getNodeNum()
	{
		return nodeNum;
	}
	void setNodeData(int data, int nodeIndex)
	{
		nodeData[nodeIndex] = data;
	}
	int getNodeData(int data, int nodeIndex)
	{
		return nodeData[nodeIndex];
	}
	void setWeight(int weight, int tail, int head)
	{
		adjMatrix[tail][head] = weight;
	}
	int getWeight(int tail, int head)
	{
		return adjMatrix[tail][head];
	}
	void printAdjMatrix()
	{
		for (int i = 0; i < nodeNum; i++)
		{
			for (int j = 0; j < nodeNum; j++)
			{
				cout << "adjMatrix[" << i << "][" << j << "]=" << adjMatrix[i][j] << "  ";
			}
			cout << endl;
		}
	}
};


shortestPathResult ShortestPathOfDijkstra(MGraph g,int startNode)
{
	shortestPathResult result(g.getNodeNum());
	vector<int> S_set(g.getNodeNum(), 0);
	for (int i = 0; i < g.getNodeNum(); i++)
	{
		//初始化distanceMatrix和pathMatrix
		result.setdistanceMatrix(i, g.getWeight(startNode, i));		
		if (result.getdistanceMatrix(i) < INFINITY)
		{
			result.setpathMatrix(startNode, i, 1);
			result.setpathMatrix(i, i, 1);
		}
	}
	S_set[startNode] = 1;

	int min = INFINITY;
	int minIndex = 0;
	for (int i = 1; i < g.getNodeNum(); i++)
	{
		min = INFINITY;
		for (int j = 0; j < g.getNodeNum(); j++)
		{
			if (!S_set[j])
			{
				if (result.getdistanceMatrix(j) < min)
				{
					min = result.getdistanceMatrix(j);
					minIndex = j;
				}
			}
		}
		S_set[minIndex] = 1;
		for (int k = 0; k < g.getNodeNum(); k++)
		{
			if ((!S_set[k])&&((min+g.getWeight(minIndex,k))< result.getdistanceMatrix(k)))
			{   //更新distanceMatrix
				result.setdistanceMatrix( k, min + g.getWeight(minIndex, k));
				for (int depth = 0; depth < g.getNodeNum(); depth++)
				{
					//更新pathMatrix
					result.setpathMatrix(depth, k, result.getpathMatrix(depth,minIndex));
				}
				result.setpathMatrix(k, k, 1);
			}
		}
	}
	return result;
}
//测试程序(在图1上测试)
int main()
{
	MGraph g(6);
	for (int nodeDataIndex = 0; nodeDataIndex < 6; nodeDataIndex++)
	{
		g.setNodeData(nodeDataIndex, nodeDataIndex);
	}
	g.setWeight(10, 0, 2);
	g.setWeight(30, 0, 4);
	g.setWeight(100, 0, 5);
	g.setWeight(5, 1, 2);
	g.setWeight(50, 2, 3);
	g.setWeight(10, 3, 5);
	g.setWeight(60, 4, 5);
	g.setWeight(20, 4, 3);
	g.printAdjMatrix();
	shortestPathResult result = ShortestPathOfDijkstra(g,0);
	result.printDistanceMatrix();
	result.printPath();
}

      F l o y d Floyd Floyd算法的思想是:假设现在对于一个有n个顶点的有向带权图求顶点 V i V_i Vi到顶点 V j V_j Vj的最短路径。顶点 V i V_i Vi到顶点 V j V_j Vj的最短路径中除开第一个顶点顶点 V i V_i Vi和最后一个顶点 V j V_j Vj外中间节点的序号的集合是集合 { 0 , 1 , 2 , . . . , n − 1 } \{0,1,2,...,n-1\} {0,1,2,...,n1}的一个子集。现在求顶点 V i V_i Vi到顶点 V j V_j Vj的最短路径可以这样的操作:

  1. 如果有顶点 V i V_i Vi指向顶点 V j V_j Vj的弧,则求得 V i V_i Vi到顶点 V j V_j Vj的当前最短路径(没有中间节点)为顶点 V i V_i Vi指向顶点 V j V_j Vj的弧的权值。
  2. 取出集合 { 0 , 1 , 2 , . . . , n − 1 } \{0,1,2,...,n-1\} {0,1,2,...,n1}中的元素0,求得 V i V_i Vi到顶点 V j V_j Vj的当前最短路径(其中间节点的序号小于等于0)。如果路径 V i − > V 0 V_i->V_0 Vi>V0和路径 V 0 − > V j V_0->V_j V0>Vj存在且路径 V i − > V 0 V_i->V_0 Vi>V0和路径 V 0 − > V j V_0->V_j V0>Vj都没有中间节点,如果路径 V i − > V 0 V_i->V_0 Vi>V0上弧的权值之和和路径 V 0 − > V j V_0->V_j V0>Vj上弧的权值之和的和小于顶点 V i V_i Vi直接指向顶点 V j V_j Vj的弧的权值,则当前最短路径为路径 V i − > V 0 V_i->V_0 Vi>V0和路径 V 0 − > V j V_0->V_j V0>Vj的连接,否则为顶点 V i V_i Vi直接指向顶点 V j V_j Vj的路径。
  3. 取出集合 { 0 , 1 , 2 , . . . , n − 1 } \{0,1,2,...,n-1\} {0,1,2,...,n1}中的元素1,求得 V i V_i Vi到顶点 V j V_j Vj的当前最短路径(其中间节点的序号小于等于1)。如果路径 V i − > . . . − > V 1 V_i->...->V_1 Vi>...>V1和路径 V 1 − > . . . − > V j V_1->...->V_j V1>...>Vj存在且路径 V i − > . . . − > V 1 V_i->...->V_1 Vi>...>V1 V 1 − > . . . − > V j V_1->...->V_j V1>...>Vj都是中间节点的序号小于等于0的路径,如果路径 V i − > . . . − > V 1 V_i->...->V_1 Vi>...>V1上弧的权值之和和路径 V 1 − > . . . − > V j V_1->...->V_j V1>...>Vj上弧的权值之和的和小于顶点 V i V_i Vi到顶点 V j V_j Vj的最短路径(其中间节点的序号小于等于0),则当前最短路径为路径 V i − > . . . − > V 1 V_i->...->V_1 Vi>...>V1和路径 V 1 − > . . . − > V j V_1->...->V_j V1>...>Vj的连接,否则为顶点 V i V_i Vi到顶点 V j V_j Vj的最短路径(其中间节点的序号小于等于0)。
  4. 迭代以上步骤直到集合 { 0 , 1 , 2 , . . . , n − 1 } \{0,1,2,...,n-1\} {0,1,2,...,n1}中所有元素被取出,这时便可以求得最短路径。

     以上原理的证明如下:如果现在已经求得有向带权图中任意一对顶点对中间的最短路径(其中中间节点的序号小于等于k),现在要求顶点 V i V_i Vi到顶点 V j V_j Vj的当前最短路径(其中中间节点的序号小于等于 k + 1 k+1 k+1)。如果现在有一条从顶点 V i V_i Vi到顶点 V j V_j Vj的路径(其中中间节点的序号小于等于 k + 1 k+1 k+1且假设中间节点包含顶点 k + 1 k+1 k+1),则该路径可以被划分为两条路径,路径 V i − > . . . − > V k + 1 V_i->...->V_{k+1} Vi>...>Vk+1和路径 V k + 1 − > . . . − > V j V_{k+1}->...->V_j Vk+1>...>Vj,则路径 V i − > . . . − > V k + 1 V_i->...->V_{k+1} Vi>...>Vk+1和路径 V k + 1 − > . . . − > V j V_{k+1}->...->V_j Vk+1>...>Vj都是中间节点的序号小于等于k的路径。为了使得当前这一条从顶点 V i V_i Vi到顶点 V j V_j Vj的路径(其中中间节点的序号小于等于 k + 1 k+1 k+1且假设中间节点包含顶点 k + 1 k+1 k+1)的距离最短,则其被划分为的路径 V i − > . . . − > V k + 1 V_i->...->V_{k+1} Vi>...>Vk+1和路径 V k + 1 − > . . . − > V j V_{k+1}->...->V_j Vk+1>...>Vj(这两条路径都是中间节点的序号小于等于k的路径)都必须为中间节点的序号小于等于k的最短路径。又因为现在已经求得有向带权图中任意一对顶点对中间的最短路径(其中中间节点的序号小于等于k),因此顶点 V i V_i Vi到顶点 V j V_j Vj的当前最短路径(其中中间节点的序号小于等于 k + 1 k+1 k+1)为顶点 V i V_i Vi到顶点 V j V_j Vj的当前最短路径(其中中间节点的序号小于等于k)或者是其被划分为的路径 V i − > . . . − > V k + 1 V_i->...->V_{k+1} Vi>...>Vk+1和路径 V k + 1 − > . . . − > V j V_{k+1}->...->V_j Vk+1>...>Vj(这两条路径都是中间节点的序号小于等于k的路径)的最短路径和。得证。
     以下的测试程序以图7为例子进行测试。数据结构 s h o r t e s t P a t h R e s u l t shortestPathResult shortestPathResult用来存储 F l o y d Floyd Floyd算法结束后每一对顶点之间的最短路径( d i s t a n c e M a t r i x distanceMatrix distanceMatrix)以及该对顶点之间的最短路径所经过的节点( p a t h M a t r i x pathMatrix pathMatrix)。 p a t h M a t r i x pathMatrix pathMatrix是一个三维数组( d e p t h depth depth, r o w row row, c o l u m n column column),如果顶点 V i V_i Vi到顶点 V j V_j Vj的最短路径中包含索引为k的顶点则三维素组的元素( d e p t h = k depth=k depth=k, r o w = i row=i row=i, c o l u m n = j column=j column=j)值为1否则为0(这里有向带权图中的顶点从0开始编号)。

 
图7.
#define INFINITY 1000000
class shortestPathResult
{
private:
	vector<vector<vector<int>>> pathMatrix;
	vector<vector<int>> distanceMatrix;
public:
	shortestPathResult(int pathNodeNum)
	{
		pathMatrix=vector<vector<vector<int>>>(pathNodeNum, vector<vector<int >> (pathNodeNum, vector<int>(pathNodeNum, 0)));
		distanceMatrix=vector<vector<int>>(pathNodeNum, vector<int>(pathNodeNum,0));
	}
	void setpathMatrix(int depth,int row,int column,int data)
	{
		pathMatrix[depth][row][column] = data;
	}

	int getpathMatrix(int depth, int row, int column)
	{
		return pathMatrix[depth][row][column];
	}

	void setdistanceMatrix(int row, int column, int data)
	{
		distanceMatrix[row][column] = data;
	}

	int getdistanceMatrix(int row, int column)
	{
		return distanceMatrix[row][column];
	}

	void printDistanceMatrix()
	{
		for (int i = 0; i < distanceMatrix.size(); i++)
		{
			for (int j = 0; j < distanceMatrix.size(); j++)
			{
				cout << "distanceMatrix[" << i << "][" << j << "]=" << distanceMatrix[i][j] << "  ";
			}
			cout << endl;
		}
	}

	void printPath()
	{
		for (int i = 0; i < pathMatrix.size(); i++)
		{
			for (int j = 0; j < pathMatrix.size(); j++)
			{
				if (i != j)
				{
				    cout << "The shortest path of node " << i << " to node " << j << " is:";
				    for (int k = 0; k < pathMatrix.size(); k++)
				    {			    
				    	if (pathMatrix[k][i][j])
				    	{
				    		cout << " " << k << " ";
				    	}			    	
				    }
					cout << endl;
				}
			}
		}
	}
};



//图的数据结构
class MGraph
{
private:
	vector<vector<int>> adjMatrix;
	vector<int> nodeData;
	int nodeNum;
	
public:
	MGraph(int num)
	{
		nodeNum = num;
		nodeData = vector<int>(num,0);
		adjMatrix = vector<vector<int>>(num, vector<int>(num));
		for (int i = 0; i < num; i++)
		{
			for (int j = 0; j < num; j++)
			{
				if (i == j)
				{
					adjMatrix[i][j] = 0;
				}
				else
				{ 
					adjMatrix[i][j] = INFINITY;
				}
			}
		}
	}
	int getNodeNum()
	{
		return nodeNum;
	}
	void setNodeData(int data,int nodeIndex)
	{
		nodeData[nodeIndex] = data;
	}
	int getNodeData(int data, int nodeIndex)
	{
		return nodeData[nodeIndex];
	}
	void setWeight(int weight, int tail, int head)
	{
		adjMatrix[tail][head] = weight;
	}
	int getWeight(int tail, int head)
	{
		return adjMatrix[tail][head];
	}
	void printAdjMatrix()
	{
		for (int i = 0; i < nodeNum; i++)
		{
			for (int j = 0; j < nodeNum; j++)
			{
				cout << "adjMatrix["<<i<<"]["<<j<<"]=" << adjMatrix[i][j]<<"  ";
			}
			cout << endl;
		}
	}
};


shortestPathResult ShortestPathOfFloyd(MGraph g)
{
	shortestPathResult result(g.getNodeNum());
	for (int i = 0; i < g.getNodeNum(); i++)
	{
		for (int j = 0; j < g.getNodeNum(); j++)
		{
		    //初始化distanceMatrix和pathMatrix
			result.setdistanceMatrix(i, j, g.getWeight(i, j));
			if (result.getdistanceMatrix(i, j) < INFINITY)
			{
				result.setpathMatrix(i,i, j, 1);
				result.setpathMatrix(j,i, j, 1);
			}
		}
	}
	for (int i = 0; i < g.getNodeNum(); i++)
	{
		for (int j = 0; j < g.getNodeNum(); j++)
		{
			for (int k = 0; k < g.getNodeNum(); k++)
			{
				if (result.getdistanceMatrix(j, i) + result.getdistanceMatrix(i, k) < result.getdistanceMatrix(j, k))
				{   //更新distanceMatrix
					result.setdistanceMatrix(j, k, result.getdistanceMatrix(j, i) + result.getdistanceMatrix(i, k));
					for (int depth = 0; depth < g.getNodeNum(); depth++)
					{
					    //更新pathMatrix
						result.setpathMatrix(depth, j, k, (result.getpathMatrix(depth, j, i) || result.getpathMatrix(depth, i, k)));
					}
				}
			}
		}
	}
	return result;
}
//测试程序
int main()
{
	MGraph g(3);
	for (int nodeDataIndex = 0; nodeDataIndex < 3; nodeDataIndex++)
	{
		g.setNodeData(nodeDataIndex, nodeDataIndex);
	}
	g.setWeight(4, 0, 1);
	g.setWeight(11, 0, 2);
	g.setWeight(6, 1, 0);
	g.setWeight(2, 1, 2);
	g.setWeight(3, 2, 0);
	g.printAdjMatrix();
	shortestPathResult result = ShortestPathOfFloyd(g);
	result.printDistanceMatrix();
	result.printPath();
}

     这里提到 F l o y d Floyd Floyd算法不适用于含有权值之和为负的环的图。同时我在网上大概查了一下 D i j k s t r a Dijkstra Dijkstra算法好像也不适用于有负权值的边的图,不过这里我没有深入研究。还有一种求从一个顶点到其它各顶点的最短路径的算法( B e l l m a n – F o r d Bellman–Ford BellmanFord算法),该算法可以处理权值为负的边。也有对 B e l l m a n – F o r d Bellman–Ford BellmanFord算法进行改进了的 SPFA( S h o r t e s t P a t h F a s t e r A l g o r i t h m Shortest Path Faster Algorithm ShortestPathFasterAlgorithm)算法。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qqssss121dfd

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值