一:迪杰斯特拉(Dijkstra)算法

       Dijkstra算法是单源最短路算法。他能求出一个点到其他点的最短距离。这个点叫做源点;

他的主要思路就是将所有顶点分为两部分,一部分是点集合是Q,剩下部分点集合是P;其中Q集合的点到源点的最短距离

已经求出来,P集合是待求到源点最短距离的点集合;

Dijkstra就是对这两个集合进行操作。

我们假定源点是S.

第一步:我们将两个集合初始化,Q集合只有S点,P集合是除S点剩下的点。因为S点到S点的最短距离已经确定,是0;

第二步:在P集合选取出一个距离S点最近的一个点,然后通过这个点对S点和其他点进行松弛;

第三步:重复第二步,直到这个过程进行了n-1次;

代码如下(复杂度为n^2):

#include<stdio.h>
#include<string.h>
#define mmset(a,b) memset(a,b,sizeof(a))
const int N = 1005;
const int INF = 1e9;
int book[N] ; //用来标记该点是在Q集合还是P集合。
int dis[N];  //用来储存源点到其他点的最短距离
int map[N][N] ;  //邻接矩阵的存图方式;


/*
4 8 1
1 2 2
1 3 6
1 4 4
2 3 3
3 1 7
3 4 1
4 1 5
4 3 12
*/
int main()
{
	int n,m,s;
	
	scanf("%d %d %d",&n, &m, &s);
	{
		for(int i = 1; i <= n; i++)
		{
			for(int j = 1; j <= n; j++)
			{
				if(i == j)
				map[i][j] = 0;
				else
				map[i][j] = INF;
			}
			
		}
		for(int i = 1; i <= m; i++)
		{
			int x,y,c;
			scanf("%d %d %d",&x,&y, &c);
			map[x][y] = c; // 假定输入的是一个有向图; 
		}
	}
	
	for(int i = 1; i <= n; i++)
	dis[i] = map[s][i];
	dis[s] = 0, book[s] = 1;
	
	for(int i = 1; i <= n-1; i++)
	{
		int min = INF, u = 0;
		for(int j = 1; j <= n; j++) if(book[j] == 0 && dis[j] < min)
		{
			min = dis[j];     //P集合选取出一个距离S点最近的一个点
			u = j;
		} 
		book[u] = 1;  
		
		for(int v = 1; v <= n; v++) if(dis[v] > dis[u] + map[u][v] && map[u][v] < INF)
		{
			dis[v] = dis[u] + map[u][v];		//通过u点对S点和其他点进行松弛
		}
	}
	for(int i = 1; i <= n; i++)
	{
		printf("%d ",dis[i]);
	}
	printf("\n");
	
	
	return 0;
} 

Dijkstra算法在选取离S点最近的一个点的时候可以用优先队列优化,代码如下(复杂度为n*Log(n) ):

#include<stdio.h>
#include<string.h>
#include<queue>
#define mmset(a,b) memset(a,b,sizeof(a)) 
using namespace std;
const int N = 1005;
int INF = 1e9;

struct node //优先队列的元素,num代表该点,w表示s到点num的距离 
{
	int num,w;
	bool operator < (const node b) const  //重载< 
	{
		return w > b.w;
	}
};

int n,m,s;

int map[N][N],dis[N];
int main()
{
	mmset(map,54);
	INF = map[0][0];
	scanf("%d %d %d",&n,&m,&s);
	
	for(int i = 1; i <= m; i++)
	{
		int x,y,c;
		scanf("%d %d %d",&x,&y,&c);
		map[x][y] = c;
	}
	
	priority_queue <node> que;
	for(int i = 1; i <= n; i++) if(i != s)
	{
		dis[i] = map[s][i];
		node temp ;
		temp.num = i, temp.w = dis[i];
		que.push(temp);
	}
	dis[s] = 0;
	
	for(int i = 1; i <= n - 1; i++)
	{
		node  p = que.top();
		que.pop();
		int u = p.num;
		
		for(int v = 1; v <= n; v++) if(dis[v] > dis[u] + map[u][v] && map[u][v] < INF)
		{
			dis[v] = dis[u] + map[u][v];
			node temp;
			temp.num = v, temp.w = dis[v];
			que.push(temp);
		}
	}
	for(int i = 1; i <= n; i++)
	{
		printf("%d ",dis[i]);
	}
	printf("\n");
	
	
	
	return 0;
}

迪杰斯特拉队列优化 + 打印路径:

#include<stdio.h>
#include<string.h>
#include<queue>
#define mmset(a,b) memset(a,b,sizeof(a)) 
using namespace std;
const int N = 1005;
int INF = 1e9;
 
struct node //优先队列的元素,num代表该点,w表示s到点num的距离 
{
	int num,w;
	bool operator < (const node b) const  //重载< 
	{
		return w > b.w;
	}
};
 
int n,m,s;

int map[N][N],dis[N], path[N];     //path记录路径 

void printfPath(int p);
int main()
{
	mmset(map,54);
	mmset(path,-1) ;
	INF = map[0][0];
	scanf("%d %d %d",&n,&m,&s);
	
	for(int i = 1; i <= m; i++)
	{
		int x,y,c;
		scanf("%d %d %d",&x,&y,&c);
		map[x][y] = c;
	}
	
	priority_queue <node> que;
	for(int i = 1; i <= n; i++) if(i != s)
	{
		dis[i] = map[s][i];
		node temp ;
		temp.num = i, temp.w = dis[i];
		que.push(temp);
	}
	dis[s] = 0;
	
	for(int i = 1; i <= n - 1; i++)
	{
		node  p = que.top();
		que.pop();
		int u = p.num;
		
		for(int v = 1; v <= n; v++) if(dis[v] > dis[u] + map[u][v] && map[u][v] < INF)
		{
			path[v] = u;     //s到v点可以通过u点松弛,说明s到v点一定经过u点 
			dis[v] = dis[u] + map[u][v];
			node temp;
			temp.num = v, temp.w = dis[v];
			que.push(temp);
		}
	}
	for(int i = 1; i <= n; i++)
	{
		printf("%d ",dis[i]);
	}
	printf("\n");
	
	
	for(int i = 1; i <= n; i++)
	{
		printfPath(i);  //输出源点到i点的路径 
		printf("\n");
	}
	return 0;
}


void printfPath(int p)
{
	if(p == -1)
	{
		printf("%d ",s);
		return ;
	}
	else
	{
		printfPath(path[p]);
		printf("%d ",p);
	}
}

 



二.Spfa算法:

    Spfa算法同Dijkstra一样,也是求单源最短路的算法;

他的主要算法分为以下几步;

第一步:将源点S加入队列。

第二步:将队列的队首弹出来,假设队首点为now,now连接的点为Pj。通过now松弛S点和Pj。

               如果能松弛就更新S点到Pj的距离。如果Pj没在队列中,就把他加入队列中。

第三步:重复第二步,知道队列为空;

代码如下(复杂度为n*m)

#include<stdio.h>
#include<string.h>
#include<queue> 
#define mmset(a,b) memset(a,b,sizeof(a))
using namespace std;
const int N = 1005;
int INF = 1e9;
struct node
{
	int to,w,next;
};

node edges[N*N];
int head[N], dis[N], book[N];
int n,m,s ,index = 1;

void addEdge(int x,int y,int c)
{
	edges[index].to = y,edges[index].w = c;
	edges[index].next = head[x];
	head[x] = index;
	index++;
}


int main()
{
	scanf("%d %d %d",&n,&m,&s);
	
	mmset(head,-1);
	mmset(book,0);
	mmset(dis,54);
	INF = dis[0];
	index = 1;
	for(int i  = 1; i <= m; i++)
	{
		int x,y,c;
		scanf("%d %d %d",&x,&y,&c);
		addEdge(x,y,c);      //链式前向星建图 
	}
	
	dis[s] = 0;         //初始化dis数组,注意这与Dijkstra不一样 
	queue <int> que;
	que.push(s);
	
	while(!que.empty())
	{
		int now = que.front();
		book[now] = 0;
		que.pop();
		for(int i = head[now]; i != -1; i = edges[i].next)
		{
			int  to = edges[i].to;
			int w = edges[i].w;
			if(dis[to] > dis[now] + w)
			{
				dis[to] = dis[now] + w;
				if(book[to] == 0)     //判断to点是否在队列中 
				{
					que.push(to);
					book[to] = 1;
					
				}
			}
		}
	}
	
	for(int i = 1; i <= n; i++)
	{
		printf("%d ",dis[i]);
	}
	printf("\n");
		
		
	
	

	
	
	
	return 0;
}

 

spfa + 打印路径

#include<stdio.h>
#include<string.h>
#include<queue>
#define mmset(a,b) memset(a,b,sizeof(a)) 
using namespace std;
const int N = 1005;
int INF = 9999999;

struct node
{
	int to,w,next;
};

node edges[N*N];
int head[N], path[N],dis[N],book[N]; //path记录路径 
int n,m,s,index = 1;


void addEdge(int x,int y,int c)
{
	edges[index].to = y, edges[index].w = c;
	edges[index].next = head[x];
	head[x] = index++;
}
void printfPath(int p);
int main()
{
	scanf("%d %d %d",&n,&m,&s);
	index = 1;
	mmset(head,-1);
	for(int i = 1; i <= m; i++)
	{
		int x,y,c;
		scanf("%d %d %d",&x,&y,&c);
		addEdge(x,y,c);
	}
	
	mmset(dis,54);
	INF = dis[0];
	dis[s] = 0;
	
	queue <int> que;
	que.push(s);
	
	while(!que.empty())
	{
		int now = que.front();
		que.pop();
		book[now] = 0;
		for(int i = head[now]; i != -1; i = edges[i].next)
		{
			int to = edges[i].to;
			int w = edges[i].w;
			if(dis[to] > dis[now] + w)
			{
				path[to] = now;
				dis[to] = dis[now] + w;
				if(book[to] == 0)
				{
					book[to] = 1;
					que.push(to);
				}
			}
		}
	}
	for(int i = 1; i <= n; i++)
	{
		printf("%d ",dis[i]) ;  //输出源点到i点的最短路的长度 
	}
	printf("\n");
	
	for(int i = 1; i <= n; i++)
	{
		printfPath(i);  //输出源点到i点的路径 
		printf("\n");
	}
	
	return 0;
} 

void printfPath(int p)
{
	/*
	这点值得注意,因为最先开始从源点松弛 ,所以找的源点就开始往回回溯。
	在迪杰斯特拉中,因为初始化dis时,就把源点到i点的距离已经给初始化了。所以源点并没有参与
	松弛。随意在迪杰斯特拉中回溯到最开始的时候(也就是path==-1)时开始往后回溯。 
	*/ 
	if(p == s)
	{
		printf("%d ",s);
		return ;
	}
	else
	{
		printfPath(path[p]);
		printf("%d ",p);
	}
}

总结一下:

Djikstra适用于稠密图,Spfa适用于稀疏图