• 图论模板详解


    目录

    Floyd算法

    例题:蓝桥公园

    Dijkstra算法

    例题:蓝桥王国 

    SPFA算法

    例题:随机数据下的最短路问题

    总结

    最小生成树MST

    Prim算法

    Kruskal算法

    例题:聪明的猴子

    Floyd算法

    最简单的最短路径算法,使用邻接矩阵存图,因为Floyd算法计算的结果是所有点对之间的最短路,本身就要n^{2}的空间,用矩阵存储最合适。效率不高,计算复杂度为O\left ( n^{3} \right ),只能用于n<300的小规模的图,不能用于大图,在某些场景下有自己的优势,难以替代,能做传递闭包问题。

    1. for(int k=1;k<=n;k++){
    2. for(int i=1;i<=n;i++){
    3. for(int j=1;j<=n;j++){
    4. dp[i][j]=min(dp[i][j],d[i][k]+dp[k][j]);
    5. }
    6. }
    7. }

    Floyd算法是多源最短路算法,以此计算能得到图中每一对结点之间(多对多)的最短路径。

    Floyd算法能判断负圈,若图中有权值为负的边,某个经过这个负边的环路,所有边长相加的总长度也是负数,这就是负圈。在这个负圈上每绕一圈,总长度就更小,从而陷入在兜圈子的死循环。Floyd算法很容易判断负圈,只要在算法运行过程中出现任意一个dp[i][j]<0就说明有负圈,因为dp[i][j]是从i出发,经过其它中转点绕一圈回到自己的最短路径,如果等于0,就存在负圈。

    例题:蓝桥公园

    1. #include<bits/stdc++.h>
    2. using namespace std;
    3. const long long INF=0x3f3f3f3f3f3f3f3fLL;
    4. const int N=405;
    5. long long dp[N][N];
    6. int n,m,q;
    7. void floyd(){
    8. for(int k=1;k<=n;k++){
    9. for(int i=1;i<=n;i++){
    10. for(int j=1;j<=n;j++){
    11. dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]);
    12. }
    13. }
    14. }
    15. }
    16. int main(){
    17. cin>>n>>m>>q;
    18. memset(dp,0x3f,sizeof(dp));
    19. for(int i=1;i<=m;i++){
    20. int u,v;
    21. long long w;
    22. cin>>u>>v>>w;
    23. dp[u][v]=dp[v][u]=min(dp[u][v],w);
    24. }
    25. floyd();
    26. while(q--){
    27. int s,t;
    28. cin>>s>>t;
    29. if(dp[s][t]==INF){
    30. cout<<"-1"<<endl;
    31. }
    32. else if(s==t){
    33. cout<<"0"<<endl;
    34. }
    35. else{
    36. cout<<dp[s][t]<<endl;
    37. }
    38. }
    39. return 0;
    40. }

    Dijkstra算法

    Dijkstra算法用于求解单源最短路径问题,非常高效而且稳定,但是只能处理不含负权边的图。

    Dijkstra算法是贪心思想实现的,首先把起点到所有点的距离存下来找个最短的,然后松弛一次再找出最短的,所谓的松弛操作就是,遍历一遍看通过刚刚找到的距离最短的点作为中转站会不会更近,如果更近了就更新距离,这样把所有的点找遍之后就存下了起点到其它所有点的最短距离。

    采用优先队列实现,每次往队列中放数据时,按从小到大的顺序放,采用小顶堆的方式,复杂度是O\left ( logn \right ),保证最小的数总在最前面。找最小值,直接取第一个数,复杂度是O\left ( 1 \right )

    例题:蓝桥王国 

    1. #include<bits/stdc++.h>
    2. using namespace std;
    3. const long long INF=0x3f3f3f3f3f3f3f3fLL;
    4. const int N=3e5+2;
    5. struct edge{
    6. int from,to;
    7. long long w;
    8. edge(int a,int b,long long c){
    9. from=a;
    10. to=b;
    11. w=c;
    12. }
    13. };
    14. vector<edge>e[N];
    15. struct s_node{
    16. int id;
    17. long long n_dis;
    18. s_node(int b,long long c){
    19. id=b;
    20. n_dis=c;
    21. }
    22. bool operator < (const s_node &a) const{
    23. return n_dis>a.n_dis;
    24. }
    25. };
    26. int n,m;
    27. long long dis[N];
    28. void dijkstra(){
    29. int s=1;
    30. bool done[N];
    31. for(int i=1;i<=n;i++){
    32. dis[i]=INF;
    33. done[i]=false;
    34. }
    35. dis[s]=0;
    36. priority_queue<s_node>Q;
    37. Q.push(s_node(s,dis[s]));
    38. while(!Q.empty()){
    39. s_node u=Q.top();
    40. Q.pop();
    41. if(done[u.id]){
    42. continue;
    43. }
    44. done[u.id]=true;
    45. for(int i=0;i<e[u.id].size();i++){
    46. edge y=e[u.id][i];
    47. if(done[y.to]){
    48. continue;
    49. }
    50. if(dis[y.to]>y.w+u.n_dis){
    51. dis[y.to]=y.w+u.n_dis;
    52. Q.push(s_node(y.to,dis[y.to]));
    53. }
    54. }
    55. }
    56. }
    57. int main(){
    58. cin>>n>>m;
    59. for(int i=1;i<=n;i++){
    60. e[i].clear();
    61. }
    62. while(m--){
    63. int u,v,w;
    64. cin>>u>>v>>w;
    65. e[u].push_back(edge(u,v,w));
    66. }
    67. dijkstra();
    68. for(int i=1;i<=n;i++){
    69. if(dis[i]>=INF){
    70. cout<<"-1";
    71. }
    72. else{
    73. cout<<dis[i];
    74. }
    75. }
    76. return 0;
    77. }

    SPFA算法

    SPFA算法=队列处理+Bellman-Ford

    Bellman-Ford算法有很多低效或无效的操作,其核心内容,是在每一轮操作中,更新所有节点到起点s的最短距离。

    计算和调整一个节点u到s的最短距离后,如果紧接着调整u的邻居节点,这些邻居肯定有新的计算结果,而如果漫无目的的计算不与u相邻的节点,这可能毫无变化,所以这些操作是低效的。

    改进:计算结点u之后,下一步只计算和调整它的邻居,能加速收敛的过程。这些步骤用队列操作

    例题:随机数据下的最短路问题

     

    1. #include<bits/stdc++.h>
    2. using namespace std;
    3. const long long INF=0x3f3f3f3f3f3f3f3f;
    4. const int N=5e3+10;
    5. struct edge{
    6. int to;
    7. long long w;
    8. edge(int tt,long long ww){
    9. to=tt;
    10. w=ww;
    11. }
    12. };
    13. long long dist[N];
    14. int inq[N];
    15. vector<edge>e[N];
    16. void spfa(int s){
    17. memset(dist,0x3f,sizeof(dist));
    18. dist[s]=0;
    19. queue<int>q;
    20. q.push(s);
    21. inq[s]=1;
    22. while(!q.empty()){
    23. int u=q.front();
    24. q.pop();
    25. inq[u]=0;
    26. if(dist[u]==INF){
    27. continue;
    28. }
    29. for(int i=0;i<e[u].size();i++){
    30. int v=e[u][i].to;
    31. long long w=e[u][i].w;
    32. if(dist[v]>dist[u]+w){
    33. dist[v]=dist[u]+w;
    34. if(!inq[v]){
    35. q.push(v);
    36. inq[v]=1;
    37. }
    38. }
    39. }
    40. }
    41. }
    42. int main(){
    43. int n,m,s;
    44. cin>>n>>m>>s;
    45. for(int i=1;i<=m;i++){
    46. int u,v;
    47. long long w;
    48. cin>>u>>v>>w;
    49. e[u].push_back(edge(v,w));
    50. }
    51. spfa(s);
    52. for(int i=1;i<=n;i++){
    53. if(dist[i]==INF){
    54. cout<<-1;
    55. }
    56. else{
    57. cout<<dist[i];
    58. }
    59. if(i!=n){
    60. cout<<" ";
    61. }
    62. else{
    63. cout<<endl;
    64. }
    65. }
    66. return 0;
    67. }

    总结

    单源最短路

    (1)当权值非负时,用Dijkstra算法。

    (2)当权值有负值,且没有负圈,则用SPFA。SPFA能检测负圈,但是不能输出负圈。

    (3)当权值有负值而且有负圈需要输出,则用Bellman-Ford,能够检测并输出负圈。

    多源最短路

    使用Floyd算法。

    最小生成树MST

    一个含有n个结点的连通图的生成树是原图的极小连通子图,包含原图中的所有n个结点,并且边的权值之和最小。

    Prim算法

    对点进行贪心操作,从任意一个点u开始,把距离它最近的点加入到MST中,下一步,把距离{u,v}最近的点w加入到MST中;继续这个过程,直到所有的点都在MST中。适用于稠密图。

    1. #include<bits/stdc++.h>
    2. using namespace std;
    3. const int INF=0x3f3f3f3f3f3f3f3f;
    4. const int MAXN=1005;
    5. vector<int>demo;
    6. int closest[MAXN],lowcost[MAXN],n,m;//m为节点的个数,n为边的数量
    7. int G[MAXN][MAXN];//邻接矩阵
    8. int prim(){
    9. for(int i=0;i<n;i++){
    10. lowcost[i]=INF;
    11. }
    12. for(int i=0;i<m;i++){
    13. closest[i]=0;
    14. }
    15. closest[0]=-1;//加入第一个点,-1表示该点在集合U中,否则在集合V中
    16. int num=0,ans=0,e=0;
    17. while(num<m-1){//当点还没有全部放进去
    18. int micost=INF;
    19. for(int i=0;i<m;i++){
    20. if(closest[i]!=-1){
    21. int temp=G[e][i];
    22. if(temp<lowcost[i]){
    23. lowcost[i]=temp;
    24. closest[i]=e;
    25. }
    26. if(lowcost[i]<micost){
    27. micost=lowcost[i];
    28. }
    29. }
    30. ans+=micost;
    31. demo.push_back(micost);
    32. closest[e]=-1;
    33. num++;
    34. }
    35. }
    36. return ans;
    37. }
    38. int main(){
    39. cin>>m>>n;
    40. memset(G,INF,sizeof(G));
    41. for(int i=0;i<n;i++){
    42. int a,b,c;
    43. cin>>a>>b>>c;
    44. G[b][a]=G[a][b]=c;
    45. }
    46. cout<<prim()<<endl;
    47. for(int i=0;i<m-1;i++){
    48. cout<<demo[i]<<" ";
    49. }
    50. return 0;
    51. }

    Kruskal算法

    对边进行贪心操作。从最短的边开始,把它加入到MST中,在剩下的边中找最短的边,加入到        MST中,继续这个过程,直到所有的点都在MST中。适用于稀疏图。

    kruskal算法的两个关键技术:

    (1)对边进行排序

    (2)判断圈,即处理连通性问题。这个问题用并查集简单而高效,并查集是krustral算法的绝配。

    例题:聪明的猴子

    1. #include<bits/stdc++.h>
    2. using namespace std;
    3. int a[5005],x[5005],y[5005],f[5005];
    4. struct Edge{
    5. int x,y;
    6. double w;
    7. }edge[1000005];
    8. int find(int x){
    9. if(x==f[x]){
    10. return x;
    11. }
    12. f[x]=find(f[x]);
    13. return f[x];
    14. }
    15. bool cmp(Edge a,Edge b){
    16. return a.w<b.w;
    17. }
    18. void merge(int x,int y){
    19. int xx=find(x);
    20. int yy=find(y);
    21. if(xx!=yy){
    22. f[yy]=xx;
    23. }
    24. }
    25. int main(){
    26. int n,m,cnt=0;
    27. scanf("%d",&m);
    28. for(int i=1;i<=m;i++){
    29. scanf("%d",&a[i]);
    30. }
    31. scanf("%d",&n);
    32. for(int i=1;i<=n;i++){
    33. scanf("%d",&x[i],&y[i]);
    34. }
    35. for(int i=1;i<=n;i++){
    36. f[i]=i;
    37. }
    38. for(int i=1;i<=n;i++){
    39. for(int j=i+1;j<=n;j++){
    40. double w=sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]*y[j]));
    41. edge[++cnt]={i,j,w};
    42. }
    43. }
    44. sort(edge+1,edge+cnt+1,cmp);
    45. int num=0;
    46. double maxn=0.0;
    47. for(int i=1;i<=cnt;i++){
    48. if(find(edge[i].x)!=find(edge[i].y)){
    49. merge(edge[i].x,edge[i].y);
    50. num++;
    51. maxn=maxn>=edge[i].w?maxn:edge[i].w;
    52. }
    53. if(num==n-1){
    54. break;
    55. }
    56. }
    57. int ans=0;
    58. for(int i=1;i<=m;i++){
    59. if(a[i]>=maxn){
    60. ans++;
    61. }
    62. }
    63. printf("%d\n",ans);
    64. return 0;
    65. }
  • 相关阅读:
    笔试面试相关记录(11)
    OpenCV #以图搜图:均值哈希算法(Average Hash Algorithm)原理与实验
    odoo 开发入门教程系列-模块交互
    Java “constant string too long” 编译错误
    C 与 C++ 的真正区别在哪里?
    centos7服务器环境配置详细教程(nginx、node、MongoDB、MySQL)
    CNN - nn.Conv1d使用
    【后台执行脚本】
    C# 之 扑克游戏 -- 21点规则介绍和代码实现
    论文解读《Deep Attention-guided Graph Clustering with Dual Self-supervision》
  • 原文地址:https://blog.csdn.net/m0_72674633/article/details/137255223