码迷,mamicode.com
首页 > 编程语言 > 详细

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

时间:2020-03-17 08:33:20      阅读:87      评论:0      收藏:0      [点我收藏+]

标签:使用   set   生成   printf   efi   int start   过程   c++   最小值   

前言

复习一下迪杰斯特拉算法,由于最小生成树的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\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 <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\n",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\n", Prim(1) <= c ?"Yes":"No");
    }
}
 
int main(){
    init(1);
    return 0;
}

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

标签:使用   set   生成   printf   efi   int start   过程   c++   最小值   

原文地址:https://www.cnblogs.com/MMMMMMMW/p/12503512.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!