[C++]最小生成树--Prim算法&Kruskal算法

最小生成树–Prim算法&Kruskal算法

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

Prim算法

此算法采用贪心算法的思想,运行时间为O( n^2 )。

算法描述

基本步骤:

设R是有n个定点的对称连通关系。

  • 1)选取R的一个定点v1,设V = {v1}, E={}。
  • 2)选取与vj ∈ V邻接的V的最近邻元vi, 并且边(vi, vj)不与E中元素形成回路。添加vi到V中,添加(vi, vj)到E中。
  • 重复2),知道|E|=n - 1,于是V包含R的所有n个定点,E包含R的最小生成树。

矩阵描述

这里写图片描述

具体例子

这里写图片描述
这里写图片描述
这里写图片描述

代码思路

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

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[]数组不断刷新最小权值,lowi的值为: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[1][6]=2;

最后就为:

代码实现

//
//  main.cpp
//  prim
//
//  Created by 颜泽鑫 on 6/12/16.
//  Copyright © 2016 颜泽鑫. All rights reserved.
//

#include <limits.h>
#include <iostream>
#define MaxInt numeric_limits<int>::max()
#define MaxEle 120
using namespace std;
int map[MaxEle][MaxEle];
int visited[MaxEle];
int distance_low[MaxEle];
/**
 *  compute the minimal spanning tree
 *
 *  @param n the size of matrix
 *
 *  @return the minimal value of MST
 */
int prim(int n) {
  int pos, min, MST = 0;
  //  The array 'visited' represent which
  //  vertix has been visited already to avoid forming circle.
  visited[1] = 1;
  pos = 1;
  //  The array 'distance_distance_low' represent that
  //  arriving vertix i will cost distance_distance_low[i] steps now.
  for (int i = 2; i <= n; i++) {
    if (map[pos][i] == 0) {
      distance_low[i] = MaxInt;
    } else {
      distance_low[i] = map[pos][i];
    }
  }
  // manipulate n-1 times at all.
  for (int i = 1; i <= n - 1; i++) {
    min = MaxInt;
    // Find the minimal value of distance
    // and corresponding position.
    for (int j = 1; j <= n; j++) {
      if (visited[j] == 0 && min > distance_low[j]) {
        min = distance_low[j];
        pos = j;
      }
    }
    visited[pos] = 1;
    MST += min;
    //  Because of add a new vertix, it is neccessary to
    //  reload the information of 'distance_low'.
    for (int j = 1; j <= n; j++) {
      if (visited[j] == 0 && distance_low[j] > map[pos][j]) {
        if (map[pos][j] != 0) {
          distance_low[j] = map[pos][j];
        }
      }
    }
  }
  return MST;
}
int main() {
  int n;
  /**
   *  input the size of matrix
   */
  while (scanf("%d", &n) != EOF) {
    // Initialise the map;
    memset(map, MaxInt, sizeof(map));
    /**
     *  input matrix information in ordered.
     */
    for (int i = 1; i != n + 1; i++) {
      for (int j = 1; j != n + 1; j++) {
        int ele;
        cin >> ele;
        map[i][j] = map[j][i] = ele;
      }
    }
    int ans = prim(n);
    cout << ans << endl;
  }
  return 0;
}

Kruskal算法

Kruskal算法也是采用贪心算法的思想,运行时间为O(nlogn)。

算法描述

设R是有n个顶点的对称连通关系。S={e1,e2,e3…..ek}是R的加权边集合。

  • 1)在S中选择最小权的边e1,设E = {e1} 用S- {e1}取代S。
  • 2)在S中选最小权的边ei,并且不与E中的元素形成回路。用E∪{ei}代替E,并用S- {ei}取代S。
  • 3)重复步骤知道|E|=n-1.

代码设计

  • 1、利用优先级队列将权值小的边放到队列最前,优先出对,保证了每次选择的都是权值最小的边。

  • 2、利用并查集的查找及结合把同处同一连通分量中的顶点连到同一父节点下。这样,每次判断是否构成回路,只要判断父节点是否相同的即可。

并查集

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

而并查集的精妙之处在于用数来表示集合。如果把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]所在树的根结点。

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

代码实现

#include <iostream>
#include <queue>
using namespace std;
#define max 200

int MST; //  最小生成树
int Nodenum;
int father[max];
struct edge {
  int from;
  int to;
  int cost;
  friend bool operator < (const edge& a, const edge& b) {
    return a.cost > b.cost;
  }
};
// to find the minimal distance of edge.
priority_queue<edge> SQ;
/**
 *  to find the final father of the node.
 *
 *  @param x node index.
 *
 *  @return father index.
 */
int find(int x) {
  return x == father[x] ? x : find(father[x]);
}
int Kruskal() {
  MST = 0;
  for (int i = 1; i <= Nodenum; i++) {
    father[i] = i;
  }
  int num = 0;
  while (!SQ.empty() && num != Nodenum - 1) {
    edge temp = SQ.top();
    SQ.pop();
    int father_x = find(temp.from);
    int father_y = find(temp.to);
    if (father_x != father_y) {
      father[father_y] = find(father[father_x]);
      MST += temp.cost;
      num++;
    }
  }
  /**
   *  Let every index has their final father.
   */
  for (int i = 1; i <= Nodenum; i++) {
    father[i] = find(father[i]);
  }
  return MST;
}
bool judge() {
  int flag = father[1];
  for (int i = 2; i != Nodenum + 1; i++) {
    if (flag != find(father[i])) {
      return false;
    }
  }
  return true;
}
int main() {
  int edgeNum; //  边总数
  cin >> Nodenum;
  cin >> edgeNum;
  while (!SQ.empty()) {
    SQ.pop();
  }
  while (edgeNum--) {
    int from; int to; int cost;
    cin >> from >> to >> cost;
    edge a;
    a.from = from;
    a.to = to;
    a.cost = cost;
    SQ.push(a);
  }
  int MST = Kruskal();
  if (judge()) {
    cout << MST << endl;
  } else {
    cout << "Don't exist!" << endl;
  }
  return 0;
}

/*
 It daesn't matter the order of edge.
 standard sample:
 8
 11
 2 1 3
 3 1 2
 3 2 4
 2 4 2
 3 4 3
 4 5 5
 3 5 2
 5 6 4
 7 6 5
 8 5 5
 7 8 4
*/
阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/stary_yan/article/details/51427864
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭