迪杰斯特拉(Dijkstra) —— 最短路算法

Dijkstra是最短路基础算法之一(还有判负环的SPFA和多源最短路的Floyd),但在正常情况下Dijkstra是最快的,也同样是最难打的(其实都不是很难),接下来我们来谈谈具体算法:

1.适用范围:没有负环(就是走一圈就可以将路程变小,没有最短路的图)的单源最短路(就是只有一个起点的最短路);

2.思路:

已知量只有每条边的权值,但我们可以很容易的想到起点到起点到起点的最短路是0,于是我们可以一开始就将定义一个dij数组来存从起点到每个点的最短路的长度,所以自然dij[1](假设1是起点)就为0,而想要更新其他的就要将除1以外的赋值为MAXN;

因为没有负的权值,所以很容易证明在已有的dij数组中的最小值是不会再被其他点更新的,所以每一次我们都将已有的最短的那个数对应的节点取出,再由它来更新它所连接的点(如果到最小值的那个点的值加上权值依然小于它所连接的那个节点的已有的最小值就更新它),同样不能忘了将这个最小值的点冻结(开个bool数组)以免重复计算;

由于每次取出一个点又冻住那个点,我们不难想到在有n个点的情况下只需要循环n次就行了;

3.操作:

输入:先用邻接表)存下图(注意是单向图还是双向),记录下起点终点;

初值:先memset(dij,0x3f,sizeof(dij)),再将dij[1]赋值为0;

Dijkstra:用一个循环n次的for循环来做取点以及冻结的操作,每次取出最小的点再循环寻找每一个与它相连的节点并更新需要更新的节点;

输出:按题目要求输出;

特殊处理(输出路径):既然都已经找到最短路了那么路径只需要再另开一个数组在更新最短路长度的同时记录是哪一个点更新的它就行了,在最后再用倒推的方式输出即可(可以类比背包问题找方案的方法);

4.例题:

骑车比赛

Description

小信准备去参加骑车比赛,比赛在 n 个城市间进行,编号从 1 到 n。选手们都从城市 1 出发,终点在城市 n。

已知城市间有 m 条道路,每条道路连接两个城市,注意道路是双向的。现在小信知道了他经过每条道路需要花费的时间,他想请你帮他计算一下,他这次比赛最少需要花多少时间完成。

Input

第一行输入两个整数 n,m(1≤n≤1,000,1≤m≤5,000),分别代表城市个数和道路总数。接下来输入 m 行,每行输入三个数字 a,b,c(1≤a,b≤n,1≤c≤200),分别代表道路的起点和道路的终点,以及小信骑车通过这条道路需要花费的时间。保证输入的图是连通的。

Output

输出一行,输出一个整数,输出小信完成比赛需要的最少时间。

Sample Input 1

5 6
1 2 2
2 3 3
2 5 5
3 4 2
3 5 1
4 5 1

Sample Output 1

6

Dijkstra的经典例题(裸题),思路就不在赘述,直接上代码:

代码1(无优化):

#include<bits/stdc++.h>
using namespace std;
struct node//结构体代表每个节点的数据 ; 
{
	int to,ti;//to代表到达的节点,ti代表所需时间 ; 
	node(int a,int b)//构造函数(不会可以不用) 
	{
		to=a;
		ti=b;
	}
	node(){}
};
int main()
{
	int n,m,x,y,z;
	cin>>n>>m;
	map<int,vector<node> > ma;//用映射来存节点以及的其对应的边 ; 
	for(int i=1;i<=m;i++)
	{
		cin>>x>>y>>z;
		ma[x].push_back(node(y,z));//注意本题是双向图 ; 
		ma[y].push_back(node(x,z));
	}
	int st=1,dis[n+1],vis[n+1];//st代表目前最小的值的节点一开始当然是1; 
	memset(dis,0x3f,sizeof(dis));//先赋一个极大值 ; 
	memset(vis,0,sizeof(vis));//表示是否冻结了节点 ; 
	dis[1]=0;//起点为0; 
	for(int j=1;j<=n;j++)//循环取(冻)点 ; 
	{//cout<<st<<endl;
		int minn=1e+8,tmp;
		vis[st]=1;//冻结节点 ; 
		int l=ma[st].size();
		for(int i=0;i<l;i++)//枚举、更新 ; 
		{
			if(!vis[ma[st][i].to])
			{
				dis[ma[st][i].to]=min(dis[ma[st][i].to],dis[st]+ma[st][i].ti);
			}
		}
		for(int i=1;i<=n;i++)//找已有的最小值 ; 
		{
			if(!vis[i]&&dis[i]<minn)
			{
				minn=dis[i];
				tmp=i;
			}
		}
		st=tmp;//更新 ; 
	}
	cout<<dis[n];
}

我们再仔细读代码便可以发现,找最小值的操作会浪费大量时间(O(n)),那可不可以优化成O(1)呢??我们可以用一个神奇的数据类型——set,set有一个天然排序的性质,我们称这种优化为堆优化;

堆优化代码:

#include<bits/stdc++.h>
using namespace std;
struct node//结构体代表每条边的数据 ; 
{
	int to,q,next;//to表示边连接的节点,q代表权值,next代表这条边的下一个边 ; 
	node(int a,int b,int c)
	{
		to=a;
		q=b;
		next=c;
	}
	node(){}
}ma[1000001];//ma用于存边 ; 
set<pair<int,int> > st;//优化的set,分别存dij[i]和i ; 
int k,head[1000001],dij[1000001],n,m;//head表示每一个节点,由head找到节点对应的边 ;
bool ok[1000001]; 
void add(int a,int b,int c)//用于存a到b用c时间的边 
{
	ma[++k]=node(b,c,head[a]);
	head[a]=k;
}
int main()
{
	memset(dij,0x3f,sizeof(dij));
	memset(head,-1,sizeof(head));
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int x,y,z;
		cin>>x>>y>>z;
		add(x,y,z);
		add(y,x,z);
	}
	dij[1]=0;
	st.insert(make_pair(0,1));
	for(int i=1;i<=n;i++)
	{
		int u=st.begin()->second;//->为指针找到最小值的节点 ; 
		ok[u]=1;//冻结节点 
		st.erase(st.begin());//删除节点 
		for(int j=head[u];~j;j=ma[j].next)//~j表示j是否为-1 ; 
		{
			if(!ok[ma[j].to]&&dij[u]+ma[j].q<dij[ma[j].to])
			{
				st.erase(make_pair(dij[ma[j].to],ma[j].to));
				dij[ma[j].to]=ma[j].q+dij[u];
				st.insert(make_pair(dij[ma[j].to],ma[j].to));
			}
		}
	}
	cout<<dij[n];
}

猜你喜欢

转载自blog.csdn.net/GENE1997/article/details/83003338