Kruskal算法详解与证明过程

1,924 阅读3分钟

Kruskal算法:基于贪心算法得到带权连通图的最小生成树的算法

最小生成树

生成树

  1. 包含无向带权图的所有顶点
  2. 边数量为顶点数减1
  3. 并且是连通的

最小生成树:无向连通带权图的连通图集合中,权值最小的生成树

最小生成树.png

如上图所示、右边的子树就是左边图的最小生成树

V = {A,B,C,D,E,F}    // 生成树的顶点集合
E = {AB,BC,CD,CE,DF} // 生成树的边集合
W = 15               // 最小生成树的权重

Kruskal算法步骤

算法步骤

  1. 对所有边排序
  2. 依次访问当前最小边e
  3. 如果边e加入树T中,T不存在环,则把e加入T中;否则跳过
  4. 直到选择n-1条边加入T为止

详细过程举例

无向连通图G如下

image.png

首先对图G排序、从小到大依次为

image.png

访问当前最小边CD、因为无环加入子树T中:E={CD} image.png

访问当前最小边AC、因为无环加入子树T中:E={CD,AC} image.png

访问当前最小边AB、因为无环加入子树T中:E={CD,AC,AB} image.png

访问当前最小边BC、会导致ABC形成环,所以跳过 image.png

访问当前最小边BE、因为无环加入子树T中:E={CD,AC,AB,BE} image.png

访问当前最小边EF、因为无环加入子树T中:E={CD,AC,AB,BE,EF}; 并且因为边数量num(E)=num(V)-1,所以已经找到最小生成树 image.png

Kruskal算法证明

证明Kruskal算法分为两个步骤

  1. Kruskal算法生成的树是生成树
  2. Kruskal算法的生成树的权值是所有子树里最小的

对于图G(V,E,W),num(V)=n,Kruskal算法得到的树为T

T是生成树

先证明T是连通子图:假设T的顶点不是连通的,

  1. 那么由于T不存在环(Kruskal算法要求)、T是有多个连通子图构成即:T={T1、T2......}。
  2. 那么T边的数量:num(T) = num(T1)+num(T2)+...... = num(V1)-1 + num(V2)-1 + ...... 所以num(T)小于n-1,这与num(T)=n-1矛盾!
  3. 所以T是连通子图、由于没有环、T的顶点数肯定为n,所以T是生成树

T的权值最小

证明之前需要了解生成树的两个性质

  1. 加入任意一条不是生成树的边都会形成环 image.png

  2. 加入任意一条不是生成树的边形成环后,删除环的任意一条边都可以形成一个新的生成树 image.png

对于图G(V,E,W):令图G最小生成树为T1,Kruskal算法生成的数为T

  1. 假设在Kruskal算法过程中遇到第一条不在T1的边为e:e∈E(T)、e∉E(T1)
  2. 根据生成树的性质在T1中加入e,必然会生成一个环C;并且由于T中不存在环,所以C中必有一条边f∉T,f∈T1
  3. 根据生成树的性质T1+e-f的树也是生成树、并且W(T1+e-f)>=W(T1),得到W(e)>=W(f)
  4. 所以在Kruskal算法过程中遇到e时,我们同样可以选择W(f);依次类推最后T会和T1完全相同
  5. 所以证明T也是最小生成树

JS实现(邻接矩阵)

/**
 * 获取链表尾部结点
 * @param nextArray
 * @param start
 * @return {*}
 */
function findEndNode (nextArra```
对于图G(V,E,W):令图G最小生成树为T1,Kruskal算法生成的数为T
```y, start) {
  while (nextArray[start] !== null) {
    start = nextArray[start];
  }
  return start;
}

/**
 * kruskal算法
 * @param matrix
 * @return {*[{i,j,weight}]}
 */
function kruskal (matrix) {
  let edgeList = [];
  for (let i = 0; i < matrix.length; i++) {
    for (let j = 0; j < matrix.length; j++) {
      let weight = matrix[i][j];
      edgeList.push({i, j, weight});
    }
  }
  // 所有边从小到大排列
  edgeList.sort((a, b) => {
    return a.weight - b.weight;
  })

  // 记录树T的所有边
  let res = [];
  // 子树链表结构的下一个结点
  let nextArray = [];
  nextArray.length = matrix.length;
  nextArray.fill(null);

  while (res.length < matrix.length - 1) {
    let edge = edgeList.shift();
    let u = findEndNode(nextArray, edge.i);
    let v = findEndNode(nextArray, edge.j);
    if (u !== v) {
      res.push(edge);
      nextArray[u] = v;
    }
  }
  return res;
}

PS

以上步骤截图都来自小程序:数据结构算法演示。小程序包含常用的数据结构算法演示、欢迎大家使用

数据结构算法演示1.jpg