图的最小生成树 - Java实现

图片来自:勿在浮沙筑高台http://blog.csdn.net/luoshixian099/article/details/51908175

一、前言

在了解图的最小生成树之前,先弄清几种图的概念:

  • 连通图:在无向图中,若任意两个顶点vivi与vjvj都有路径相通,则称该无向图为连通图。
  • 强连通图:在有向图中,若任意两个顶点vivi与vjvj都有路径相通,则称该有向图为强连通图。
  • 连通网:在连通图中,若图的边具有一定的意义,每一条边都对应着一个数,称为权;权代表着连接连个顶点的代价,称这种连通图叫做连通网。
  • 生成树:一个连通图的生成树是指一个连通子图,它含有图中全部n个顶点,但只有足以构成一棵树的n-1条边。一颗有n个顶点的生成树有且仅有n-1条边,如果生成树中再添加一条边,则必定成环。
  • 最小生成树:在连通网的所有生成树中,所有边的代价和最小的生成树,称为最小生成树。
    在这里插入图片描述

二、两种最小生成树算法

1.Prim算法

此算法可以称为“加点法”,每次迭代选择代价最小的边对应的点,加入到最小生成树中。算法从某一个顶点s开始,逐渐长大覆盖整个连通网的所有顶点。

步骤:

  1. 图的所有顶点集合为V;初始令集合u={s},v=V−u
  2. 在两个集合u,v能够组成的边中,选择一条代价最小的边(u0,v0),加入到最小生成树中,并把v0并入到集合u中
  3. 重复上述步骤,直到最小生成树有n-1条边或者n个顶点为止

需要将代价最小的边对应的点加入到最小生成树(集合u)中,所以需要一个辅助数组dis[],用来维护每个顶点到最小生成树(集合u)最小代价边信息
在这里插入图片描述
代码实现

private void prim_(EdgeWeightGraph G, int v) {
        int count = 0;
        int min,minVertex = 0;
        // 标记起点,并加入集合u(mst)
        marked[v] = true;
        mst.add(v);
        ++count;
        while (count < G.getV()) {
            // 查找与生成树不连接,且边权最小的顶点
            min = Integer.MAX_VALUE;
            for (int i = 1; i < dis.length; i++) {
                if (!marked[i] && dis[i] < min) {
                    min = dis[i];
                    minVertex = i;
                }
            }
            marked[minVertex] = true;
            mst.add(minVertex);
            sum += dis[minVertex];
            ++count;

            // 加入新的顶点,更新dis数组
            Vertex vertex = G.getVertex(minVertex);
            for (int i = 0; i < vertex.size(); i++) {
                Edge edge = vertex.get(i);              // 相邻的边
                int nextVertex = edge.getVertexIndex(); // 相邻的顶点
                int nextWeight = edge.getWeight();      // 与相邻顶点之间的边权
                if (!marked[nextVertex] && nextWeight < dis[nextVertex])
                    dis[nextVertex] = nextWeight;
            }
        }
    }

时间复杂度:O(e*v)

使用优先队列优化Prim算法

步骤:

  1. 访问起点s,将与之相连的边加入到优先队列
  2. 每次取到代价最小的边,如果边的两个顶点都被访问,那么边失效;否则,访问为标记的顶点,并加入的最小生成树(集合u)
  3. 重复步骤2,直到队列为空
private void prim_(EdgeWeightGraph G, int v) {
        PriorityQueue<Edge> queue = new PriorityQueue<>(new Comparator<Edge>() {
            @Override
            public int compare(Edge o1, Edge o2) {
                return o1.getWeight() - o2.getWeight();
            }
        });

        visit(G, v, queue);
        while (!queue.isEmpty()) {
            // 取得边权最小的判断顶点是否访问过
            Edge edge = queue.remove();
            int v1 = edge.getpVertex();
            int v2 = edge.getVertexIndex();
            if (marked[v1] && marked[v2])   // 边失效
                continue;
            sum += edge.getWeight();
            mst.add(edge);
            if (!marked[v1])
                visit(G, v1, queue);
            if (!marked[v2])
                visit(G, v2, queue);
        }
    }
    // 将与顶点v关联(且没有访问过)的边添加到优先队列中
    private void visit(EdgeWeightGraph G, int v, PriorityQueue quque) {
        // 标记顶点v并将所有连接v和为标记顶点的边添加队列
        marked[v] = true;
        Vertex vertex = G.getVertex(v);
        for (int i = 0; i < vertex.size(); i++) {
            int nextVertex = vertex.get(i).getVertexIndex();
            if (!marked[nextVertex])
                quque.add(vertex.get(i));
        }
    }

使用了优先队列和邻接表优化之后时间复杂度:O(e*lge)

2.Kruskal算法

此算法可以称为“加边法”,初始最小生成树边数为0,每迭代一次就选择一条满足条件的最小代价边,加入到最小生成树的边集合里。

步骤:

  1. 把图中的所有边按代价从小到大排序
  2. 把图中的n个顶点看成独立的n棵树组成的森林
  3. 按权值从小到大选择边,所选的边连接的两个顶点ui,vi,应属于两颗不同的树,则成为最小生成树的一条边,并将这两颗树合并作为一颗树
  4. 重复(3),直到所有顶点都在一颗树内或者有n-1条边为止

主要问题是如何判断这个代价最小的边的两个顶点是否相通,这里我们使用并查集解决问题
点击了解并查集
在这里插入图片描述
代码如下:

扫描二维码关注公众号,回复: 10595360 查看本文章
private void Kruskal_(EdgeWeightGraph G, PriorityQueue<Edge> queue) {
        int v = G.getV();
        int count = 0;
        while (!queue.isEmpty()) {
            Edge edge = queue.remove();
            int v1 = edge.getpVertex();
            int v2 = edge.getVertexIndex();
            // 判断两个顶点是否连通
            if (merge(v1, v2)) {
                // 如果为连通,那么就选这条边
                ++count;
                mst.add(edge);
                sum += edge.getWeight();
            }
            // 选择了n-1条边退出
            if (count == v - 1)
                break;
        }
    }

    // 判断两个顶点是否为在同一集合,如果不在那么合并两个顶点
    private boolean merge(int v, int w) {
        int t1 = getF(v);
        int t2 = getF(w);
        if (t1 != t2) {
            // 如果没在同一集合,约定t2的父顶点为t1
            f[t2] = t1;
            return true;
        }
        return false;
    }

    // 并查集寻找顶点的父顶点
    private int getF(int v) {
        if (f[v] == v) {
            return v;
        } else {
            // 更新父顶点 - 路径压缩
            f[v] = getF(f[v]);
            return f[v];
        }
    }
发布了120 篇原创文章 · 获赞 16 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_43327091/article/details/103651965