图论——迪杰斯特拉算法和最小生成树

前言

复习一下迪杰斯特拉算法,由于最小生成树的Prim算法与迪杰斯特拉算法极其类似,再顺便复习下最小生成树,顺便找两道水题验证代码正确性。

迪杰斯特拉算法

目的

该算法用于单源最短路,求一个图中,从起点S,到终点E的最短路径

思路

算法基于贪心思想,简单来讲就是两步:

  • 找出起点距离其他点的最短距离中的最小的那个
  • 用最小的来更新其他点的最短距离,更新完后舍弃

依我所见,迪杰斯特拉类似于排序,假设从起点到其他点的路径为边。

  • 选出最短的边,通过最短的边来更新其他的边。
  • 再通过第二短的边,更新其他的边。
  • 整个过程就是从小到大依次找出从起点到其他点的最短边

题目

牛客网:https://ac.nowcoder.com/acm/problem/17511

普通方法

先遍历顶点,再遍历该顶点到其他顶点的边,时间复杂度:\(O(n^2)\)

#include 
#define ll long long
#define MAX 1005
using namespace std;
 
int mp[MAX][MAX],ans[MAX],n,m,s,t;
bool used[MAX];
 
void init(){
    scanf("%d%d%d%d",&n,&m,&s,&t);
    memset(mp,0x3f,sizeof(mp));
    memset(ans,0x3f,sizeof(ans));
    memset(used,false,sizeof(used));
    ans[s] = 0;
    for(int i = 1;i <= m;i++){
        int x,y,v;
        scanf("%d%d%d",&x,&y,&v);
        mp[x][y] = mp[y][x] = min(mp[y][x],v);
        if (x == s) ans[y] = mp[x][y];
        else if (y == s) ans[x] = mp[x][y];
    }
}
int dijkstra(int start,int end){
 
    while(true){
        int min_edge = 0;
        for(int i = 1;i <= n;i++)//寻找从起点到其他点的路线中的最短路线
            if (!used[i]&&(!min_edge || ans[i] < ans[min_edge]))
                min_edge = i;
        //当找到终点时,可提前退出
        if (min_edge == end || !min_edge) break;
        used[min_edge] = true;
 
        for(int i = 1;i <= n;i++)
            ans[i] = min(ans[i],ans[min_edge]+mp[min_edge][i]);
    }
 
    return ans[end]==0x3f3f3f3f?-1:ans[end];
}
 
int main(){
    init();
    printf("%d\n",dijkstra(s,t));
    return 0;
}

优先队列(堆)

m为边数,n为顶点数
先上结论,时间复杂度\(O(m*logn)\)

for(int i = 1;i <= n;i++)//寻找从起点到其他点的路线中的最短路线
    if (!used[i]&&(!min_edge || ans[i] < ans[min_edge]))
        min_edge = i;

显然,对于上面代码,可以使用优先队列(堆)来使得时间复杂度降为\(O(logn)\),但是!!!下面还有个for循环,所以本身时间复杂度还是\(O(n^2)\)

for(int i = 1;i <= n;i++)
    ans[i] = min(ans[i],ans[min_edge]+mp[min_edge][i]);

那么,可否把这里也改下呢?显然,我们不需要遍历所有的顶点,因为点到点不一定有边,这时候我们只需要遍历边即可,也就是把边存储起来,即使用邻接表。

总结一下,整个过程每个顶点可能遍历了多次,但其中只有一次需要遍历邻接的边,即所有边也只需要遍历一次(无向边就是两次),由于使用了堆,所以还需要加上用堆的时间复杂度,所以总的时间复杂度为\(O(m*logn)\)

下面是代码

#include 
#define ll long long
#define MAX 1005
using namespace std;

int ans[MAX],n,m,s,t;//ans为起点到某一点的最短路线
vector> mp[MAX*10];//邻接表存储边,mp下标表示起点,pair第一个值表示长度,第二个值表示终点
void init(){
	scanf("%d%d%d%d",&n,&m,&s,&t);
	memset(ans,0x3f,sizeof(ans));
	ans[s] = 0;
	for(int i = 1;i <= m;i++){
		int x,y,v;
		scanf("%d%d%d",&x,&y,&v);
		mp[x].push_back(make_pair(v,y));
		mp[y].push_back(make_pair(v,x));
	}
}
int dijkstra(int start,int end){

	priority_queue,vector>,greater> > p_que;
	p_que.push(make_pair(ans[start],start));//pair第一个值表示长度,第二个值表示顶点

	while(!p_que.empty()){
		pair node = p_que.top();
		p_que.pop();
		if (node.first > ans[node.second]) continue;
		for(int i = mp[node.second].size()-1;i >= 0;i--){
			pair temp = mp[node.second][i];
			if (ans[temp.second] > ans[node.second]+temp.first){
				ans[temp.second] = ans[node.second]+temp.first;
				p_que.push(make_pair(ans[temp.second],temp.second));
			}
		}
	}

	return ans[end]==0x3f3f3f3f?-1:ans[end];
}

int main(){
	init();
	printf("%d\n",dijkstra(s,t));
	return 0;
}

最小生成树

目的

给定一个无向图

  • 生成树:一个子图,其任意两点都能互通,且是棵树
  • 最小生成树:边上有值,且所有边的和最小的生成树

思路

假设迪杰斯特拉算法是以一个点为起点,求该起点到其他所有点的最小值,那么,最小生成树的Prim算法则是以一个集合(有多个点)为起点,求该集合到其他所有点的最小值,并求和,步骤如下:

  • 每次循环,找出集合与非集合的点中的最小边,假设这个点为\(x\)
  • 集合加入\(x\)点,并遍历\(x\)与其他点的边,假设有一条边\(edge[x][y]\) < 集合到 \(y\) 的距离,则更新集合到\(y\)的距离
  • 回到第一步

题目

牛客网:https://ac.nowcoder.com/acm/problem/15108

代码

显然,可以跟迪杰斯特拉一样,使用堆进行优化,由于时间问题,只给出普通代码。

#include 
#define ll long long
#define MAX 1005
using namespace std;
 
int mp[MAX][MAX],ans[MAX],c,n,m;
bool used[MAX];

int Prim(int start){//几乎和迪杰斯特拉算法一模一样
 	int len = 0;
    while(true){
        int min_edge = 0;
        for(int i = 1;i <= n;i++)//寻找从集合到其他点的路线中的最短路线
            if (!used[i]&&(!min_edge || ans[i] < ans[min_edge]))
                min_edge = i;
        //len >= 0x3f3f3f3f说明无法生成最小生成树,即图不连通
        if (!min_edge || len >= 0x3f3f3f3f) break;
        used[min_edge] = true;
 		len += ans[min_edge];//加上最小值
        for(int i = 1;i <= n;i++)
            ans[i] = min(ans[i],mp[min_edge][i]);//注意这里和迪杰斯特拉不同,也几乎是唯一的不同点
    }
 
    return len;
}
 
void init(int start){
	while(~scanf("%d%d%d",&c,&m,&n)){
	    memset(mp,0x3f,sizeof(mp));
	    memset(ans,0x3f,sizeof(ans));
	    memset(used,false,sizeof(used));
	    ans[start] = 0;
	    for(int i = 1;i <= m;i++){
	        int x,y,v;
	        scanf("%d%d%d",&x,&y,&v);
	        mp[x][y] = mp[y][x] = min(mp[y][x],v);
	    }
	    printf("%s\n", Prim(1) <= c ?"Yes":"No");
	}
}
 
int main(){
    init(1);
    return 0;
}

你可能感兴趣的:(图论——迪杰斯特拉算法和最小生成树)