【AcWing算法基础课】第三章 搜索与图论(2)

简介: 特点:尽可能先向纵深方向搜索。使用stack实现。所需空间O(h)(h为深度)。不具有“最短性”。

六、Dijkstra算法

源点即起点,汇点即终点。

6bff69661815bb53f88655047b7d7c85_c1f4557746e14b149c7ca2d065a086af.png


n表示点数,m表示边数

8dd30e5cc49743aaa4f4059017c1c7ea_3a93fe4dd9d44116b1dc8ae6a89ffdb6.png


稠密图:m和n2一个级别

稀疏图:m和n一个级别

e9bb896f8f8280de8b9cf70e02be0655_d4449027fb7a4caa821403add36e7614.png


核心模板

稠密图用邻接矩阵存储,稀疏图用邻接表存储。


朴素dijkstra算法

31ebefbb046c311407f84a235a2f8302_614d3f584cea4d388fbb1772bbe8ee17.png

时间复杂度是O(n2+m),n表示点数,m表示边数


int g[N][N];  //存储每条边
int dist[N][N]; //存储1号点到每个点的最短距离
bool st[N]; //存储每个点的最短路是否已经确定
//求1号点到n号点的最短路,如果不存在则返回-1
int dijkstra(){
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    for(int i=0;i<n-1;i++){
        int t=-1;   //在还未确定最短路的点中,寻找距离最小的点
        for(int j=1;j<=n;j++){
            if(!st[j]&&(t==-1||dist[t]>dist[j]))  t=j;
        }
        //用t更新其他点的距离
        for(int j=1;j<=n;j++){
            dist[j]=min(dist[j],dist[t]+g[t][j]);
        }
        st[t]=true;
    }
    if(dist[n]==0x3f3f3f3f) return -1;
    return dist[n];
}


下图来自AcWing官网,作者们如图,侵权删。

e379dc1c41cbba92f341b86cd29394cd_4c9408b6f52e4c47bf7cd67a0090e146.png

堆优化版dijkstra

手写堆可以保证堆中元素个数一定,而优先队列不可以修改任意一个元素,只能继续插入新的点,有冗余。


下图来自AcWing官网,作者们如图,侵权删。

c30676df90904f244cff21c730762444_0e3014f1ee574b4cbecdd9820088115f.png

时间复杂度是O(mlogn),n表示点数,m表示边数。

typedef pair<int,int> PII;
int n;  //点的数量
int h[N],w[M],e[M],ne[M],idx;  //领接表存储所有边
int dist[N];   //存储所有点到1号点的距离
bool st[N];   //存储每个点的最短距离是否已确定
//求1号点到n号点的最短距离,如果不存在,则返回-1
int dijkstra(){
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    priority_queue<PII,vector<PII>,greater<PII>> heap;
    heap.push({0,1});   //first存储距离,second存储节点编号
    while(heap.size()){
        auto t=heap.top();
        heap.pop();
        int ver=t.second,distance=t.first;     //distance等同于dist[ver]
        if(st[ver]) continue;
        st[ver]=true;
        for(int i=h[ver];i!=-1;i=ne[i]){
            int j=e[i];
            if(dist[j]>distance+w[i]){
                dist[j]=distance+w[i];
                heap.push({dist[j],j});   //注意与spfa区分,此时只要更新最短路就入堆
            }
        }
    }
    if(dist[n]==0x3f3f3f3f) return -1;
    return dist[n];
}


题目一

题目链接:849. Dijkstra求最短路 I


6.1题目描述

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为正值。


请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 −1 。


输入格式


第一行包含整数 n 和 m 。


接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。


输出格式


输出一个整数,表示 1 号点到 n 号点的最短距离。


如果路径不存在,则输出 −1 。


数据范围


1≤n≤500,1≤m≤105,图中涉及边长均不超过10000。


输入样例:


3 3
1 2 2
2 3 1
1 3 4



6.2思路分析

套用模板即可,注意细节


6.3代码实现

#include <iostream>
#include <cstring>
using namespace std;
const int N=510;
int n,m;
int g[N][N];  //稠密图用领接矩阵存储,存储两个点之间的距离 
int dist[N];  //存储从1号点走到每个点的当前最短距离 
bool st[N];   //存储每个点的最短路是否确定 
int dijkstra(){
  memset(dist,0x3f,sizeof dist);  //初始化从1号点走到每个点的距离为正无穷 
    dist[1]=0;    //1号点走到自己的最短距离为0 
    for(int i=0;i<n;i++){
      int t=-1;     //t在还未确定最短路的点中寻找距离最短的点 
      for(int j=1;j<=n;j++){
         //在st[]=false的点中找dist[]最小的点
      //如果当前点没有确定最短路而且t没有更新过或者当前点没有确定最短路而且t已经更新过,但是j离1号点距离更短,则更新t 
        if(!st[j]&&(t==-1||dist[t]>dist[j]))  t=j;  
  }
  st[t]=true;   //t已确定最短路
  //更新最短路,看是否存在从t走到j再走到终点的距离比j直接走到终点距离小,若存在则更新 
     for(int j=1;j<=n;j++){
      dist[j]=min(dist[j],dist[t]+g[t][j]);
     }
  }
  //如果最短路没有被更新过,说明走不到终点返回-1,否则返回最短路 
  if(dist[n]==0x3f3f3f3f) return -1;
  return dist[n];
}
int main(){
    cin>>n>>m;
    //初始化邻接矩阵 
    memset(g,0x3f,sizeof g);
    while(m--){
      int a,b,c;
      cin>>a>>b>>c;
      //因为存在重边和自环,所以两点间距离取最短的即可 
      g[a][b]=min(g[a][b],c); 
  }
  int t=dijkstra();
  cout<<t; 
    return 0;
}


题目二

题目链接:850. Dijkstra求最短路 II


6.4题目描述

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为非负值。


请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 −1 。


输入格式


第一行包含整数 n 和 m 。


接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。


输出格式


输出一个整数,表示 1 号点到 n 号点的最短距离。


如果路径不存在,则输出 −1。


数据范围


1≤n,m≤1.5×105,图中涉及边长均不小于 0,且不超过 10000。


数据保证:如果最短路存在,则最短路的长度不超过 109。


输入样例:


3 3
1 2 2
2 3 1
1 3 4


6.5思路分析

利用堆优化dijkstra算法,套用模板即可,注意细节。


6.6代码实现

#include <iostream>
#include <cstring>
#include <queue>
#include <utility>
using namespace std;
typedef pair<int,int> PII;    //两个值分别存储每个点到1号点最短距离和点编号 
const int N=150010;
int n,m;
int h[N],w[N],e[N],ne[N],idx;   //邻接表存储每条边,w[]代表每条边的权重 
int dist[N];     //存储1号点到n号点的最短距离 
bool st[N];      //存储每个点的最短距离是否已经确定 
//邻接表加边 
void add(int a,int b,int c){
  e[idx]=b;
  w[idx]=c;
  ne[idx]=h[a];
  h[a]=idx++;
}
int dijkstra(){
  memset(dist,0x3f,sizeof dist);
  dist[1]=0;      //1号点到1号点(自己)的距离为0 
  priority_queue<PII,vector<PII>,greater<PII>> heap;   //建立小根堆 
    heap.push({0,1});     //将起点放入堆中 
  while(heap.size()){
  auto t=heap.top();   //t取出当前距离最小的点(即堆顶元素) 
  heap.pop();
  int ver=t.second,distance=t.first;
  if(st[ver]) continue;     //如果堆顶元素已被访问过,直接跳过后续,找下一个距离最小的点 
  st[ver]=true;         //否则,标记该点已被访问过 
  for(int i=h[ver];i!=-1;i=ne[i]){  //遍历这个点的所有邻边 
    int j=e[i];
    if(dist[j]>distance+w[i]){     //如果从1号点到ver点再到j点的距离比从1号点直接到j点的距离小,则更新1号点到j的最短距离为前者 
    dist[j]=distance+w[i];
    heap.push({dist[j],j});    //把更新后的的距离和该点编号,入堆 
    }
  }
  } 
  if(dist[n]==0x3f3f3f3f)  return -1;    //如果无法从1号点到达n号点返回-1 
  return dist[n];                        //否则返回从1号点到n号点的距最短离 
}
int main(){
  cin>>n>>m;
  memset(h,-1,sizeof h);
  while(m--){
  int a,b,c;
  cin>>a>>b>>c;
  add(a,b,c);
  }
  int t=dijkstra();
  cout<<t;
  return 0;
}


七、Bellman-Ford算法

存在最短路则一定不存在负权回路,如果存在负权回路则最短路可能是负无穷。


时间复杂度O(nm),n表示点数,m表示边数


核心模板

13b3d598991a1db094a39fd5b0e45607_b3b33e0cc3214e9190b834a3048d9f6f.png


int n,m;   //n表示点数,m表示边数
int dist[N];   //dist[x]存储1到x的最短路距离
struct Edge{   //边,a表示出点,b表示入点,w表示边的权重
    int a,b,w;
}edge[M];
//求1到n的最短路距离,如果无法从1走到n,返回-1
int bellman_ford(){
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    //如果第n次迭代仍然会松弛三角不等式,就说明存在一条长度是n+1的最短路径,由抽屉原理,路径中至少存在两个相同的点,说明图中存在负权回路
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
            int a=edges[j].a,b=edges[j].b,w=edges[j].w;
            if(dist[b]>dist[a]+w)  dist[b]=dist[a]+w;
        }
    }
    if(dist[n]>0x3f3f3f3f/2)  return -1;
    return dist[n];
}



题目链接:853. 有边数限制的最短路


7.1题目描述

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数。


请你求出从 1 号点到 n 号点的最多经过 k 条边的最短距离,如果无法从 1 号点走到 n 号点,输出 impossible。


注意:图中可能 存在负权回路 。


输入格式


第一行包含三个整数 n,m,k。


接下来 m 行,每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z 。


点的编号为 1∼n。


输出格式


输出一个整数,表示从 1 号点到 n 号点的最多经过 k 条边的最短距离。


如果不存在满足条件的路径,则输出 impossible。


数据范围


1≤n,k≤500,1≤m≤10000,1≤x,y≤n,任意边长的绝对值不超过 10000。


输入样例:


3 3 1
1 2 1
2 3 1
1 3 3


输出样例:


7.2思路分析

套用模板即可,注意细节。


7.3代码实现

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=510,M=10010;
int n,m,k;
int backup[N];    //存储上一次循环中dist[]数组的值 
int dist[N];     //存储1号点到n号点的最短距离 
//利用结构体存每条边:a起点,b终点,w权重 
struct Edge{
  int a,b,w;
}edges[M];
int bellman_ford(){
  memset(dist,0x3f,sizeof dist);
  dist[1]=0;
  for(int i=0;i<k;i++){
  memcpy(backup,dist,sizeof dist);      //每次迭代需备份,确保更新只用到上一次的结果 
  for(int j=0;j<m;j++){
    int a=edges[j].a,b=edges[j].b,w=edges[j].w;
    dist[b]=min(dist[b],backup[a]+w);
  }
  }
  if(dist[n]>0x3f3f3f3f/2) return -3;   //不能写成==,如果图中某个点是无法到达,到达该点的最短距离为0x3f3f3f3f,而这个点到最后一个点的最短距离为-1,则会导致dist[n]!=0x3f3f3f3f,但是这个点不能到达n号点 
  return dist[n]; 
}
int main(){
  cin>>n>>m>>k;
  for(int i=0;i<m;i++){
  int a,b,w;
  cin>>a>>b>>w;
  edges[i]={a,b,w};
  }  
  int t=bellman_ford();
  if(t==-3)  cout<<"impossible";
  else cout<<t;
  return 0;
}


八、spfa算法(队列优化的Bellman-Ford算法)


b83b465be26bcf94c53befa6fc4fc744_18f07741ebc341d18bcc11a7a1a27013.png

spfa算法

时间复杂度平均情况下O(m),最坏情况下O(nm),n表示点数,m表示边数

核心模板

int n;    //总点数
int h[N],w[N],e[N],ne[N],idx;  //领接表存储所有边
int dist[N];    //存储每个点到1号点的最短距离
bool st[N];   //存储每个点是否在队列中
//求1号点到n号点的最短路距离,如果从1号点无法走到n号点则返回-1
int spfa(){
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    queue<int> q;
    q.push(1);
    st[1]=true;
    while(q.size()){
        auto t=q.front();
        q.pop();
        st[t]=false;
        for(int i=h[t];i!=-1;i=ne[i]){
            int j=e[i];
            if(dist[j]>dist[t]+w[i]){
                dist[j]=dist[t]+w[i];
                //注意与dijkstra堆优化区分,此时如果不在队列才入队
                if(!st[j]){     //注意该判断语句的位置(在第一个if中) //如果队列中已存在j,则不需要将j重复插入
                    q.push(j);
                    st[j]=true;
                }
            }
        }
    }
    if(dist[n]==0x3f3f3f3f)  return -1;
    return dist[n];
}

0

spfa判断图中是否存在负环

时间复杂度是O(nm),n表示点数,m表示边数

int n;   //总点数
int h[N],w[N],e[N],ne[N],idx;  //领接表存储所有边
int dist[N],cnt[N];    //dist[x]存储1号点到x的最短距离,cnt[x]存储1到x的最短路中经过的点数
bool st[N];    //存储每个点是否在队列中
//如果存在负环,则返回true,否则返回false
bool spfa(){
    //不需要初始化dist数组
    //原理:如果某条最短路径上有n个点(除了自己),那么加上自己之后一共有n+1个点,由抽屉原理,一定有两个点相同,所以存在环。
    queue<int> q;
    for(int i=1;i<=n;i++){
        q.push(i);
        st[i]=true;
    }
    while(q.size()){
        auto t=q.front();
        q.pop();
        st[t]=false;
        for(int i=h[t];i!=-1;i=ne[i]){
            int j=e[i];
            if(dist[j]>dist[t]+w[i]){
                dist[j]=dist[t]+w[i];
                cnt[j]=cnt[t]+1;
                if(cnt[j]>=n)  return true;   //如果从1号点到x的最短路中包含至少n个点(不包括自己),则说明存在环
                if(!st[j]){
                    q.push(j);
                    st[j]=true;
                }
            }
        }
    }
    return false;
}


题目一

题目链接:851. spfa求最短路


8.1题目描述

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数。


请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 impossible。


数据保证不存在负权回路。


输入格式


第一行包含整数 n 和 m。


接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。


输出格式


输出一个整数,表示 1 号点到 n 号点的最短距离。


如果路径不存在,则输出 impossible。


数据范围


1≤n,m≤105,图中涉及边长绝对值均不超过 10000。


输入样例:

3 3
1 2 5
2 3 -3
1 3 4


8.2思路分析

套用模板即可,注意细节。


8.3代码实现

#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N=150010;
int n,m;
int h[N],w[N],e[N],ne[N],idx;   //邻接表存储每条边,w[]代表每条边的权重 
int dist[N];     //存储1号点到n号点的最短距离 
bool st[N];      //存储每个点是否已经在队列中,防止队列中存储重复的点 
//邻接表加边 
void add(int a,int b,int c){
  e[idx]=b;
  w[idx]=c;
  ne[idx]=h[a];
  h[a]=idx++;
}
int spfa(){
  memset(dist,0x3f,sizeof dist);
  dist[1]=0;      //1号点到1号点(自己)的距离为0 
    queue<int> q;     //队列中存储能被更新的更新后的点(更新后的点,其后的点的距离才会被更新,否则无意义) 
    q.push(1);       //将1号点入队 
    while(q.size()){
      int t=q.front();
      q.pop();
      st[t]=false;  
      for(int i=h[t];i!=-1;i=ne[i]){    //遍历该点的所有出边 
      int j=e[i];
      if(dist[j]>dist[t]+w[i]){     //如果该点的距离可以被更新,则更新距离 
        dist[j]=dist[t]+w[i];     
        if(!st[j]){               //如果j不在队列中,则加入 
        q.push(j);
        st[j]=true;
    }
    }
  }
  }
  if(dist[n]==0x3f3f3f3f)  return -3;    //如果无法从1号点到达n号点返回-1 
  return dist[n];                        //否则返回从1号点到n号点的距最短离 
}
int main(){
  cin>>n>>m;
  memset(h,-1,sizeof h);
  while(m--){
  int a,b,c;
  cin>>a>>b>>c;
  add(a,b,c);
  }
  int t=spfa();
    if(t==-3) cout<<"impossible";
  else cout<<t;
  return 0;
}


题目二

题目链接:852. spfa判断负环


8.4题目描述

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数。


请你判断图中 是否存在负权回路。


输入格式


第一行包含整数 n 和 m。


接下来 m 行每行包含三个整数 x,y,z ,表示存在一条从点 x 到点 y 的有向边,边长为 z。


输出格式


如果图中存在负权回路,则输出 Yes,否则输出 No。


数据范围


1≤n≤2000,1≤m≤10000,图中涉及边长绝对值均不超过 10000。


输入样例:


3 3
1 2 -1
2 3 4
3 1 -4


输出样例:


Yes


8.5思路分析

套用模板即可,注意细节。


下图来自AcWing官网,作者们如图,侵权删。

94a35f9dce2959c07ef66bee75af66e2_5786a85fbcc648cd81f7ca5e16653950.png

8.6代码实现

#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N=150010;
int n,m;
int h[N],w[N],e[N],ne[N],idx;   //邻接表存储每条边,w[]代表每条边的权重 
int dist[N],cnt[N];     //dist[]存储1号点到n号点的最短距离,cnt[i]存储从1号点到i号点经过的点的数量
bool st[N];      //存储每个点是否已经在队列中,防止队列中存储重复的点 
//邻接表加边 
void add(int a,int b,int c){
  e[idx]=b;
  w[idx]=c;
  ne[idx]=h[a];
  h[a]=idx++;
}
int spfa(){
    queue<int> q;     //队列中存储能被更新的更新后的点(更新后的点,其后的点的距离才会被更新,否则无意义) 
    //将所有点入队
    for(int i=1;i<=n;i++){
        st[i]=true;   
        q.push(i);
    }
    while(q.size()){
      int t=q.front();
      q.pop();
      st[t]=false;  
      for(int i=h[t];i!=-1;i=ne[i]){    //遍历该点的所有出边 
      int j=e[i];
      if(dist[j]>dist[t]+w[i]){     //如果该点的距离可以被更新,则更新距离 
        dist[j]=dist[t]+w[i];
        cnt[j]=cnt[t]+1;          //j经过的点数加1
        if(cnt[j]>=n) return true;
        if(!st[j]){               //如果j不在队列中,则加入 
        q.push(j);
        st[j]=true;
    }
    }
  }
  }
  return false;
}
int main(){
  cin>>n>>m;
  memset(h,-1,sizeof h);
  while(m--){
  int a,b,c;
  cin>>a>>b>>c;
  add(a,b,c);
  }
  if(spfa()) cout<<"Yes";
  else cout<<"No";
  return 0;
}


九、floyd算法

核心模板

下图来自AcWing官网,作者们如图,侵权删。

78c5a73f390bec0aee9c3543001eb5b1_913afa4a09904924a05271d01c1a4b52.png

时间复杂度是O(n3),n表示点数

//初始化
for(int i=1;i<=n;i++){
    for(int j=1;j<=n;j++){
        if(i==j) d[i][j]=0;
        else d[i][j]=INF;
    }
}
//算法结束后,d[a][b]表示a到b的最短距离
void floyd(){
    for(int k=1;k<=n;k++){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
            }
        }
    }
}


题目链接:854. Floyd求最短路


9.1题目描述

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,边权可能为负数。


再给定 k 个询问,每个询问包含两个整数 x 和 y,表示查询从点 x 到点 y 的最短距离,如果路径不存在,则输出 impossible。


数据保证图中不存在负权回路。


输入格式


第一行包含三个整数 n,m,k。


接下来 m 行,每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。


接下来 k 行,每行包含两个整数 x,y,表示询问点 x 到点 y 的最短距离。


输出格式


共 k 行,每行输出一个整数,表示询问的结果,若询问两点间不存在路径,则输出 impossible。


数据范围


1≤n≤200,1≤k≤n2

1≤m≤20000,图中涉及边长绝对值均不超过 10000。


输入样例:


3 3 2
1 2 1
2 3 2
1 3 1
2 1
1 3


9.2思路分析

套用模板即可,注意细节。


9.3代码实现

#include <iostream>
#include <algorithm>
using namespace std;
const int N=210,INF=1e9;
int n,m,q;
int d[N][N];       //邻接矩阵
void floyd(){
    for(int k=1;k<=n;k++){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
            }
        }
    }
}
int main(){
    cin>>n>>m>>q;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(i==j) d[i][j]=0;
            else d[i][j]=INF;
        }
    }
    while(m--){
        int a,b,w;
        cin>>a>>b>>w;
        d[a][b]=min(d[a][b],w);       //如果有重边只取最短的
    }
    floyd();
    while(q--){
        int a,b;
        cin>>a>>b;
        if(d[a][b]>INF/2) cout<<"impossible"<<endl;
        else cout<<d[a][b]<<endl;
    }
    return 0;
}


十、Prim算法

e759252686069a7ce4bd57c0c75be66c_7066807db6564df08d22e1f72f86d702.png


核心模板

4e3157cd899ccd932789a6afd6dc422d_5e3a4e28b2cc409ea27e615cfcefe5ad.png

时间复杂度是O(n2+m),n表示点数,m表示边数


int n;      //n表示点数
int g[N][N];    //邻接矩阵,存储所有边
int dist[N];   //存储其他点到当前最小生成树的距离
bool st[N];     //存储每个点是否已经在生成树中
//如果图不连通,则返回INF(值是0x3f3f3f3f),否则返回最小生成树的树边权重之和
int prim(){
    memset(dist,0x3f,sizeof dist);
    int res=0;
    for(int i=0;i<n;i++){
        int t=-1;
        for(int j=1;j<=n;j++){
            if(!st[j]&&(t==-1||dist[t]>dist[j])) t=j;
        }
        if(i&&dist[t]==INF) return INF;
        if(i) res+=dist[t];
        st[t]=true;
        for(int j=1;j<=n;j++) dist[j]=min(dist[j],g[t][j]);
    }
    return res;
}


题目链接:858. Prim算法求最小生成树


10.1题目描述

给定一个 n 个点 m 条边的无向图,图中可能存在重边和自环,边权可能为负数。


求最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible。


给定一张边带权的无向图 G=(V,E),其中 V 表示图中点的集合,E 表示图中边的集合,n=|V|,m=|E|。


由 V 中的全部 n 个顶点和 E 中 n−1 条边构成的无向连通子图被称为 G 的一棵生成树,其中边的权值之和最小的生成树被称为无向图 G 的最小生成树。


输入格式


第一行包含两个整数 n 和 m。


接下来 m 行,每行包含三个整数 u,v,w,表示点 u 和点 v 之间存在一条权值为 w 的边。


输出格式


共一行,若存在最小生成树,则输出一个整数,表示最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible。


数据范围


1≤n≤500,1≤m≤105,图中涉及边的边权的绝对值均不超过 10000。


输入样例:


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



10.2思路分析

套用模板即可,注意理解算法思想,注意细节。


10.3代码实现

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=510,INF=0x3f3f3f3f;
int n,m;
int g[N][N];    //邻接矩阵存储所有边
int dist[N];    //存储其他点到当前最小生成树的距离
bool st[N];     //存储每个点是否已经在生成树中
int prim(){
  memset(dist,0x3f,sizeof dist);    //初始化所有点距离当前最小生成树的距离为正无穷
  int res=0;                  //res存储最小生成树边的权重之和
  for(int i=0;i<n;i++){       //循环n次,每次去找当前距离最小生成树的距离最小的点,记录在t中
  int t=-1;
  for(int j=1;j<=n;j++){
    if(!st[j]&&(t==-1||dist[t]>dist[j])) t=j;   
  }
  if(i&&dist[t]==INF) return INF;     //如果距离最小仍然是正无穷,说明图并不连通,返回INF
  if(i) res+=dist[t];                 //将距离累加进答案中
  for(int j=1;j<=n;j++) dist[j]=min(dist[j],g[t][j]);    //以当前距离最小的点来更新其他点
  st[t]=true;                      //标记为已在生成树中
  }
  return res;
}
int main(){
  cin>>n>>m;
  memset(g,0x3f,sizeof g);
  while(m--){
  int a,b,c;
  cin>>a>>b>>c;
  g[a][b]=g[b][a]=min(g[a][b],c);
  }
  int t=prim();
  if(t==INF) cout<<"impossible"<<endl;
  else cout<<t<<endl;
  return 0;
}
目录
相关文章
|
4月前
|
算法
【算法】二分算法——搜索插入位置
【算法】二分算法——搜索插入位置
|
1月前
|
算法 搜索推荐 数据库
二分搜索:高效的查找算法
【10月更文挑战第29天】通过对二分搜索的深入研究和应用,我们可以不断挖掘其潜力,为各种复杂问题提供高效的解决方案。相信在未来的科技发展中,二分搜索将继续发挥着重要的作用,为我们的生活和工作带来更多的便利和创新。
50 1
|
27天前
|
JSON 算法 数据挖掘
基于图论算法有向图PageRank与无向图Louvain算法构建指令的方式方法 用于支撑qwen agent中的统计相关组件
利用图序列进行数据解读,主要包括节点序列分析、边序列分析以及结合节点和边序列的综合分析。节点序列分析涉及节点度分析(如入度、出度、度中心性)、节点属性分析(如品牌、价格等属性的分布与聚类)、节点标签分析(如不同标签的分布及标签间的关联)。边序列分析则关注边的权重分析(如关联强度)、边的类型分析(如管理、协作等关系)及路径分析(如最短路径计算)。结合节点和边序列的分析,如子图挖掘和图的动态分析,可以帮助深入理解图的结构和功能。例如,通过子图挖掘可以发现具有特定结构的子图,而图的动态分析则能揭示图随时间的变化趋势。这些分析方法结合使用,能够从多个角度全面解读图谱数据,为决策提供有力支持。
|
2月前
|
算法 决策智能
基于禁忌搜索算法的VRP问题求解matlab仿真,带GUI界面,可设置参数
该程序基于禁忌搜索算法求解车辆路径问题(VRP),使用MATLAB2022a版本实现,并带有GUI界面。用户可通过界面设置参数并查看结果。禁忌搜索算法通过迭代改进当前解,并利用记忆机制避免陷入局部最优。程序包含初始化、定义邻域结构、设置禁忌列表等步骤,最终输出最优路径和相关数据图表。
|
3月前
|
大数据 UED 开发者
实战演练:利用Python的Trie树优化搜索算法,性能飙升不是梦!
在数据密集型应用中,高效搜索算法至关重要。Trie树(前缀树/字典树)通过优化字符串处理和搜索效率成为理想选择。本文通过Python实战演示Trie树构建与应用,显著提升搜索性能。Trie树利用公共前缀减少查询时间,支持快速插入、删除和搜索。以下为简单示例代码,展示如何构建及使用Trie树进行搜索与前缀匹配,适用于自动补全、拼写检查等场景,助力提升应用性能与用户体验。
69 2
|
2月前
|
存储 算法 C++
【搜索算法】 跳马问题(C/C++)
【搜索算法】 跳马问题(C/C++)
|
2月前
|
人工智能 算法 Java
【搜索算法】数字游戏(C/C++)
【搜索算法】数字游戏(C/C++)
|
4月前
|
机器学习/深度学习 算法 文件存储
【博士每天一篇文献-算法】 PNN网络启发的神经网络结构搜索算法Progressive neural architecture search
本文提出了一种名为渐进式神经架构搜索(Progressive Neural Architecture Search, PNAS)的方法,它使用顺序模型优化策略和替代模型来逐步搜索并优化卷积神经网络结构,从而提高了搜索效率并减少了训练成本。
62 9
|
4月前
|
算法
【算法】递归、搜索与回溯——汉诺塔
【算法】递归、搜索与回溯——汉诺塔
|
4月前
|
存储 算法 调度
基于和声搜索算法(Harmony Search,HS)的机器设备工作最优调度方案求解matlab仿真
通过和声搜索算法(HS)实现多机器并行工作调度,以最小化任务完成时间。在MATLAB2022a环境下,不仅输出了工作调度甘特图,还展示了算法适应度值的收敛曲线。HS算法模拟音乐家即兴创作过程,随机生成初始解(和声库),并通过选择、微调生成新解,不断迭代直至获得最优调度方案。参数包括和声库大小、记忆考虑率、音调微调率及带宽。编码策略将任务与设备分配映射为和声,目标是最小化完成时间,同时确保满足各种约束条件。
下一篇
DataWorks