kruskal算法求加权无向图的最小生成树

  • kruskal算法概述:按照边的权重(从小到大)处理它
    们,将边加入最小生成树中,加入的边不会与已经加入最小生成树的边构成环,直到树中含有V-1条边为止。

kruskal的核心思想就是默认把图中每个顶点都当做一个树,创建一个队列,保存最小生成树所有的边,同时将该图中所有的边全部存入到一个最小优先队列中,然后依次从队列中弹出最小边,然后判断这个边连接的这两个顶点是否已经处于同一个数中了,如果处于同一个树中了,则跳过本次循环,否则合并这两个顶点到同一个树中,然后将该条边添加至最小生成树的边中。

  • 并查集API
package com.suanfa.uf;

//路径压缩:防止在找某个元素的根节点时变成一种线性查找
//如何解决?设q元素的到根节点的路径是4,p节点到根节点的路径是2,那么要让p节点的根节点等于q节点的根节点
//这样每次合并可以保证将高度低的树合并到高度高的树上,不会增加数的高度。
public class UF_Tree_Weighted {

    private int[] eleAndGroup;

    private int count;//当前并查集中剩余组的个数

    private int[] sc;//记录每个根节点对应的树中保存的每个节点的个数

    public UF_Tree_Weighted(int n){
        this.count = n;
        //初始化一个具有n个元素的并查集
        eleAndGroup = new int[n];
        for (int i = 0; i < eleAndGroup.length; i++) {
            eleAndGroup[i] = i;//初始化默认组
        }
        //默认情况下,每个树中的元素个数都是1
        this.sc = new int[n];
        for (int i = 0; i < sc.length; i++) {
            sc[i] = 1;
        }

    }

    //查找第p个元素所在的组
    public int find(int p){
        //找到p元素的组,继续找p元素的组的组,如果当前元素的组等于当前元素,则表示找打了
        int group = eleAndGroup[p];
        while (group != eleAndGroup[group]){
            group = eleAndGroup[group];
        }
        return group;
    }

    //判断第p个元素和第q个元素是否处于同一个组
    public boolean connected(int p,int q){
        return find(p) == find(q);
    }

    //合并p和q这两个元素
    public boolean union(int p,int q){
        if (connected(p,q)){
            return false;
        }
        //合并两个树,这里注意,要找到其中一个树的根节点,让这个根节点的值等于q节点的根节点
        int pRoot = find(p);
        int qRoot = find(q);

        if (sc[pRoot] < sc[qRoot]){
            //qRoot大,将pRoot合并到qRoot中
            eleAndGroup[pRoot] = qRoot;
            //qRoot的数量等于qRoot数量 + sc[pRoot]
            sc[qRoot] = sc[qRoot] + sc[pRoot];
        }else {
            //pRoot大
            eleAndGroup[qRoot] = pRoot;
            sc[pRoot] = sc[pRoot] + sc[qRoot];
        }

        count--;
        return true;
    }

    public int getGroupCount() {
        return count;
    }

}

  • 最小优先队列API
//最小优先堆
public class MinHeap<T extends Comparable<T>> {

    private int size;

    private T[] elements;

    public MinHeap(int capacity) {
        this.elements = (T[]) new Comparable[capacity];
    }

    public int size(){
        return size;
    }

    //添加一个元素,使得父元素的总是小于等于子元素,对子元素的顺序没有要求
    public void push(T t) {
        elements[++size] = t;
        //使用上浮算法,将该元素的移动到合适位置
        swim(size);
    }

    //删除最小的元素
    public T deleteMin(){
        //交换1与size处的位置
        if (size > 0){
            exchange(1,size);
            T data = elements[size];
            elements[size] = null;
            size--;
            //使用下沉算法,让索引1处的元素移动到指定位置
            sink(1);
            return data;
        }else {
            return null;
        }

    }

    private void sink(int position) {

        //至少有一个左节点
        while (2 * position <= size){
            if ((2*position) + 1 <= size){
                //说明还有右节点
                int minPos = less(2*position,(2*position) + 1) ? 2 * position : (2 * position) + 1;
                //比较父节点与较小的那个节点,如果比较小的还要大,则交换位置
                if (bigger(position,minPos)){
                    exchange(position,minPos);
                    position = minPos;
                }else break;
            }else {
                //只有左节点,比较与左节点的大小,如果比左节点大,则交换位置
                if (bigger(position,position * 2)){
                    exchange(position,position * 2);
                    position = position * 2;
                }else {
                    break;
                }
            }
        }

    }

    private boolean bigger(int i, int j) {
        return elements[i].compareTo(elements[j]) > 0;
    }

    private void swim(int size) {
        //比较size处的元素与 size/2处的元素,如果size比size / 2位置出元素小,则交换位置
        while (size > 1){
            if (less(size,size / 2)){
                exchange(size,size / 2);
                size /= 2;
            }else {
              break;
            }
        }
    }

    private boolean less(int i,int j){
        return elements[i].compareTo(elements[j]) < 0;
    }

    private void exchange(int i, int j) {
        T temp = elements[i];
        elements[i] = elements[j];
        elements[j] = temp;
    }


}

  • 加权边API
//无向图边
public class Edge implements Comparable<Edge>{

    private int v;

    private int w;

    private double weight;//边的权重

    public Edge(int v,int w,double weight){
        this.v = v;
        this.w = w;
        this.weight = weight;
    }

    public double getWeight() {
        return weight;
    }

    public int either(){
        return v;
    }



    public int other(int v){
        if (this.v == v){
            return w;
        }else {
            return this.v;
        }
    }

    @Override
    public int compareTo(Edge o) {
        double result = this.weight - o.weight;
        return result > 0 ? 1 : result == 0 ? 0 : - 1;
    }
}
  • 加权无向图API
//加权无向图
public class EdgedWeightedGraph {

    private int nums;//保存顶点的个数

    private int edgeNums;//保存边的个数

    private List<Edge>[] edges;

    public EdgedWeightedGraph(int num){
        this.nums = num;
        this.edgeNums = 0;
        this.edges = new List[num];
        for (int i = 0; i < edges.length; i++) {
            edges[i] = new ArrayList<>();
        }
    }

    public boolean addEdge(Edge edge){
        int v = edge.either();
        int w = edge.other(v);
        if (edges[v].contains(w)){
            return false;
        }
        edges[v].add(edge);
        edges[w].add(edge);
        edgeNums++;
        return true;
    }

    public List<Edge> getEdges(int v){
        return edges[v];
    }

    //获取所有的边
    public List<Edge> allEdges(){
        List<Edge> allEdges = new ArrayList<>();
        for (int i = 0; i < edges.length; i++) {
            List<Edge> edge = edges[i];
            for (Edge e : edge) {
                int w = e.other(i);
                //在无向图中,所有顶点是不可能重复的,一条边连接了两个顶点,这两个订单必然是一大一小
                // 所以在添加边时,只需判断比other边小,如果true就添加
                if (i < w){
                    allEdges.add(e);
                }
            }
        }
        return allEdges;
    }

}
  • kruskal算法API
public class KruskalMST {

    private List<Edge> treeEdges = new ArrayList<>();//保存最小生成树的所有边

    private MinHeap<Edge> edgeMinHeap;

    private UF_Tree_Weighted ufTree;

    public KruskalMST(EdgedWeightedGraph graph){
        this.ufTree = new UF_Tree_Weighted(graph.getNums());
        edgeMinHeap = new MinHeap<>(graph.getEdgeNums() + 1);
        List<Edge> edges = graph.allEdges();//获取所有的边
        for (Edge edge : edges) {
            edgeMinHeap.push(edge);
        }
        while (edgeMinHeap.size() >0 && treeEdges.size() < graph.getNums() - 1){
            //从最小优先队列中弹出一个边
            Edge edge = edgeMinHeap.deleteMin();
            int v = edge.either();
            int w = edge.other(v);
            //判断这两个顶点是否在同一个数中
            if (!ufTree.connected(v,w)){
                //这两个顶点不在同一个树中
                ufTree.union(v,w);
                treeEdges.add(edge);
            }
        }

    }

    public List<Edge> getEdges() {
        return treeEdges;
    }
}

测试数据

8
16
4 5 0.35
4 7 0.37
5 7 0.28
0 7 0.16
1 5 0.32
0 4 0.38
2 3 0.17
1 7 0.19
0 2 0.26
1 2 0.36
1 3 0.29
2 7 0.34
6 2 0.40
3 6 0.52
6 0 0.58
6 4 0.93

测试代码

public class kruskalTest {

    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new FileReader("min_create_tree_test.txt"));
        Integer nums = Integer.parseInt(br.readLine());
        EdgedWeightedGraph graph = new EdgedWeightedGraph(nums);
        int edges = Integer.parseInt(br.readLine());
        for (int i = 0; i < edges; i++) {
            String s = br.readLine();
            String[] s1 = s.split(" ");
            Edge edge = new Edge(Integer.parseInt(s1[0]), Integer.parseInt(s1[1]), Double.parseDouble(s1[2]));
            graph.addEdge(edge);
        }

        KruskalMST kruskalMST = new KruskalMST(graph);
        for (Edge edge : kruskalMST.getEdges()) {
            System.out.println(edge);
        }

    }

}

最终打印在这里插入图片描述
最小生成树

在这里插入图片描述

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值