图的最小生成树(Kruskal算法&Prim算法)

最小生成树

在生活中,我们可能会遇到如下的情景。在某个地方分布着N个村庄,现在需要在N个村庄之间修路,每个村庄之间的距离不同,问怎么修最短的路,将各个村庄连接起来。这个问题可以归纳为最小生成树问题,用正则表达式的表述方式描述为:给定一个无向的带权图G=(V,E),最小生成树的集合为T,T是最小代价连接V中所有顶点所用变E的最小集合,集合T中的边能够形成一棵树,这是因为每个结点(除根节点外)都能向上找到它的一个父节点。

最小生成树的性质

连通图中的每一棵生成树,都是原图的一个极大无环子图。即:从中删去任何一条边,生成树都不再连通,反之,在其中加入任何一条边,都会形成一条回路。

若连通图由n个顶点组成,则其生成树必含n个顶点和n-1条边。因此,构造最小生成树的准则有以下三条:
1. 只能使用图中的边来构造最下生成树
2. 只能使用恰好n-1条边来连接图中的n个顶点。
3. 选用的n-1条边不能构成回路

构造最小生成树的算法包括:Kruskal算法和Prim算法。这两个算法均采用了逐步求解的贪心策略。

贪心策略指的是在解决某一个问题时,总是做出当前看起来最好的选择,不是整体最优的,只是局部最优。贪心算法不是对所有的问题都能得到整体最优解。

Kruskal算法

首先,将每个顶点放入其自身的数据集合中。然后,按照权值的升序来选择边。当选择每条边时,判断定义边的顶点是否在不同的数据集中。如果是,将此边插入最小生成树的集合中,同时,将集合中包含每个顶点的联合体取出,如果不是,就移动到下一条边。重复这个过程直到所有的边都探查过。
这里写图片描述

typedef Graph<V, W, IsDirect> Self;
     // 最小生成树---克鲁斯卡尔算法;了解普里姆算法
     Self Kruskal()
     {
          Self g;//创建一个图,保存当前图
          g._v = _v;
          g._linkEdges.resize(_v.size());
          vector<PNode> edges;//用来保存边,滤过重复边
          for (size_t i = 0; i < _v.size(); ++i)
          {
              PNode pCur = _linkEdges[i];
              while (pCur)//遍历
              {
                   if (IsDirect || !IsDirect && (pCur->_src < pCur->_dst))
                        edges.push_back(pCur);
                   pCur = pCur->_pNext;
              }
          }
          //比较权值的仿函数,重载括号
          class Compare
          {
          public:
              bool operator()(const PNode left, const PNode right)
              {
                   return left->_weight < right->_weight;
              }
          };
          sort(edges.begin(), edges.end(), Compare());//按照权值排序

          size_t count = _v.size() - 1;//n个顶点,n-1条边
          UnionFindSet u(_v.size());//创建一个并查集
          for (size_t i = 0; i < edges.size(); ++i)
          {
              PNode pCur = edges[i];
              size_t srcRoot = u.FindRoot(pCur->_src);//找到组长元素
              size_t dstRoot = u.FindRoot(pCur->_dst);
              if (srcRoot != dstRoot)//不在一个组,即没有形成回路,加边
              {
                   g.AddEdge(_v[pCur->_src], _v[pCur->_dst], pCur->_weight);
                   if (!IsDirect)//无向图
                        g.AddEdge(_v[pCur->_dst], _v[pCur->_src], pCur->_weight);
                   u.Union(pCur->_src,pCur->_dst);//合并两个集合
                   count--;
                   if (count == 0)
                        break;
              }
          }
          if (count > 0)
              cout << "最小生成树非法" << endl;
          return g;
     }

Prim算法

该算法于1930年由捷克数学家沃伊捷赫·亚尔尼克(英语:Vojtěch Jarník)发现;并在1957年由美国计算机科学家罗伯特·普里姆(英语:Robert C. Prim)独立发现;1959年,艾兹格·迪科斯彻再次发现了该算法。

Prim算法指的是从任意一个顶点开始,每次选择一个与当前顶点集最近的一个顶点,并将两顶点之间的边加入到树中。Prim算法在找当前最近顶点时使用到了贪婪算法。
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值