Prim算法+Kruskal算法

本文详细介绍了两种用于寻找图的最小生成树的经典算法——Prim算法与Kruskal算法。Prim算法通过迭代选择与当前生成树连接且权重最小的边来逐步构建最小生成树。Kruskal算法则按边的权重从小到大排序,依次添加不会形成环的边。文中还提供了两种算法的具体实现代码。

转自:http://www.cnblogs.com/Veegin/archive/2011/04/29/2032388.html

http://www.cnblogs.com/Veegin/archive/2011/04/29/2032423.html


Prim算法:


今天从志权师兄那里学会了最小生成树。所谓生成树,就是n个点之间连成n-1条边的图形。而最小生成树,就是权值(两点间直线的值)之和的最小值。

  

         首先,要用二维数组记录点和权值。如上图所示无向图:

int map[7][7];
       map[1][2]=map[2][1]=4;
       map[1][3]=map[3][1]=2;
       ......

      然后再求最小生成树。具体方法是:

1.先选取一个点作起始点,然后选择它邻近的权值最小的点(如果有多个与其相连的相同最小权值的点,随便选取一个)。如1作为起点。

visited[1]=1;

pos=1;

//用low[]数组不断刷新最小权值,low[i](0<i<=点数)的值为:i点到邻近点(未被标记)的最小距离。

low[1]=0;  //起始点i到邻近点的最小距离为0

low[2]=map[pos][2]=4;

low[3]=map[pos][3]=2;

low[4]==map[pos][4]=3;

low[5]=map[pos][5]=MaxInt;  //无法直达

low[6]=map[pos][6]=MaxInt;

 

  2.再在伸延的点找与它邻近的两者权值最小的点。

//low[]以3作当前位置进行更新

visited[3]=1;

pos=3;

low[1]=0;   //已标记,不更新

low[2]=map[1][2]=4;  //比5小,不更新

low[3]=2;  //已标记,不更新

low[4]=map[1][4]=3;   //比1大,更新后为:low[4]=map[3][4]=1;

low[5]=map[1][5]=MaxInt;//无法直达,不更新

low[6]=map[1][6]=MaxInt;//比2大,更新后为:low[6]=map[3][6]=2;

 

    3.如此类推...

 
 
     当所有点都连同后,结果最生成树如上图所示。

     所有权值相加就是最小生成树,其值为2+1+2+4+3=12。

代码如下:

#include <stdio.h>
#include <string.h>
#define MaxInt 0x3f3f3f3f
#define N 110
//创建map二维数组储存图表,low数组记录每2个点间最小权值,visited数组标记某点是否已访问
int map[N][N],low[N],visited[N];
int n;
 
int prim()
{
    int i,j,pos,min,result=0;
    memset(visited,0,sizeof(visited));
//从某点开始,分别标记和记录该点
    visited[1]=1;pos=1;
//第一次给low数组赋值
    for(i=1;i<=n;i++)
        if(i!=pos) low[i]=map[pos][i];
//再运行n-1次
    for(i=1;i<n;i++)
    {
//找出最小权值并记录位置
     min=MaxInt;
     for(j=1;j<=n;j++)
         if(visited[j]==0&&min>low[j])
         {
             min=low[j];pos=j;
         }
//最小权值累加
    result+=min;
//标记该点
    visited[pos]=1;
//更新权值
    for(j=1;j<=n;j++)
        if(visited[j]==0&&low[j]>map[pos][j])
            low[j]=map[pos][j];
    }
    return result;
}
 
int main()
{
    int i,v,j,ans;
    while(scanf("%d",&n)!=EOF)
    {
//所有权值初始化为最大
        memset(map,MaxInt,sizeof(map));
        for(i=1;i<=n;i++)
            for(j=1;j<=n;j++)
            {
                scanf("%d",&v);
                map[i][j]=map[i][j]=v;
            }
            ans=prim();
            printf("%d\n",ans);
    }
    return 0;
}



Kruskal算法:

对于稀疏图来说,用Kruskal写最小生成树效率更好,加上并查集,可对其进行优化。

Kruskal算法的步骤:

1.对所有边进行从小到大的排序。

2.每次选一条边(最小的边),如果如果形成环,就不加入(u,v)中,否则加入。那么加入的(u,v)一定是最佳的。

并查集:
我们可以把每个连通分量看成一个集合,该集合包含了连通分量的所有点。而具体的连通方式无关紧要,好比集合中的元素没有先后顺序之分,只有“属于”与“不属于”的区别。图的所有连通分量可以用若干个不相交集合来表示。

而并查集的精妙之处在于用数来表示集合。如果把x的父结点保存在p[x]中(如果没有父亲,p[x]=x),则不难写出结点x所在树的递归程序:

find(int x) {return p[x]==x?x:p[x]=find(p[x]);}

意思是,如果p[x]=x,说明x本身就是树根,因此返回x;否则返回x的父亲p[x]所在树的根结点。

既然每棵树表示的只是一个集合,因此树的形态是无关紧要的,并不需要在“查找”操作之后保持树的形态不变,只要顺便把遍历过的结点都改成树根的儿子,下次查找就会快很多了。如下图所示:

设第i条边的端点序号和权值分别保存在u[i],v[i],w[i]中,而排序后第i小的边保存在r[i]中。(间接排序是指排序的关键字是对象的代号,而不是对象本身。)
#include <stdio.h>
#include <stdlib.h>
#include <algorithm>
#define N 150
using namespace std;
int m,n,u[N],v[N],w[N],p[N],r[N];
int cmp(const int i,const int j) {return w[i]<w[j];}
int find(int x) {return p[x]==x?x:p[x]=find(p[x]);}
int kruskal()
{
    int cou=0,x,y,i,ans=0;
    for(i=0;i<n;i++) p[i]=i;
    for(i=0;i<m;i++) r[i]=i;
    sort(r,r+m,cmp);
    for(i=0;i<m;i++)
    {
        int e=r[i];x=find(u[e]);y=find(v[e]);
        if(x!=y) {ans += w[e];p[x]=y;cou++;}
    }
    if(cou<n-1) ans=0;
    return ans;
}
 
int main()
{
    int i,ans;
    while(scanf("%d%d",&m,&n)!=EOF&&m)
    {
        for(i=0;i<m;i++)
        {
            scanf("%d%d%d",&u[i],&v[i],&w[i]);
        }
        ans=kruskal();
        if(ans) printf("%d\n",ans);
        else printf("?\n",ans);
    }
    return 0;
}


### Prim算法Kruskal算法的比较 #### 工作原理的不同 Prim算法是从任意一个顶点开始构建最小生成树,逐步扩展已有的部分直到覆盖所有顶点。每次迭代过程中选取当前未被纳入生成树集合中的离已有结构最近的一个节点并将其加入其中[^4]。 相比之下,Kruskal算法则侧重于处理边而非顶点。该方法首先按照权值升序排列所有的边,在遍历这些有序列表的同时尝试添加每条边至正在形成的森林里——只要这样做不会形成环路即可[^5]。 #### 数据结构的选择 对于Prim算法而言,通常会利用优先队列来高效找到下一个要访问的最佳候选者;而在实现上可以借助二叉堆或是斐波那契堆等高级数据结构进一步优化性能表现[^3]。 另一方面,由于Kruskal算法涉及频繁查询两个端点是否属于同一连通分量的操作,因此一般配合不相交集(Union-Find)的数据结构使用以加速此类判断过程[^1]。 #### 时间复杂度分析 当面对稠密图时,即边的数量接近于\(V^2\)的情况之下,Prim算法能够展现出更优的时间效率,特别是通过邻接矩阵表示法下的朴素版本更是如此。然而,在稀疏图的情况下,则应考虑采用基于边表表达形式的Kruskal方案,其主要开销在于初始阶段对全部边实施排序操作\[O(E\log E)\]。 #### 应用场景建议 针对具体的应用环境选择合适的最小生成树算法至关重要: - 对于较为密集的网络拓扑结构,推荐选用Prim算法; - 如果待解决问题所对应的图形呈现明显稀疏特征,则更适合应用Kruskal算法来进行求解。 ```cpp // 示例代码展示两种算法的核心逻辑差异 (C++) // Prim Algorithm Core Logic void prim(vector<vector<int>>& graph, int start){ priority_queue<pair<int,int>, vector<pair<int,int>>, greater<>> pq; vector<bool> visited(graph.size(), false); pq.push({0,start}); while(!pq.empty()){ auto [weight,node]=pq.top(); pq.pop(); if(visited[node]) continue; visited[node]=true; // Process node and add adjacent nodes to the queue... } } // Kruskal Algorithm Core Logic struct Edge { int u,v,w; }; bool cmp(const Edge& a,const Edge& b){return a.w<b.w;} int find(int x,vector<int>& parent); void kruskal(vector<Edge>& edges, int n){ sort(edges.begin(),edges.end(),cmp); vector<int>parent(n),rank(n,0); iota(parent.begin(),parent.end(),0); for(auto &e : edges){ int pu=find(e.u,parent),pv=find(e.v,parent); if(pu!=pv){ union_sets(pu,pv,parent,rank); // Add edge e into MST ... } } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值