最短路径——Dijkstra算法和Floyd算法

一、最短路径概念

        图中一个顶点到另一个顶点可能存在多条路径,每条路径所经过边的数量可能不同,每条路径所经过边的权值也可能不同,我们把花费最小的路径成为两个顶点的最短路径。

        最短路径相关的两种常用算法:迪克斯特拉(Dijstra)算法和弗洛伊德(Floyd)算法。Dijstra算法用于快速求得图中一个顶点到其他所有顶点的最短距离和路径,Floyd算法用于求图中每对顶点的最短路径。

二、应用

        最短路径问题已经应用在了诸多领域,如地图导航,公路建设,路由器寻址等等。

三、Dijstra算法和Floyd算法的具体实现

(1)Dijstra算法

        Dijstra算法的基本思想是:将图中的顶点集合分成两组,第1组为已求出最短路径的顶点集合S,第2组为其余未确定最短路径的顶点集合U。然后从一个已知的顶点k开始,寻找离k最近的顶点imin,然后把顶点imin加入到第1组顶点集合S中,如果以顶点imin作为中间点到其他顶点的距离更短,则设置imin最为路径的中间点,并更新k到其他顶点的最短距离,重复寻找最近顶点imin直至所有顶点都加入到集合S中。

        其执行步骤如下:

将图中的顶点集合分成两组,第1组为已求出最短路径的顶点集合S,第2组为其余未确定最短路径的顶点集合U。

②将已知的起始点k加入S中,并初始化k到其他顶点的最短距离为有向图相关边的权值(若不存在边,设置距离为无穷大)。

③重复步骤④,直至所有顶点都加入到集合S中。

寻找离k最近的顶点imin,然后把顶点imin加入到第1组顶点集合S中,如果以顶点imin作为中间点到其他顶点的距离更短,则设置imin最为路径的中间点,并更新k到其他顶点的最短距离。

        C++代码实现:

        图的邻接矩阵声明:

#define N 100

typedef char ElemType;

/*
图的邻接矩阵声明
*/
typedef struct _MGraph
{
	int edges[N][N]; //边集合
	int n; //顶点数
}MGraph;

        Dijstra算法:

/*
迪克斯特拉算法
g存储有向图的边
k代表出发的顶点
path[i]保存第i个顶点的上一个顶点
dis[i]保存从k出发到顶点i的最短距离
*/
void Dijkstra(MGraph &g, int k, int path[], int dis[])
{
	int* visited = new int[g.n](); //存储顶点是否被访问过,初始化为0
	for (int i = 0; i < g.n; i++) 
	{
		dis[i] = g.edges[k][i]; //初始化最短距离数组
		path[i] = k; //初始化路径数组
	}
	visited[k] = 1;
	dis[k] = 0;
	for (int cnt = 1; cnt < g.n; cnt++) //循环n-1次
	{
		int imin = -1; //存储最短边的下标
		for (int i = 0; i < g.n; i++) //寻找没访问过的最短边
		{
			if (!visited[i] && (imin == -1 || dis[i] < dis[imin])) 
				imin = i;
		}
		visited[imin] = 1;
		for (int i = 0; i < g.n; i++) //如果新的顶点到其他顶点的距离更短,更新最短距离和路径
		{
			if (!visited[i] && dis[imin] + g.edges[imin][i] < dis[i])
			{
				dis[i] = dis[imin] + g.edges[imin][i];
				path[i] = imin;
			}
		}
	}
	delete[] visited; //记得释放内存
}

        根据Dijstra算法生成的path数组输出路径:

/*
输出从开始顶点到顶点k的最短路径
*/
void DisplayPath(int k, int path[])
{
	stack s;
	while (path[k] != k)
	{
		s.push(k);
		k = path[k];
	}
	s.push(k);
	int cnt = 0;
	while (!s.empty())
	{
		if (cnt++ > 0) cout << "->";
		cout << s.top();
		s.pop();
	}
	cout << endl;
}

        Dijstra算法包含了两重for循环,其时间复杂度为O(n²)。

(2)Floyd算法

        Floyd算法的基本思想是:用一个二维数组dis来保存每对顶点之间的最短路径长度,即dis[i][j]表示从顶点i到顶点j的最短路径长度,dis数组初始化为图的邻接矩阵数组dis[i][j] = g.edges[i][j]。从顶点k=0开始,将k作为中间节点,如果顶点i以k最为中间节点到达顶点j的距离更短,则设置k为中间节点,并更新i到j的最短路径。将k的值加1,重复选取中间节点直到所有顶点都被假设为中间节点为止。

        其执行步骤如下:

①用一个二维数组dis来保存每对顶点之间的最短路径长度,即dis[i][j]表示从顶点i到顶点j的最短路径长度,dis数组初始化为图的邻接矩阵数组dis[i][j] = g.edges[i][j]。

②从顶点k=0开始,将k假设为中间节点,重复步骤③直至所有顶点都被假设为中间节点为止。

③如果顶点i以k最为中间节点到达顶点j的距离更短,则设置k为中间节点,并更新i到j的最短路径。

        C++代码实现:

        Floyd算法:

/*
弗洛伊德算法
g存储有向图的边
dis[i][j]存储顶点i到顶点j的最短距离长度
path[i][j]存储顶点j的上一个顶点
*/
void Floyd(MGraph& g, int dis[][N], int path[][N])
{
	for (int i = 0; i < g.n; i++)
	{
		for (int j = 0; j < g.n; j++)
		{
			dis[i][j] = (i == j ? 0 : g.edges[i][j]); //初始化距离数组
			path[i][j] = i; //初始化路径数组
		}
	}
	for (int k = 0; k < g.n; k++)
	{
		for (int i = 0; i < g.n; i++)
		{
			for (int j = 0; j < g.n; j++)
			{
				if (dis[i][k] + dis[k][j] < dis[i][j]) //如果以K为中间点距离更短,更新距离数组和路径数组
				{
					dis[i][j] = dis[i][k] + dis[k][j];
					path[i][j] = path[k][j];
				}
			}
		}
	}
}

        根据Floyd生成的path数组输出最短路径:

/*
输出每对顶点的最短路径
*/
void DisplayPath(int n, int path[][N], int dis[][N])
{
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < n; j++)
		{
			cout << i << "-" << j << "最短路径长度:" << dis[i][j] << " 最短路径:";
			DisplayPath(j, path[i]);
		}
	}
}

        Floyd算法包含三重for循环,其时间复杂度为O(n³),相当于以每个顶点作为源点调用Dijstra算法的时间复杂度,但Floyd算法代码实现简单。

四、测试

        问题:

        输入一个有向图,求顶点0到其他顶点的最短路径长度和最短路径,以及每对顶点的最短路径长度和最短路径。

        样例输入:

        4 8
        0 1 5
        0 3 7
        1 2 4
        1 3 2
        2 0 3
        2 1 3
        2 3 2

        3 2 1

        样例输出:

        0-0最短路径长度:0 最短路径:0
        0-1最短路径长度:5 最短路径:0->1
        0-2最短路径长度:8 最短路径:0->3->2
        0-3最短路径长度:7 最短路径:0->3
        0-0最短路径长度:0 最短路径:0
        0-1最短路径长度:5 最短路径:0->1
        0-2最短路径长度:8 最短路径:0->3->2
        0-3最短路径长度:7 最短路径:0->3
        1-0最短路径长度:6 最短路径:1->3->2->0
        1-1最短路径长度:0 最短路径:1
        1-2最短路径长度:3 最短路径:1->3->2
        1-3最短路径长度:2 最短路径:1->3
        2-0最短路径长度:3 最短路径:2->0
        2-1最短路径长度:3 最短路径:2->1
        2-2最短路径长度:0 最短路径:2
        2-3最短路径长度:2 最短路径:2->3
        3-0最短路径长度:4 最短路径:3->2->0
        3-1最短路径长度:4 最短路径:3->2->1
        3-2最短路径长度:1 最短路径:3->2
        3-3最短路径长度:0 最短路径:3

#include 
#include 
using namespace std;

#define N 100

typedef char ElemType;

/*
图的邻接矩阵声明
*/
typedef struct _MGraph
{
	int edges[N][N]; //边集合
	int n; //顶点数
}MGraph;

/*
迪克斯特拉算法
g存储有向图的边
k代表出发的顶点
path[i]保存第i个顶点的上一个顶点
dis[i]保存从k出发到顶点i的最短距离
*/
void Dijkstra(MGraph &g, int k, int path[], int dis[])
{
	int* visited = new int[g.n](); //存储顶点是否被访问过,初始化为0
	for (int i = 0; i < g.n; i++) 
	{
		dis[i] = g.edges[k][i]; //初始化最短距离数组
		path[i] = k; //初始化路径数组
	}
	visited[k] = 1;
	dis[k] = 0;
	for (int cnt = 1; cnt < g.n; cnt++) //循环n-1次
	{
		int imin = -1; //存储最短边的下标
		for (int i = 0; i < g.n; i++) //寻找没访问过的最短边
		{
			if (!visited[i] && (imin == -1 || dis[i] < dis[imin])) 
				imin = i;
		}
		visited[imin] = 1;
		for (int i = 0; i < g.n; i++) //如果新的顶点到其他顶点的距离更短,更新最短距离和路径
		{
			if (!visited[i] && dis[imin] + g.edges[imin][i] < dis[i])
			{
				dis[i] = dis[imin] + g.edges[imin][i];
				path[i] = imin;
			}
		}
	}
	delete[] visited; //记得释放内存
}

/*
输出从开始顶点到顶点k的最短路径
*/
void DisplayPath(int k, int path[])
{
	stack s;
	while (path[k] != k)
	{
		s.push(k);
		k = path[k];
	}
	s.push(k);
	int cnt = 0;
	while (!s.empty())
	{
		if(cnt++ > 0) cout << "->";
		cout << s.top();
		s.pop();
	}
	cout << endl;
}

/*
弗洛伊德算法
g存储有向图的边
dis[i][j]存储顶点i到顶点j的最短距离长度
path[i][j]存储顶点j的上一个顶点
*/
void Floyd(MGraph& g, int dis[][N], int path[][N])
{
	for (int i = 0; i < g.n; i++)
	{
		for (int j = 0; j < g.n; j++)
		{
			dis[i][j] = (i == j ? 0 : g.edges[i][j]); //初始化距离数组
			path[i][j] = i; //初始化路径数组
		}
	}
	for (int k = 0; k < g.n; k++)
	{
		for (int i = 0; i < g.n; i++)
		{
			for (int j = 0; j < g.n; j++)
			{
				if (dis[i][k] + dis[k][j] < dis[i][j]) //如果以K为中间点距离更短,更新距离数组和路径数组
				{
					dis[i][j] = dis[i][k] + dis[k][j];
					path[i][j] = path[k][j];
				}
			}
		}
	}
}

/*
输出每对顶点的最短路径
*/
void DisplayPath(int n, int path[][N], int dis[][N])
{
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < n; j++)
		{
			cout << i << "-" << j << "最短路径长度:" << dis[i][j] << " 最短路径:";
			DisplayPath(j, path[i]);
		}
	}
}

int main()
{
	MGraph g;
	while (cin >> g.n)
	{
		for (int i = 0; i < g.n; i++)
			for (int j = 0; j < g.n; j++)
				g.edges[i][j] = INT16_MAX;
		int m, u, v, w;
		cin >> m;
		while (m-- > 0)
		{
			cin >> u >> v >> w;
			g.edges[u][v] = w;
		}
		//Dijkstra
		{
			int* path = new int[g.n];
			int* dis = new int[g.n];
			Dijkstra(g, 0, path, dis);
			for (int i = 0; i < g.n; i++)
			{
				cout << 0 << "-" << i << "最短路径长度:" << dis[i] << " 最短路径:";
				DisplayPath(i, path);
			}
			delete[] path, dis;
		}
		//Floyd
		{
			int path[N][N], dis[N][N];
			Floyd(g, dis, path);
			DisplayPath(g.n, path, dis);
		}
	}
	return 0;
}

参考文献

[1] 李春葆.数据结构教程.清华大学出版社,2013.

你可能感兴趣的:(数据结构和算法)