zoukankan      html  css  js  c++  java
  • 图论——迪杰斯特拉算法和最小生成树

    前言

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

    迪杰斯特拉算法

    目的

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

    思路

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

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

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

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

    题目

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

    普通方法

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

    #include <bits/stdc++.h>
    #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
    ",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 <bits/stdc++.h>
    #define ll long long
    #define MAX 1005
    using namespace std;
    
    int ans[MAX],n,m,s,t;//ans为起点到某一点的最短路线
    vector<pair<int,int>> 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<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>> > p_que;
    	p_que.push(make_pair(ans[start],start));//pair第一个值表示长度,第二个值表示顶点
    
    	while(!p_que.empty()){
    		pair<int,int> 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<int,int> 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
    ",dijkstra(s,t));
    	return 0;
    }
    

    最小生成树

    目的

    给定一个无向图

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

    思路

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

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

    题目

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

    代码

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

    #include <bits/stdc++.h>
    #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
    ", Prim(1) <= c ?"Yes":"No");
    	}
    }
     
    int main(){
        init(1);
        return 0;
    }
    
  • 相关阅读:
    【转】Quartz企业作业调度配置参考
    [转]quartz中参数misfireThreshold的详解
    【转】MFC下拉框使用方法
    MFC中使用tinyxml
    【转】MYSQL中复制表结构的几种方法
    C++错误:重定义 不同的存储类
    【转】vbsedit提示“无法创建空文档”解决办法
    wordbreak和wordwrap
    css字体font
    js和jquery书籍
  • 原文地址:https://www.cnblogs.com/MMMMMMMW/p/12503512.html
Copyright © 2011-2022 走看看