美文网首页
道路修建问题——最小生成树

道路修建问题——最小生成树

作者: 四喜汤圆 | 来源:发表于2018-08-19 19:30 被阅读24次

一、相关概念

  1. 连通图

两个顶点v、w连通是指:v和w之间存在路径(直接或间接)。
连通图:是指无向图,图中任意两节点都连通,则称为连通图。

  1. 连通分量

无向图中的极大连通子图(这些节点都是连通的,并且不能再多了,再多就有不连通的了)称为连通分量
① 任何连通图的连通分量只有一个,即是其自身
② 非连通的无向图有多个连通分量。

  1. 强连通图

两个顶点v、w强连通是指:从v到w、从w到v都有路径。
强连通图:是指有向图,图中任意两节点都强连通,则称为强连通图。

  1. 强连通分量

有向图中的极大强连通子图,称为强连通分量
① 强连通图只有一个强连通分量,即是其自身。
② 非强连通的有向图有多个强连分量。

  1. 连通网

连通图中的边有具有一定的意义,具有权值,则称这样的连通图为连通网。

  1. 生成树

生成树中包含图中的n个节点,n-1条边(再多一条边就会成环)。关键点:1)任意两点间都连通;2)是一棵树

  1. 最小生成树

在连通网的所有生成树中,边代价和最小的生成树,称为最小生成树

二、题目

题目

1.png
2.png

思路

无向图的最小生成树的特点:最小生成树中有n个节点,n-1条边,且这n个节点一定是连通的,各边上代价和最小。所以,这道题目实质上就是要求无向图的最小生成树。本文采用Kruskal算法生成最小生成树,这是一种基于并查集的贪心算法

代码

package 面试.贝壳网;

import java.util.Arrays;
import java.util.Scanner;

/**
 * 
* <p>Description:
* 5
* 4 1 8 2 </p>  
* @author 杨丽金  
* @date 2018-8-19  
* @version 1.0
 */
public class 最小生成树 {
    // 图节点
    class Node{
        // 节点编号(从0开始)
        int num;
        // 节点信息:海拔
        int A;
        
        public Node(int num,int A){
            this.num=num;
            this.A=A;
        }

        @Override
        public String toString() {
            return "Node [num=" + num + ", A=" + A + "]";
        }
        
        
    }
    
    // 图的边
    class Road implements Comparable{
        // 边编号(从0开始)
        int num;
        // 边左节点编号
        int a;
        // 边右节点编号
        int b;
        // 边权值
        int cost;
        
        // 定义默认排序规则
        @Override
        public int compareTo(Object o) {
            if(o instanceof Road){
                Road anotherRoad=(Road)o;
                if(this.cost>anotherRoad.cost){
                    return 1;
                }else if(this.cost<anotherRoad.cost){
                    return -1;
                }else{
                    return 0;
                }
            }
            return 0;
        }
        @Override
        public String toString() {
            return "Road [num=" + num + ", a=" + a + ", b=" + b + ", cost="
                    + cost + "]";
        }
        public Road(int num, int a, int b, int cost) {
            super();
            this.num = num;
            this.a = a;
            this.b = b;
            this.cost = cost;
        }
        
    }
    
    
    // 图
    class MGraph{
        // 节点数
        int n;
        // 边数
        int e;
        // 节点
        Node[] nodes;
        // 边
        Road[] roads;
        // 边及权值
        int[][] edges;
        
        public MGraph(int n){
            this.n=n;
            // 节点编号从0开始
            nodes=new Node[n];
            edges=new int[n][n];
            e=(n*n-n)/2;
            roads=new Road[e];
        }

        @Override
        public String toString() {
            return "MGraph [n=" + n + ", nodes=" + Arrays.toString(nodes)
                    + ", roads=" + Arrays.toString(roads)
                    + ",roads.length="+roads.length;
        }

        
    }
    
    public static void main(String[] args) {
        new 最小生成树().exe();
    }
    
    private void exe(){
        Scanner scan=new Scanner(System.in);
        // N个节点
        int N=scan.nextInt();
        MGraph g=new MGraph(N);
        for(int i=0;i<N;i++){
            g.nodes[i]=new Node(i,scan.nextInt());
        }
        // 初始化边的大小
        /*for(int i=0;i<N;i++){
            for(int j=0;j<N;j++){
                if(i==j){
                    // (0,0)
                    g.edges[i][j]=0;
                }else{
                    // 取决于海拔大的村庄
                    g.edges[i][j]=Math.max(g.nodes[i].A, g.nodes[j].A);
                }
            }
        }*/
        int k=0;
        for(int i=0;i<N;i++){
            for(int j=i+1;j<N;j++){
                // 遍历一半的节点
//              g.roads[k]=new Road(k,i,j,g.edges[i][j]);
                g.roads[k]=new Road(k,i,j,Math.max(g.nodes[i].A, g.nodes[j].A));
                k++;
            }
        }
        // 输出
        System.out.println(g);
        // 图的边大小
        for(int i=0;i<N;i++){
            for(int j=0;j<N;j++){
                System.out.print(g.edges[i][j]+"\t");
            }
            System.out.println();
        }
        int result=kruskal(g);
        System.out.println(result);
    }
    
    /**
     * 基于并查集的贪心算法
     * 利用最小生成树Kruskal算法得到最小生成树,并得到最小代价和
     * @param g
     * @return
     */
    private  int kruskal(MGraph g){
        // 最小生成树的边代价综合
        int sum=0;
        // 将边按照权重从小到大的顺序排列
        Road[] roads=g.roads;
        Arrays.sort(roads);
        // 并查集
        int[] v=new int[g.n];
        // 初始化并查集
        for(int i=0;i<g.n;i++){
            // 每个节点分别处在独立的集合中
            v[i]=i;
        }
        // 贪心算法:使每次选择看起来都是当前最佳选择,期望通过局部最优选择得到全局最优选择
        for(int i=0;i<g.e;i++){
            // 当前这条边的两个端点是否在同一集合
            int a=getRoot(v,g.roads[i].a);
            int b=getRoot(v,g.roads[i].b);
            if(a!=b){
                // 不在同一集合,可以放入最小生成树
                v[a]=b;
                System.out.println("road:"+g.roads[i]);
                sum+=g.roads[i].cost;
            }else{
                continue;
            }
        }
        return sum;
        
    }

    /**
     * 从并查集v中得到节点x的根节点
     * 并查集:就是树的双亲存储结构,
     * 里面存储了一个或多个树(当某节点的父节点是其本身时,它就是它所在树的根节点)
     * @param v
     * @param x
     * @return
     */
    private int getRoot(int[] v, int x) {
        while(x!=v[x]){
            x=v[x];
        }
        return x;
    }
    
}

三、参考文献

算法导论--最小生成树(Kruskal和Prim算法)
最小生成树之Kruskal算法
王道论坛_数据结构

相关文章

网友评论

      本文标题:道路修建问题——最小生成树

      本文链接:https://www.haomeiwen.com/subject/thvciftx.html