Find the minimum spanning tree (Prim algorithm and Kruskal algorithm and union search)


1. Case requirements

Use the greedy algorithm idea to find the minimum spanning tree of the undirected graph (complete the Prim algorithm and the Kruskal algorithm respectively, among which, the Kruskal algorithm requires the use of a check-and-set check loop)

Suppose the given undirected graph is as follows:

insert image description here

2. Algorithm design and implementation

2.1 Prim algorithm
2.1.1 Constructing an undirected graph

A two-dimensional array is used to construct an undirected graph, each path is set to have the same bidirectional weight, and the weight of an unconnected path is set to the largest integer.

2.1.2 Writing the Prim algorithm function

The lowcost array records the shortest distance from the current minimum spanning tree to other points. The initial state is the distance from other vertices to the root node. The mst record records the vertices passed by the edges from the other vertices to the minimum spanning tree. It is initialized to 1 and traverses nodes other than the root node. Find the vertex closest to the binary tree and not in the tree, add it, and judge whether the distance from the new vertex to the binary tree is shorter for the rest of the vertices. If it is shorter, update the lowcost array and the mst array until all vertices are traversed.

2.1.3 Implementation code

Time complexity: O(n 2 ) (n is the number of vertices)

public static int Prim(int[][] graph, int m) {
    
    
    //设置两个数组
    // lowcost:记录当前最小生成树到其余点最短距离
    // mst:记录其余顶点到最小生成树的边所经过顶点,如mst[i]=j表示顶点i到最小生成树的边是i->j
    int[] lowcost = new int[m + 1];
    int[] mst = new int[m + 1];
    //记录最小生成树的和
    int sum = 0;

    //数组初始化
    for (int i = 2; i <= m; i++) {
    
    
        //顶点1到其余顶点距离
        lowcost[i] = graph[1][i];
        //mst初始化为1
        mst[i] = 1;
    }
    //第一个顶点已被遍历
    mst[1] = 0;

    //第二个点开始
    for (int i = 2; i <= m; i++) {
    
    
        int min = Integer.MAX_VALUE;
        int minid = 0;
        for (int j = 2; j <= m; j++) {
    
    
            //找到距离二叉树最近的顶点且未在树中
            if (lowcost[j] < min && mst[j] != 0) {
    
    
                min = lowcost[j];
                //记录顶点位置
                minid = j;
            }
        }
        //打印路径
        System.out.println(mst[minid] + "->" + minid + " " + min);
        sum += min;
        lowcost[minid] = 0;
        mst[minid] = 0;

        for (int j = 2; j <= m; j++) {
    
    
            //判断从新顶点到二叉树的距离是否更短
            if (graph[minid][j] < lowcost[j]) {
    
    
                //更新
                lowcost[j] = graph[minid][j];
                mst[j] = minid;
            }
        }
    }
    return sum;
}
2.1.4 Screenshot of running results

insert image description here

2.2 Kruskal algorithm
2.2.1 Constructing an undirected graph

Construct an Edge class with three attributes: start point, end point, and weight, rewrite the compareTo function, and use the list list to store the edges of the undirected graph.

2.2.2 Write and check the UnionFind class

The union(x,y) function implements the merge function, and sets the parent node of node x to node y; the find(x) function implements the search function, traverses to find the parent node of x until the root node, and returns the root node, in order to reduce time complexity At the same time, record all the parent nodes of the search process, and set the parent node of all nodes as the root node, so that it will take too much time to traverse the search in the future.

2.2.3 Writing the Kruskal algorithm

First sort the edges by their weights, from small to large, and use the find(x) function to judge whether the root nodes of the two points on the edge are the same (judging whether to form a closed loop after joining), if they are different, join the edge set of the minimum spanning tree , and at the same time merge the two vertices of the edge through union (x, y), and finally until the edge number of the minimum spanning tree edge set == the number of vertices -1.

2.2.4 Implementation code

Time complexity: O(nlog 2 n)

And check the code:

import java.util.HashSet;
import java.util.Set;

//并查集
public class UnionFind {
    
    
    //实现查功能
    public static UFNode find(UFNode x) {
    
    
        UFNode p = x;
        Set<UFNode> path = new HashSet<>();
        //记录向上追溯的路径上的点
        while (p.parent != null) {
    
    
            path.add(p);
            p = p.parent;
        }
        //这些点的parent全部指向这个集的代表(他们共同的老大)
        //优化:第一次未生效,后续生效,后续只需找一次就能找到当前节点的根节点
        for (UFNode ppp : path) {
    
    
            ppp.parent = p;
        }
        return p;

    }

    //实现并功能
    public static void union(UFNode x, UFNode y) {
    
    
        //将x作为y的父结点
        find(y).parent = find(x);
    }

    //定义静态内部类,这是并查集中的结点
    public static class UFNode {
    
    
        UFNode parent;//父结点
    }

}

Side class code:

public class Edge<T> implements Comparable<Edge> {
    
    
    private T start;
    private T end;
    private int distance;

    public Edge(T start, T end, int distance) {
    
    
        this.start = start;
        this.end = end;
        this.distance = distance;
    }

    public T getStart() {
    
    
        return start;
    }

    public void setStart(T start) {
    
    
        this.start = start;
    }

    public T getEnd() {
    
    
        return end;
    }

    public void setEnd(T end) {
    
    
        this.end = end;
    }

    public int getDistance() {
    
    
        return distance;
    }

    public void setDistance(int distance) {
    
    
        this.distance = distance;
    }

    @Override
    public String toString() {
    
    
        return start + "->" + end + " " + distance;
    }

    @Override
    public int compareTo(Edge o) {
    
    
        return distance > o.getDistance() ? 1 : (distance == o.getDistance() ? 0 : -1);
    }
}

Kruskal algorithm code:

public class Kruskal {
    
    
    private final List<Edge> edgeList;//存放图中的所有边
    private final int n;//总顶点数

    private Set<Edge> T = new HashSet<>();//存放生成树的边
    private Map pntAndNode = new HashMap();//边上的每个顶点都和并查集中有与之对应的node

    private Set<Edge> getT() {
    
    
        buildMST();
        return T;
    }

    public Kruskal(List<Edge> edgeList, int n) {
    
    
        this.edgeList = edgeList;
        //为每个顶点建立一个并查集的点
        for (Edge edge : edgeList) {
    
    
            pntAndNode.put(edge.getStart(), new UnionFind.UFNode());
            pntAndNode.put(edge.getEnd(), new UnionFind.UFNode());
        }
        this.n = n;
    }

    //构造一个边表
    private static List<Edge> build() {
    
    
        List<Edge> li = new ArrayList<>();
        li.add(new Edge("1", "2", 2));
        li.add(new Edge("1", "6", 8));
        li.add(new Edge("1", "4", 5));
        li.add(new Edge("2", "3", 7));
        li.add(new Edge("2", "4", 7));
        li.add(new Edge("2", "5", 2));
        li.add(new Edge("3", "5", 3));
        li.add(new Edge("4", "5", 6));
        li.add(new Edge("4", "6", 7));
        li.add(new Edge("4", "7", 3));
        li.add(new Edge("5", "7", 4));
        li.add(new Edge("6", "7", 4));

        return li;
    }

    private void buildMST() {
    
    
        //先对边集进行排序
        Collections.sort(edgeList);
        for (Edge e : edgeList) {
    
    
            //寻找每条边上两个结点在map集合中映射的UFNode
            UnionFind.UFNode x = (UnionFind.UFNode) pntAndNode.get(e.getStart());
            UnionFind.UFNode y = (UnionFind.UFNode) pntAndNode.get(e.getEnd());
            if (UnionFind.find(x) == UnionFind.find(y))
                continue;//如果两个结点来自同一顶点集,则跳过这条边,否则会形成回路
            UnionFind.union(x, y);
            //把边加入到T中
            T.add(e);
            if (T.size() == n - 1)
                return;//生成树的边数==总顶点数-1,表示所有的点已经连接
        }
    }

    public static void main(String[] args) {
    
    
        List<Edge> edgeList = build();
        Kruskal obj = new Kruskal(edgeList, 7);
        //getT中就调用了buildMST方法,将生成的边放到了集合中
        for (Edge e : obj.getT())
            System.out.println(e);
    }

}
2.2.5 Screenshot of running results

insert image description here

3. Summary

The basic idea of ​​the greedy algorithm: the optimal situation is always selected each time. This time, the Prim algorithm always selects the edge closest to the minimum spanning tree each time. The Kruskal algorithm always selects the shortest edge after sorting the edge set.

And reviewed the relevant knowledge of union search, among which the two key functions union and find implement the merge function and search function respectively. During search, time complexity can also be reduced by related optimization algorithms, such as parent node update method and weighted mark method, in this case the parent node update method is used.

Guess you like

Origin blog.csdn.net/weixin_49588575/article/details/130740714
Recommended