最小生成树

来源:互联网 发布:网络语音监控 编辑:程序博客网 时间:2024/06/16 18:43

最小生成树

在含有n个顶点的连通加权无向图中选择n-1条边,构成一棵极小连通子图,并使该连通子图中n-1条边上权值之和达到最小,则称其为连通网的最小生成树。

普里姆(Prim)算法

算法流程

从单一顶点开始,普里姆算法按照以下步骤逐步扩大树中所含顶点的数目,直到遍及连通图的所有顶点。

  • 输入:一个加权连通图,其中顶点集合为V,边集合为E
  • 初始化:Vnew={x},其中x为集合V中的任一节点(起始点),Enew={}
  • 重复下列操作,直到Vnew = V
    • 在集合E中选取权值最小的边(u, v),其中u为集合Vnew中的元素,而v则是V中没有加入Vnew的顶点(如果存在有多条满足前述条件即具有相同权值的边,则可任意选取其中之一);
    • 将v加入集合Vnew中,将(u, v)加入集合Enew中;
  • 输出:使用集合VnewEnew来描述所得到的最小生成树。

具体流程如下图所示:
enter image description here

算法实现(Java)

/** * @description 最小生成树之普里姆(Prim)算法 *              输入 --> 无向有权图和起始顶点下标 *              输出 --> 打印最小生成树边及其对应的权重 * @author GongchuangSu * @since 2016.04.13 * @version v1.0 */public class Prim {    Edge[] result; // 最小生成树数组    WeightedGraph G;    public void prim(WeightedGraph G, int s) {        result = new Edge[G.size() - 1];        this.G = G;        final int[] dist = new int[G.size()];            // 相关顶点间边的权值        final int[] pred = new int[G.size()];            // prim最小生成树结果数组        final boolean[] visited = new boolean[G.size()]; // 顶点访问标记数组        // 初始化        for (int i = 0; i < dist.length; i++) {            dist[i] = Integer.MAX_VALUE;        }        dist[s] = 0; // 将起始点对应的最小权值设置为0        for (int i = 0; i < dist.length; i++) {            final int next = minVertex(dist, visited); // 在dist中寻找未被访问点所对应的最小权值,并返回该顶点坐标            visited[next] = true;                      // 将该顶点设置为已访问            final int[] n = G.getNighbors(next);       // 取得与该顶点相邻接的点            for (int j = 0; j < n.length; j++) {                final int v = n[j];                final int d = G.getWeight(next, v);                if (dist[v] > d && !visited[v]) {                    dist[v] = d;    // 将更小权值保存至dist数组中                    pred[v] = next; // 并将先前结点保存至pred数组中                }            }        }        // 将结果保存至最小生成树数组        for (int i = 0; i < G.size() - 1; i++) {            result[i] = new Edge(pred[i + 1], i + 1, G.getWeight(pred[i + 1], i + 1));        }        // 打印结果        print();    }    /**     * 功能:在未遍历点中寻找距离最小的点,并返回该点位置     */    private static int minVertex(int[] dist, boolean[] v) {        int x = Integer.MAX_VALUE;        int y = -1;        for (int i = 0; i < dist.length; i++) {            if (!v[i] && dist[i] < x) {                y = i;                x = dist[i];            }        }        return y;    }    /**     * 功能:边集结构体     */    private static class Edge {        int source;        int target;        int weight;        public Edge(int source, int target, int weight) {            this.source = source;            this.target = target;            this.weight = weight;        }    }    /**     * 功能:打印结果     */    private void print() {        System.out.print("Prim:\n");        for (int i = 0; i < G.size() - 1; i++)            System.out.printf("(%d, %d) %d \n", result[i].source, result[i].target, result[i].weight);    }}

源代码下载:普里姆(Prim)算法

时间复杂度

由算法代码中的循环嵌套可得知其算法时间复杂度为O(n2)

克鲁斯卡尔(Kruskal)

算法流程

  • 新建图GG中拥有原图中相同的节点,但没有边
  • 将原图中所有的边按权值从小到大排序
  • 从权值最小的边开始,如果这条边连接的两个节点于图G中不在同一个连通分量中,则添加这条边到图G中(不构成环)
  • 重复3,直至图G中所有的节点都在同一个连通分量中

具体流程如下图所示:
enter image description here

为什么这条边一定属于最小树?

反证法:如果这条边不在最小生成树中,它连接的两个连通分量最终还是要连起来的,通过其他的连法,那么另一种连法与这条边一定构成了环,而环中一定有一条权值大于这条边的边,用这条边将其替换掉,图仍旧保持连通,但总权值减小了。也就是说,如果不选取这条边,最后构成的生成树的总权值一定不会是最小的。

算法实现(Java)

/** * @description 最小生成树之克鲁斯卡尔(Kruskal)算法 *              输入 --> 无向有权图 *              输出 --> 打印最小生成树边及其对应的权重 * @author GongchuangSu * @since 2016.04.13 * @version v1.0 */public class Kruskal {    int vlen;      // 顶点个数    int elen;      // 边个数    Edge[] edges;  // 边集数组    Edge[] result; // 最小生成树数组    int[] parent;  // 用来判断边与边是否形成环路    WeightedGraph G;    public void kruskal(WeightedGraph G){        vlen = G.size();                   elen = G.getEdgNum();              edges = new Edge[elen];        result = new Edge[vlen-1];        parent = new int[vlen];        this.G = G;        edges = getEdges();  // 获取图中所有的边        sortEdges(edges);    // 对边进行排序        // 初始化数组值为0        for(int i = 0; i < vlen; i++){            parent[i] = 0;        }        // 循环每一条边        int index = 0;        for(int i = 0; i < elen; i++){                           int n = getEnd(parent, edges[i].source);            int m = getEnd(parent, edges[i].target);            // 如果n与m不相等,说明此边没有与现有生成树形成环路            if(n != m){                 parent[n] = m;                result[index++] = new Edge(edges[i].source, edges[i].target, edges[i].weight);            }        }        // 打印结果        print();    }    /**     * 功能:边集结构体     */    private static class Edge{        int source;        int target;        int weight;        public Edge(int source, int target, int weight){            this.source = source;            this.target = target;            this.weight = weight;        }    }    /**     * 功能:获取图中所有的边     */    private Edge[] getEdges(){        int index = 0;        Edge[] edges = new Edge[elen];        for(int i = 0; i < vlen; i++){            for(int j = i + 1; j < vlen; j++)                if(G.getWeight(i, j) != 0)                    edges[index++] = new Edge(i, j, G.getWeight(i, j));        }        return edges;    }    /**     * 功能:将边按照权值大小由小到大进行排序     */    private void sortEdges(Edge[] edges){        for(int i = 0; i < elen; i++)            for(int j = i + 1; j < elen; j++)                if(edges[i].weight > edges[j].weight){                    Edge temp = edges[i];                    edges[i] = edges[j];                    edges[j] = temp;                }    }    /**     * 功能:查找顶点在该最小树中的终点下标     */    private int getEnd(int[] parent, int i) {        while( parent[i] > 0 )            i = parent[i];        return i;    }    /**     * 功能:打印结果     */    private void print() {        System.out.print("Kruskal:\n");        for(int i = 0; i < vlen - 1; i++)            System.out.printf("(%d, %d) %d \n",result[i].source, result[i].target, result[i].weight);    }}

源代码下载:克鲁斯卡尔(Kruskal)算法

时间复杂度

由算法代码中的循环嵌套可得知其算法时间复杂度为O(Elog2E),其中E为图中的边数。

0 0
原创粉丝点击