20230404 8.1. 最小生成树问题

概念

  • 最小生成树(Minimum Spanning Tree)
  • 最小生成树存在 ↔ 图连通
  • 是一棵树
    • 无回路
    • |V|个顶点一定有|V|-1条边
    • 向生成树中任加一条边都一定构成回路
  • 是生成树
    • 包含全部顶点
    • |V|-1条边都在图里
  • 边的权重和最小

贪心算法

  • 什么是“贪”:每一步都要最好的
  • 什么是“好”:权重最小的边
  • 需要约束:
    • 只能用图里有的边
    • 只能正好用掉|V|-1条边
    • 不能有回路
  • 两种算法:
    • Prim算法(普里姆算法)
    • Kruskal算法(克鲁斯卡尔算法)

Prim算法(普里姆算法) — 让一棵小树长大

  • 从起始节点开始,每次都加入最小权重的边和节点
  • 参考博客
  • 和最短路径问题的 Dijkstra 算法(迪杰斯特拉算法)有相似之处
  • T = O( |V|2 )
  • 稠密图合算
void Prim()
{ 
    MST = {s};
    while (1) {
        V = 未收录顶点中dist最小者;
        if ( 这样的V不存在 )
            break; 
        将V收录进MST: dist[V] = 0;
        for ( V 的每个邻接点 W )
            if ( W未被收录 ) 
                if ( E(V,W) < dist[W] ){
                    dist[W] = E(V,W) ;
                    parent[W] = V;
                }
    }
    if ( MST中收的顶点不到|V|个 )
        Error ( “生成树不存在” );
}

代码实现

/*
* prim最小生成树
*
* 参数说明:
*   start -- 从图中的第start个元素开始,生成最小树
*/
public void prim2(int start) {

    boolean[] collected = new boolean[size];
    collected[start] = true;

    int[] dist = new int[size];
    System.arraycopy(edges[start], 0, dist, 0, size);
    dist[start] = 0;

    // 这里path和以上不同,以上的path是记录索引路过所在节点的上一个节点的索引
    // 这里path是路过的节点顺序
    List<Integer> path = ListUtil.toList(start);


    while (true) {
        // 寻找最短路径相接的节点
        int min = Integer.MAX_VALUE, v = -1;
        for (int j = 0; j < size; j++) {
            if (!collected[j] && min > dist[j]) {
                min = dist[j];
                v = j;
            }
        }
        if (v == -1) {
            // 无法找到
            break;
        }
        collected[v] = true;
        path.add(v);


        // 当第w个顶点被加入到最小生成树的结果数组中之后,更新其它顶点的权值。
        for (int w = 0; w < size; w++) {
            // 当第w个节点没有被处理,并且需要更新时才被更新。
            if (!collected[w] && edges[v][w] < dist[w]) {
                dist[w] = edges[v][w];
            }
        }
    }

    if (path.size() != size) {
        throw new RuntimeException("生成树不存在");
    }

    // 打印最小生成树
    Console.log("start:", start);
    Console.log("path:", path);
    Console.log("dist:", dist);
    Console.log("最小生成树权重:", NumberUtil.add(Arrays.stream(dist).boxed().toArray(Number[]::new)));
}

Kruskal算法(克鲁斯卡尔算法) — 将森林合并成树

  • 每次选择权重最小的边,判断是否构成回路
  • 参考博客
  • T = O( |E| log |E| )
void Kruskal ( Graph G )
{ 
    MST = { } ;
    while ( MST 中不到 |V|-1 条边 && E 中还有边 ) {
        从 E 中取一条权重最小的边 E(v,w) ;  /* 最小堆 */
        将E(v,w)从 E 中删除;
        if ( E(V,W)不在 MST 中构成回路)     /* 并查集 */
            将E(V,W) 加入 MST;
        else
            彻底无视 E(V,W);
    }
    if ( MST 中不到 |V|-1 条边 )
        Error ( “生成树不存在” );
}

代码实现

/*
    * 克鲁斯卡尔(Kruskal)最小生成树
    */
public void kruskal2() {
    // 并查集
    int[] parent = new int[size];
    Arrays.fill(parent, -1);

    List<Pair<Integer, Integer>> retList = ListUtil.toList();

    // 最小堆,(start,end) -> weight
    PriorityQueue<Pair<Pair<Integer, Integer>, Integer>> edgePriorityQueue = new PriorityQueue<>(
            Comparator.comparingInt(Pair::getValue));
    for (int i = 0; i < size; i++) {
        for (int j = 0; j < size; j++) {
            if (edges[i][j] != 0 && edges[i][j] != Integer.MAX_VALUE) {
                edgePriorityQueue.offer(new Pair<>(new Pair<>(i, j), edges[i][j]));
            }
        }
    }

    int weightSum = 0;
    while (!edgePriorityQueue.isEmpty()) {
        Pair<Pair<Integer, Integer>, Integer> pair = edgePriorityQueue.poll();
        Pair<Integer, Integer> startEndPair = pair.getKey();
        Integer start = startEndPair.getKey();
        Integer end = startEndPair.getValue();
        Integer weight = pair.getValue();

        // 并查集判断是否存在回路
        int startRoot = find(parent, start);
        int endRoot = find(parent, end);

        if (startRoot != endRoot) {
            // 不构成回路
            retList.add(startEndPair);
            weightSum += weight;
            union(parent, start, end);
        }

    }

    if (retList.size() != size - 1) {
        throw new RuntimeException("生成树不存在");
    }

    Console.log(weightSum, retList);

}

public static int find(int[] parent, int i) {
    if (parent[i] == -1) {
        return i;
    }
    return find(parent, parent[i]);
}

public static void union(int[] parent, int x, int y) {
    int xset = find(parent, x);
    int yset = find(parent, y);
    parent[xset] = yset;
}
posted @ 2023-06-21 16:23  流星<。)#)))≦  阅读(6)  评论(0编辑  收藏  举报