最小生成树问题(Prim算法和Kruskal算法)

问题引入:

        这算是一道模板题了,只不过这次在做的时候感觉又学到了些新的东西,之前都是数据结构里学的,因为用惯了C++,所以就想摆脱那些邻接数组之类的写法,用STL试一下,在其中把我遇到的一些问题写出来分享给大家:

关于最小生成树:

       一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。 [1]  最小生成树可以用kruskal(克鲁斯卡尔)算法或prim(普里姆)算法求出。

        其次是关于最小生成树的唯一性问题一直困扰了我很长时间,网上看到些答案都说是不唯一的,但是它的权值却是唯一的,举个例子,等边三角形的三个顶点,不管连接哪两个点都是最小生成树,所以在Prim算法中,我们可以任意取一条边的一个顶点作为第一条进入最小生成树的一条边,也可以将各个边按边权排序后让最小的边进入,在上学期的数据结构中,我一直是默认让第一条边直接进入最小生成树,这里可能有人问,万一你选的那一条边恰好不在最小生成树的边集中呢?起初我也是这么想的,但是prim算法每次都是把当前最小的边加入最小生成树,所以一个点要想进入最小生成树,就需要和其他的点连通,怎样保证连通且最小的呢,当然是选择最小的边,选择任意的一个顶点开始之所以成立,就是因为最小生成树不仅仅可以从叶节点开始找,也可以从根节点开始找,只不过要保证在寻找过程中不能连通(一般采用visited数组来标记已访问顶点)。

1.Prim算法(筛选节点)

      思路:先将各个边按权值由小到大排序,先选择边权最小的边,设一个集合来判断节点,当节点数等于n时就可以找到最小的生成树

class Solution {
public:
   
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 返回最小的花费代价使得这n户人家连接起来
     * @param n int n户人家的村庄
     * @param m int m条路
     * @param cost intvector<vector<>> 一维3个参数,表示连接1个村庄到另外1个村庄的花费的代价
     * @return int
     */
    static bool cmp(vector<int>& x, vector<int>& y){ //重载比较,按照边权递增
        return x[2] < y[2];
    }
    
    int miniSpanningTree(int n, int m, vector<vector<int> >& cost) {
        // write code here
       unordered_set<int> points;//集合可以避免重复
       int ans=0;//权值结果
       sort(cost.begin(),cost.end(),cmp);
       ans+=cost[0][2];//排序后第一条便即为最小边
       points.insert(cost[0][0]);
       points.insert(cost[0][1]);
       //点入集合
       while(points.size()<n)
       {
        for(auto iter=cost.begin();iter!=cost.end();iter++)
        {
            //寻找与当前集合内的点相连的未选择的边
            if((points.find((*iter)[0]) != points.end() && points.find((*iter)[1]) == points.end()) || (points.find((*iter)[1]) != points.end() && points.find((*iter)[0]) == points.end()))
            {
                ans+=(*iter)[2];
                points.insert((*iter)[0]);
                points.insert((*iter)[1]);
                cost.erase(iter);
                break;
            }
        }
       }
       return ans;

    }
};

2.kruskal算法

前言:并查集

      并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这就类似于族谱,如果想调查你和一个人是否有亲戚,那就从你和那个人开始往上调查族谱,找到了两个人的祖先有关系,那就是亲戚。

       思路:初始时将所有顶点的边全都看作没有在集合内,定义声明寻找顶点父亲节点的函数,在选择边时,如果这条边的两个顶点的父节点相等,那么表示加入该边后会构成回路,所以判断出该边不可取

class Solution {
public:
   
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 返回最小的花费代价使得这n户人家连接起来
     * @param n int n户人家的村庄
     * @param m int m条路
     * @param cost intvector<vector<>> 一维3个参数,表示连接1个村庄到另外1个村庄的花费的代价
     * @return int
     */
    static bool cmp(vector<int>& x, vector<int>& y){ //重载比较,按照边权递增
        return x[2] < y[2];
    }
    int find(vector<int>&parent,int x)
    {
        if(parent[x]!=x)//该点还没有被选择
         parent[x]=find(parent,parent[x]);//parent[x]中保存的是x节点的父亲,但是不是祖先
         return parent[x];
    }
    int miniSpanningTree(int n, int m, vector<vector<int> >& cost) {
        // write code here
       vector<int> parent(n+1);
       for(int i=0;i<=n;i++)
        parent[i]=i;//初始时将自己的祖先节点设置为自己
       sort(cost.begin(),cost.end(),cmp);
       int ans=0;
       for(auto iter=cost.begin();iter!=cost.end();iter++)
       {
         int x=(*iter)[0];
         int y=(*iter)[1];
         int px=find(parent,x);
         int py=find(parent,y);
         if(px!=py)//表示当前的边加入不会构成回路,可以加入最小生成树
         {
               ans+=(*iter)[2];
               parent[px]=py;//更新其父亲节点表示在同一个集合
         }
       }
       return ans;
    }
};

以上便是我对最小生成树算法的进一步理解,以上见解如有错误请不吝赐教,希望可以共同进步,谢谢大家。

最好的药物是忙碌,最好的治愈是读书;

最好的爱情是自爱,最好的自爱是自律。

不要在二十出头的年纪就整天为了那点感情emo,等你成功的那天,双向奔赴的那个人就一定会出现的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值