算法导论 单源最短路径

本章中有三个最短路径算法:

1、Bellman-Ford算法:解决的是一般情况下的单源最短路径问题,可适用于边的权重为负值,且有环路的情况,算法返回一个bool值,表明是否存在一个从源结点可以到达的权重为负值的环路。如果存在,则返回false,否则,可以求出最短路径和这条路径的权重。


2、Dag_Shortest_Paths: 解决有向无环图的单源最短路径问题,算法根据结点的拓扑排序对带权重的有向无环图G= (V, E)进行边的松弛操作,则可以在θ(V+E)的时间内计算出从单个源节点到所有结点之间的最短路径,允许有负权值的边存在。


3、Dijkstra算法:解决的是带权重的有向图,算法要去所有的权重大于0,运行时间小于Bellman-Ford算法,算法在运行过程中维持一组结点集合S,从源节点s到该集合中的每个结点之间的最短距离已经找到。算法重复从结点集V-S中选择最短路径估计中最小的结点u,将u加入集合S,然后对从u出发的边进松弛操作。


1、Bellman-Ford算法:(代码实现)

/**
Bellman-ford算法()
*/
#include <iostream>
using namespace std;
const int maxnum = 100;
const int maxint = 99999;  //表示不可达

// 边,
typedef struct Edge{
	int u, v;    // 起点,终点
	int weight;  // 边的权值
}Edge;

Edge edge[maxnum];     // 保存边的值
int  dist[maxnum];     // 结点到源点最小距离

int nodenum, edgenum, source;    // 结点数,边数,源点

// 初始化图
void init()
{
	cout << "请输入结点数,边数,源点:(三个正整数)" << endl;
	// 输入结点数,边数,源点
	cin >> nodenum >> edgenum >> source;
	for (int i = 1; i <= nodenum; ++i)
		dist[i] = maxint;    //刚开始结点的距离都设置为不可达
	dist[source] = 0;
	cout << "请输入源结点(u),目标结点(V),边的权重(weight):(三个数(权值可以为负值))" << endl;
	for (int i = 1; i <= edgenum; ++i)
	{
		cin >> edge[i].u >> edge[i].v >> edge[i].weight;
		if (edge[i].u == source)          //注意这里设置初始情况
			dist[edge[i].v] = edge[i].weight;
	}
}

// 松弛计算
void relax(int u, int v, int weight)
{
	if (dist[v] > dist[u] + weight)
		dist[v] = dist[u] + weight;
}
//BF算法主要的函数
bool Bellman_Ford()
{
	for (int i = 1; i <= nodenum - 1; ++i) //遍历所有点
	   for (int j = 1; j <= edgenum; ++j)  //遍历所有边(对每条边做松弛操作)
		  relax(edge[j].u, edge[j].v, edge[j].weight);
	bool flag = 1;
	// 判断是否有负环路
	for (int i = 1; i <= edgenum; ++i)
	{
		if (dist[edge[i].v] > dist[edge[i].u] + edge[i].weight)
		{
			flag = 0;
			break;
		}
	}
	return flag;
}
int main()
{
	
	init();
	if (Bellman_Ford())
	{
		cout << "源结点是:" << source << endl;
		cout << "源结点到各个结点(1,2,3,4,....)最短距离依次是:" << endl;
		for (int i = 1; i <= nodenum; i++)
		{   		
			cout <<dist[i]<<endl;
		}
	}
	return 0;
}
/**
例如算法导论:(图例子)
(5个点 10条边  1作为源结点)
  5  10  1   
  1  2   6
  1  5   7
  2  5   8
  2  3   5
  2  4   -4
  3  2   -2
  4  3   7 
  4  1   2
  5  4   9
  5  3   -3

 (5个点 10条边  2作为源结点)
  5  10  2    
  1  2   6
  1  5   7
  2  5   8
  2  3   5
  2  4   -4
  3  2   -2
  4  3   7
  4  1   2
  5  4   9
  5  3   -3

*/
运行结果如下:


2、Dag_Shortest_Paths+Floyd:(代码如下)

#include <iostream>  
using namespace std;

//枚举类型,图的种类 DG:有向图;WDG:带权值的有向图;  
//UDG: 无向图;WUDG: 带权值的无向图  
enum GraphKind { DG, WDG, UDG, WUDG };
const int SIZE = 5;              //定义二维数组的维度  
typedef int(*pArray)[SIZE];     //定义二维数组返回指针  

//vertexType顶点类型,VRType:顶点之间的关系类型,InfoType:弧的信息类型  
template <typename VertexType>
class MGraph
{
public:
	MGraph(int vexNum, GraphKind __kind) : vexnum(vexNum), arcnum(0), kind(__kind)
	{
		//分配顶点向量数组  
		vvec = new VertexType[vexnum];
		//动态分配二维数组, 注意二维数组的动态分配  
		arcs = new int *[vexnum];
		for (int i = 0; i < vexnum; i++)
		{
			//为每一行动态分配空间  
			arcs[i] = new int[vexnum];
		}
	}

	//初始化邻接矩阵  
	void InitArcs()
	{
		for (int i = 0; i < vexnum; i++)
		{
			for (int j = 0; j < vexnum; j++)
			{
				if ((kind == WUDG || WDG) && i != j)
					arcs[i][j] = INFINITE;
				else
					arcs[i][j] = 0;
			}
		}
	}

	void CreateWDG1()
	{
		cout << "构造402页 带权有向图...." << endl;
		//构造顶点数组  
		for (int i = 0; i < vexnum; i++)
		{
			vvec[i] = 'a' + 0;
		}
		InitArcs();

		//构造边  
		insertArc(0, 1, 3);
		insertArc(0, 2, 8);
		insertArc(0, 4, -4);
		insertArc(1, 3, 1);
		insertArc(1, 4, 7);
		insertArc(2, 1, 4);
		insertArc(3, 2, -5);
		insertArc(3, 0, 2);
		insertArc(4, 3, 6);
		cout << "带权有向图:" << endl;
	}

	//构造边  
	void insertArc(int vhead, int vtail, int weight)
	{
		arcs[vhead][vtail] = weight;
		arcnum++;
	}

	void displayGraph()
	{
		cout << "总共有" << vexnum << "个顶点,"
			<< arcnum << "条边" << endl;
		for (int i = 0; i < vexnum; i++)
		{
			cout << "第" << i + 1 << "个顶点是:" << vvec[i]
				<< "相邻的顶点有: ";
			for (int j = 0; j < vexnum; j++)
			{
				if (arcs[i][j] != INFINITE)
					cout << vvec[j] << "(" << arcs[i][j] << ") ";
			}
			cout << endl;
		}
		cout << "**********************************************" << endl;
	}

	/*******************************************************************
	带返回值的结点对最短路径算法,二维数组全部采用动态分配new的方式申请,所以在
	delete []之前,二维数组一直存在,所以可以当返回值传出去。
	*******************************************************************/
	int** externShortestPaths(int **L, int **W)
	{
		//分配Lnext数组, 根据L和arcs来计算出Lnext, 在L的基础上再多加一条边  
		int **Lnext = new int*[SIZE];
		for (int i = 0; i < SIZE; i++)
		{
			Lnext[i] = new int[SIZE];
		}

		for (int i = 0; i < vexnum; i++)
		{
			for (int j = 0; j < vexnum; j++)
			{
				Lnext[i][j] = INFINITE;
				for (int k = 0; k < vexnum; k++)
				{
					//Lnext[i][j]的值为L[i][k]每次加上一条边的权重的最小值  
					// 0 < k < vexnum,相当于将所有的边都加到原来的最小值上过一遍。  
					if (L[i][k] + W[k][j] < Lnext[i][j])
						Lnext[i][j] = L[i][k] + W[k][j];
				}
			}
		}
		return Lnext;
	}

	int** slowAllPairsShortestPaths()
	{
		cout << "slowAllPairsShortestPaths求出结点对之间的最短路径....." << endl;

		int **p;   //指向前一个二维数组  
		p = arcs;
		displayTwoDimArray(p);

		//递归求出具有m条边的最小权值  
		for (int m = 2; m < vexnum; m++)
		{
			int **Lm;
			Lm = externShortestPaths(p, arcs);
			p = Lm;
			displayTwoDimArray(p);
		}
		return p;
	}

	//通过使用重复平方来计算矩阵  
	int** fastAllPairsShortestPaths()
	{
		cout << "fastAllPairsShortestPaths求出结点对之间的最短路径....." << endl;

		int **p;   //指向前一个二维数组  
		p = arcs;
		displayTwoDimArray(p);

		//递归求出具有m条边的最小权值  
		for (int m = 2; m < vexnum; m++)
		{
			int **Lm;
			Lm = externShortestPaths(p, p);
			p = Lm;
			displayTwoDimArray(p);
		}
		return p;
	}

	/*************************************************************************
	Floyed算法:
	dk[i][j]:从结点i到结点j的所有中间结点全部取自于集合{1,2....k}的一条最短路径的权重
	arcs[i][j]        if k == 0
	dk[i][j] =
	min(d(k-1)[i][j], d(k-1)[i][k] + d(k-1)[k][j])  if (k >= 1)
	矩阵Dn = (d(n)[i][j])即为最后的答案

	关于pi的求法:
	if k == 0
	NULL      if i = j | arcs[i][j] = INFINITE
	pi(0) =
	i         if i != j && arcs[i][j] != INFINITE

	if k >= 1
	pi(k-1)[i][j]     if d(k-1)[i][j] <= d(k-1)[i][k] + d(k-1)[k][j]
	pi(k)[i][j] =
	pi(k-1)[k][j]     if d(k-1)[i][j] > d(k-1)[i][k] + d(k-1)[k][j]
	**************************************************************************/
	int** FloydWarshall()
	{
		int i, j, k;
		int **p = arcs;
		/*int **parr[SIZE+1];*/
		/*parr[0] = p;*/
		cout << "FloydWarshall初始的权重矩阵:" << endl;
		displayTwoDimArray(p);
		int **pi = new int *[SIZE];
		for (i = 0; i < SIZE; i++)
		{
			pi[i] = new int[SIZE];
		}
		//当k == 0时,初试化pi(0)  
		for (i = 0; i < SIZE; i++)
		{
			for (j = 0; j < SIZE; j++)
			{
				if (i == j || arcs[i][j] == INFINITE)
					pi[i][j] = NULL;
				else
					pi[i][j] = i + 1;
			}
		}
		cout << "d:" << endl;
		displayTwoDimArray(p);
		cout << "pi:" << endl;
		displayTwoDimArray(pi);

		for (k = 1; k <= SIZE; k++)
		{
			//构造D[k]和Pi[k]  
			int **dk = new int *[SIZE];
			for (i = 0; i < SIZE; i++)
				dk[i] = new int[SIZE];
			int **pii = new int *[SIZE];
			for (i = 0; i < SIZE; i++)
				pii[i] = new int[SIZE];

			for (i = 0; i < SIZE; i++)
			{
				for (j = 0; j < SIZE; j++)
				{
					if (p[i][j] <= p[i][k - 1] + p[k - 1][j])
					{
						dk[i][j] = p[i][j];
						pii[i][j] = pi[i][j];
					}
					else
					{
						dk[i][j] = p[i][k - 1] + p[k - 1][j];
						pii[i][j] = pi[k - 1][j];
					}
				}
			}
			/*parr[k] = dk;*/
			p = dk;
			pi = pii;
			cout << "d:" << endl;
			displayTwoDimArray(p);
			cout << "pi:" << endl;
			displayTwoDimArray(pi);
		}
		return p;
	}

	//输出一个二维数组  
	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;
	}

	/*******************************************************************
	不带返回值的结点对最短路径算法,二维数组是直接定义,属于局部定义,而要求
	的矩阵也都是通过参数传递,而不是返回值,因为返回值不能返回一个局部的数组
	*******************************************************************/
	void externShortestPaths1(int(*L)[SIZE], int(*Lnext)[SIZE], int(*W)[SIZE])
	{
		//Lnext数组, 根据L和arcs来计算出Lnext, 在L的基础上再多加一条边  
		for (int i = 0; i < vexnum; i++)
		{
			for (int j = 0; j < vexnum; j++)
			{
				if (i == j)
					Lnext[i][j] = 0;
				else
					Lnext[i][j] = INFINITE;
				for (int k = 0; k < vexnum; k++)
				{
					//Lnext[i][j]的值为L[i][k]每次加上一条边的权重的最小值  
					// 0 < k < vexnum,相当于将所有的边都加到原来的最小值上过一遍。  
					if (L[i][k] + W[k][j] < Lnext[i][j])
						Lnext[i][j] = L[i][k] + W[k][j];
				}
			}
		}
	}

	void slowAllPairsShortestPaths1()
	{
		cout << "slowAllPairsShortestPaths1求出结点对之间的最短路径....." << endl;

		int(*p)[SIZE];
		int L1[SIZE][SIZE];
		for (int i = 0; i < vexnum; i++)
		for (int j = 0; j < vexnum; j++)
			L1[i][j] = arcs[i][j];
		p = L1;
		displayTwoDimArray1(p);

		//递归求出具有m条边的最小权值  
		for (int m = 2; m < vexnum; m++)
		{
			int Lm[SIZE][SIZE];
			externShortestPaths1(p, Lm, L1);
			p = Lm;
			displayTwoDimArray1(p);
		}
	}

	void fastAllPairsShortestPaths1()
	{
		cout << "fastAllPairsShortestPaths1求出结点对之间的最短路径....." << endl;

		int(*p)[SIZE];
		int L1[SIZE][SIZE];
		for (int i = 0; i < vexnum; i++)
		for (int j = 0; j < vexnum; j++)
			L1[i][j] = arcs[i][j];
		p = L1;
		displayTwoDimArray1(p);

		//递归求出具有m条边的最小权值  
		for (int m = 1; m < vexnum - 1; m *= 2)
		{
			int Lm[SIZE][SIZE];
			externShortestPaths1(p, Lm, p);
			p = Lm;
			displayTwoDimArray1(Lm);
		}
	}

	//输出一个二维数组,参数为指向二维数组的指针  
	void displayTwoDimArray1(int(*p)[SIZE])
	{
		for (int i = 0; i < SIZE; i++)
		{
			for (int j = 0; j < SIZE; j++)
				cout << p[i][j] << " ";
			cout << endl;
		}
		cout << "~~~~~~~~~~~~~~~" << endl;
	}

private:
	static const int INFINITE = 1000;     //如果两个顶点之间可不达,则为该值  

	VertexType *vvec;          //顶点向量  
	int **arcs;                //邻接矩阵, 存放顶点关系,对带权图,为边权值  
	//对于无权图,用1或0表示,表示相邻与否;  
	int vexnum;                //图的当前顶点个数  
	int arcnum;                //图的弧数  
	GraphKind kind;            //图的种类标志  
	//const int SIZE;   //邻接矩阵的维度  
};
int main()
{

	MGraph<char> wdgGraph(5, WDG);
	wdgGraph.CreateWDG1();
	wdgGraph.displayGraph();
	wdgGraph.slowAllPairsShortestPaths();
	wdgGraph.slowAllPairsShortestPaths1();
	wdgGraph.fastAllPairsShortestPaths();
	wdgGraph.fastAllPairsShortestPaths1();
	wdgGraph.FloydWarshall();

	system("pause");
	return 0;
}
运行结果如下:



3、Dijkstra算法:(代码如下)

//Single_shortestPath.cpp : 定义控制台应用程序的入口点。
//Dijstra算法代码实现:
//Dijstra.cpp : 定义控制台应用程序的入口点。

#include "stdafx.h"
#include <iostream>
#include<stack>
#define M 100
#define N 100
using namespace std;

//定义节点的数据类型
typedef struct node
{
	int matrix[N][M];      //邻接矩阵 
	int n;                 //顶点数 
	int e;                 //边数 
}MGraph;

//dijsktra主要的函数
void DijkstraPath(MGraph g, int *dist, int *path, int v0)   //v0表示源顶点 
{
	int i, j, k;
	bool *visited = (bool *)malloc(sizeof(bool)*g.n);
	for (i = 0; i<g.n; i++)     //初始化 
	{
		if (g.matrix[v0][i]>0 && i != v0)
		{
			dist[i] = g.matrix[v0][i];
			path[i] = v0;     //path记录最短路径上从v0到i的前一个顶点 
		}
		else
		{
			dist[i] = INT_MAX;    //若i不与v0直接相邻,则权值置为无穷大 
			path[i] = -1;
		}
		visited[i] = false;
		path[v0] = v0;
		dist[v0] = 0;
	}
	visited[v0] = true;
	for (i = 1; i < g.n; i++)     //循环扩展n-1次 
	{
		int min = INT_MAX;
		int u;
		for (j = 0; j < g.n; j++)    //寻找未被扩展的权值最小的顶点 
		{
			if (visited[j] == false && dist[j] < min)
			{
				min = dist[j];
				u = j;
			}
		}
		visited[u] = true;
		for (k = 0; k<g.n; k++)   //更新dist数组的值和路径的值 (松弛操作)
		{
			if (visited[k] == false && g.matrix[u][k]>0 && min + g.matrix[u][k] < dist[k])
			{
				dist[k] = min + g.matrix[u][k];
				path[k] = u;
			}
		}
	}
}

//打印最短路径上的各个顶点 
void showPath(int *path, int v, int v0)
{
	stack<int> s;
	int u = v;
	while (v != v0)
	{
		s.push(v);
		v = path[v];
	}
	s.push(v);
	while (!s.empty())
	{
		if (!s.empty())
		{
			cout << s.top() << "-> ";
			s.pop();
		}
	}
}

//主函数
int _tmain(int argc, _TCHAR* argv[])
{

	int n, e;     //表示输入的顶点数和边数 
	while (cin >> n >> e&&e != 0)
	{
		int i, j;
		int s, t, w;      //表示存在一条边s->t,权值为w
		MGraph g;
		int v0;
		int *dist = (int *)malloc(sizeof(int)*n);
		int *path = (int *)malloc(sizeof(int)*n);
		for (i = 0; i < N; i++)
		for (j = 0; j < M; j++)
			g.matrix[i][j] = 0;
		g.n = n;
		g.e = e;
		for (i = 0; i < e; i++)
		{
			cin >> s >> t >> w;
			g.matrix[s][t] = w;
		}
		cin >> v0;        //输入源顶点 
		DijkstraPath(g, dist, path, v0);
		for (i = 0; i < n; i++)
		{
			if (i != v0)
			{
				showPath(path, i, v0);
				cout << "最短距离为:" << dist[i] << endl;
			}
		}
	}
	        return 0;
}

//
//输入数据如下:构造5个节点,7条边的有向图
//5 7
//0 1 100
//0 2 30
//0 4 10
//2 1 60
//2 3 60
//3 1 10
//4 3 50
//0



//运行结果:



  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Dijkstra算法是一种用于求解单源最短路径的经典算法。它以一个源顶点为起始点,通过不断扩展最短路径树来寻找到其他所有顶点的最短路径。下面是Dijkstra算法的思路和步骤。 首先,我们需要定义一个顶点集合,用于存放已经求得最短路径的顶点。算法开始时,所有顶点都被标记为未被访问,并且它们的最短路径长度都初始化为无穷大。 然后,我们从起始顶点开始,将其最短路径长度置为0,并将其加入到已求得最短路径的集合中。此外,我们还需要更新起始顶点的邻居顶点的最短路径长度。 接下来,我们进入循环,不断选择最短路径长度最小的顶点,将其加入到已求得最短路径的集合中。然后,更新该顶点的邻居顶点的最短路径长度。具体的更新方式是,如果通过当前选中的顶点访问邻居顶点的路径长度比已知的最短路径长度小,那么更新邻居顶点的最短路径长度。 最后,当所有顶点都被加入到已求得最短路径的集合中,或者存在无穷大的路径时,算法结束。此时,我们得到了从起始顶点到其他所有顶点的最短路径长度。 Dijkstra算法的时间复杂度为O(V^2),其中V为图中顶点的数量。此外,它还可以通过使用优先队列来优化,将时间复杂度降低到O((V+E)logV),其中E为图中边的数量。 总之,Dijkstra算法是一种求解单源最短路径的有效算法,它可以应用于各种实际问题中,如路由选择、网络通信、物流规划等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值