迪杰斯特拉算法及其堆优化

迪杰斯特拉算法及其堆优化

迪杰斯特拉算法是一种求解图的单点最短路径的算法。
一句话来说就是找最近点,更新相邻距离,再找最近点,更新相邻距离
迪杰斯特拉算法的原理是
1.首先在没有中间节点的情况下,也就是直达路径中找到到达某点p的最短路径。易知,该路径一定是原点到点p的最短路径。将点p标记在vis数组中,并将最短路径的值存在dist数组中。
2.再对所有节点进行松弛操作,也就是下一个节点的最短路径有两种情况,一种是经过某个已知的最短路(就是被vis标记的最短路径),第二种情况是直达。所以,求解下一个最短路径就是求解递推公式
dist[i]=min(dist[i] , map[i][now]+dist[now])
now是上一个最短路径。
3.找到最短的dist并标记在vis中,并迭代2,3步。
总体来说,迪杰斯特拉算法是按从小到大的顺序求解到各个点的最短路径,每求出一个最短路径,下次循环时就判断所有的点(其实等同于没有求出最短路径的点)如果经过上一个已经求出最短路径的点,是否会出现更短的路径(这一步就成为松弛操作)。在遍历所有点的同时,找到没有求出最短路径的点中的最短路径,并保存。
没有优化的迪杰斯特拉算法的时间复杂度为O(n*n)。
下面是一张图解:
迪杰斯特拉算法及其堆优化_第1张图片

以下是参考代码:

#include
using namespace std;
#define inf 0x7fffffff
long long int dist[100100];
int Map[1000][1000];
int vis[1010];
int main(){
	int n,m,s;
	while(~scanf("%d%d%d",&n,&m,&s)){
		for( int i=1;i<=n;i++){
			for( int j=1;j<=n;j++){
				Map[i][j]=0x7fffffff;
			}
			dist[i]=0x7fffffff;
		}
		memset(vis,0,sizeof(vis));
		for( int i=1;i<=m;i++){
			int from,to,val;
			cin>>from>>to>>val;
			Map[from][to]=min(val,Map[from][to]);
			Map[to][from]=min(val,Map[from][to]);	
		}
		dist[s]=0;
		vis[s]=1;
		int start=s;
		for( int j=1;j<=n;j++){
			int next, minn=0x7fffffff;;
			for( int i=1;i<=n;i++){
				if(Map[start][i]!=inf) {
					dist[i]=min(dist[i],Map[start][i]+dist[start]);
				}
				if(vis[i]==0&&dist[i]<minn){
					next=i;
					minn=dist[i];
				}	
			}
			
			if(minn==inf)break;
			start=next;
			vis[start]=0;
		}
		for( int i=1;i<=n;i++){
			cout<<dist[i];
			if(i!=n) cout<<" ";
		}
		cout<<endl;
		
	}
}

对迪杰斯特拉算法的优化:
上面的迪杰斯特拉算法主要缺陷是,每当找到一个最短路径,如果需要找下一个最短路径,就需要在完成松弛操作之后,遍历dist数组,寻找其中的最小值。遍历dist数组的时间复杂度为O(n)。
如果图的边数为n*(n-1),那么每找到一个最小值,所要进行的松弛操作数就是n-1,这和遍历dist数组可以同时进行,算法优化的空间不大。
然而,如果是稀疏图,每找到一个最小值,所要进行的松弛操作数就远小于n-1,这时就可以对算法进行优化。优化的关键是省去对dist的线性查找,如果每次可以直接返回dist中的最大值,就可以大大减小算法的时间复杂度。
堆优化后的迪杰斯特拉算法复杂度为ElogE

#include
using namespace std;
#define inf 0x7fffffff
struct p{
	long long int to,val,next;
};
p edge[200100];
long long int dist[100100];
int vis[100100];
int head[100100];
int cnt;
void add_edge( long long from,long long to,long long  val){
	edge[++cnt].val=val;
	edge[cnt].to=to;
	edge[cnt].next=head[from];
	head[from]=cnt;
}
struct node{
	long long int from,val;
	bool operator <(const node &a ) const{
		return a.val<val;
	}
};
int main(){
	int n,m,s;
	while(~scanf("%d%d%d",&n,&m,&s)){
		cnt=0;
		memset(edge,0,sizeof(edge));
		for( int i=1;i<=n;i++){
			dist[i]=1e12;
			head[i]=0;
			vis[i]=0;
		}
		for( int i=1;i<=m;i++) edge[i].val=1e12;
		for( int i=1;i<=m;i++){
			long long int from,to,val;
			scanf("%lld%lld%lld",&from,&to,&val);
			add_edge(from,to,val);
		}
		dist[s]=0;
		priority_queue<node>q;
		q.push({s,0});
		while(!q.empty()){
			node now=q.top();
			q.pop();
			if(vis[now.from]) continue;
			vis[now.from]=1;
			for( long long int  i=head[now.from];i!=0;i=edge[i].next){
				int j=edge[i].to;
				if(dist[now.from]+edge[i].val<dist[j]){//进行松弛操作
					dist[j]=dist[now.from]+edge[i].val;
					q.push({j,dist[j]});
				}
			}
		}
		for( int i=1;i<=n;i++){
			printf("%lld ",dist[i]);
		}
		cout<<endl;	
	}
}

迪杰斯特拉算法的优化可以总结为以下几步:
1.将最短距离出队。
2.进行松弛操作,并将成功松弛的点入队。

你可能感兴趣的:(图论,经典算法,算法,图论,数据结构,leetcode)