Figure -- 05 -- greedy algorithm, Prim algorithm, kruskal algorithm

Tip: after the article is written, the directory can be generated automatically. Please refer to the help document on the right for how to generate it

Greedy Algorithm

Greedy algorithm - Definition:

Use the segmentation theorem to find one edge of the minimum spanning tree and repeat until all edges of the minimum spanning tree are found

  • Greedy algorithm is the basic algorithm for calculating the minimum spanning tree of a graph. Its basic principle is the segmentation theorem,
  • Use the segmentation theorem to find one edge of the minimum spanning tree and repeat until all edges of the minimum spanning tree are found.
  • If a graph has V vertices, it needs to find V-1 edges to represent the minimum spanning tree of the graph.

Greedy algorithm - principle:



Algorithm of minimum spanning tree

  • There are many algorithms for calculating the minimum spanning tree of a graph, but these algorithms can be regarded as a special case of greedy algorithms,
  • The difference between these algorithms is the way to save the segmentation and determine the cross cutting edge with the smallest weight.

Prim algorithm

  • We learn the first method to calculate the minimum spanning tree, called Prim algorithm, which adds an edge to a spanning tree at each step. At first, the tree has only one vertex, and then V-1 edges will be added to it. Each time, the next edge with the lowest weight connecting the vertex in the tree and the vertex not in the tree will be added to the tree.

Segmentation rules:

  • The vertices in the minimum spanning tree are regarded as a set, and the vertices not in the minimum spanning tree are regarded as another set.

Algorithm API design

Implementation principle of Prim algorithm

Prim algorithm always divides the vertices in the graph into two sets, the minimum spanning tree vertex and the non minimum spanning tree vertex. By constantly repeating some operations, you can gradually add the vertices in the non minimum spanning tree to the minimum spanning tree until all the vertices are added to the minimum spanning tree

  • When designing the API, we use the minimum index priority queue to store the effective crosscutting edges of vertices in the tree and non vertices in the tree. How is it represented? We can let the index value of the minimum index priority queue represent the vertices of the graph, and let the value in the minimum index priority queue represent the edge weight from another vertex to the current vertex.

1. Initialization status

  • In the initialization state, 0 is the only vertex in the minimum spanning tree by default, and other vertices are not in the minimum spanning tree. At this time, the crosscutting edge is the four edges 0-2,0-4,0-6,0-7 in the adjacency table of vertex 0. We only need to store the weight values of these edges at the indexes 2, 4, 6 and 7 of the index priority queue.

2. Find the edge with the smallest weight and add it to the tree

Now you just need to find the edge with the least weight from the four crosscutting edges, and then add the corresponding vertices. Therefore, the weight of the crosscutting edge 0-7 is the smallest, so the edge 0-7 is added. At this time, 0 and 7 belong to the vertices of the minimum spanning tree, and the others do not. Now the edges in the adjacent table of vertex 7 also become crosscutting edges,

Two operations are required:

  • The 0-7 edge is no longer a crosscutting edge and needs to be invalidated: it can be completed by calling the delMin() method of the minimum index priority queue;
  • 2 and 4 vertices each have two connections pointing to the minimum spanning tree, and only one needs to be reserved:
    The weight of 4-7 is less than that of 0-4, so keep 4-7 and call change(4,0.37) of the index priority queue,
    The weight of 0-2 is less than that of 2-7, so keep 0-2 and no additional operation is required.

3. Repeat the above action,

  • By repeating the above actions, we can add all vertices to the minimum spanning tree.

Auxiliary class

1.Edge - edge

package graph.tu;

public class Edge implements Comparable<Edge> {
    private final int v;//Vertex one
    private final int w;//Vertex two
    private final double weight;//The weight of the current edge

    //Construct an edge object from vertices v and w and weight values
    public Edge(int v, int w, double weight) {
        this.v = v;
        this.w = w;
        this.weight = weight;
    }

    //Gets the weight value of the edge
    public double weight(){
        return weight;
    }

    //Gets a point on the edge
    public int either(){
        return v;
    }

    //Gets another vertex on the edge except vertex vertex
    public int other(int vertex){
        if (vertex==v){
            return w;
        }else{
            return v;
        }
    }

    @Override
    public int compareTo(Edge that) {
        //Use a traversal record to compare the results
        int cmp;

        if (this.weight()>that.weight()){
            //If the weight value of the current edge is large, let cmp=1;
            cmp = 1;
        }else if (this.weight()<that.weight()){
            //If the weight value of the current edge is small, let cmp=-1;
            cmp=-1;
        }else{
            //If the weight value of the current edge is as large as that edge, let cmp=0
            cmp = 0;
        }

        return cmp;
    }
}

2.EdgeWeightedGraph - weighted undirected graph

package graph.tu;


import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

public class EdgeWeightedGraph {
    //Total number of vertices
    private final int V;
    //Total number of edges
    private int E;
    //Adjacency table
    private Queue<Edge>[] adj;

    //Create a null weighted undirected graph with V vertices
    public EdgeWeightedGraph(int v) {
        //Number of initialization vertices
        this.V = v;
        //Number of initialization edges
        this.E = 0;
        //Initialize adjacency table
        this.adj = new ConcurrentLinkedQueue[v];
        for (int i = 0; i < adj.length; i++) {
            adj[i] = new ConcurrentLinkedQueue<Edge>();
        }

    }

    //Gets the number of vertices in the graph
    public int V() {
        return V;
    }

    //Gets the number of edges in the graph
    public int E() {
        return E;
    }


    //Add an edge to the weighted undirected graph e
    public void addEdge(Edge e) {
        //You need to make edge e appear in the adjacency table of two vertices of edge e at the same time
        int v = e.either();
        int w = e.other(v);

        adj[v].offer(e);
        adj[w].offer(e);

        //Number of sides + 1
        E++;
    }

    //Gets all edges associated with the vertex v
    public Queue<Edge> adj(int v) {
        return adj[v];
    }

    //Gets all edges of a weighted undirected graph
    public Queue<Edge> edges() {

        //Create a queue object to store all the edges
        Queue<Edge> allEdges = new ConcurrentLinkedQueue<>();

        //Traverse each vertex in the graph and find the adjacency table of the vertex, which stores each edge associated with the vertex

        //Because this is an undirected graph, the same edge appears in the adjacency table of its associated two vertices at the same time. It is necessary to record an edge only once;
        for(int v =0;v<V;v++){
            //Traverse the adjacency table of v vertices and find each edge associated with v
            for (Edge e : adj(v)) {

                if (e.other(v)<v){
                    allEdges.offer(e);
                }

            }
        }


        return allEdges;
    }
}


3.IndexMinPriorityQueue - minimum priority queue

package graph.tu;

public class IndexMinPriorityQueue<T extends Comparable<T>> {
    //Elements in the storage heap
    private T[] items;
    //Save the index of each element in the items array. The pq array needs to be heap ordered
    private int[] pq;
    //Save the reverse order of qp, the value of pq as the index and the index of pq as the value
    private int[] qp;
    //Record the number of elements in the heap
    private int N;


    public IndexMinPriorityQueue(int capacity) {
        this.items = (T[]) new Comparable[capacity+1];
        this.pq = new int[capacity+1];
        this.qp= new int[capacity+1];
        this.N = 0;

        //By default, no data is stored in the queue, so that all elements in qp are - 1;
        for (int i = 0; i < qp.length; i++) {
            qp[i]=-1;
        }

    }

    //Gets the number of elements in the queue
    public int size() {
        return N;
    }

    //Determine whether the queue is empty
    public boolean isEmpty() {
        return N==0;
    }

    //Judge whether the element at index i in the heap is smaller than the element at index j
    private boolean less(int i, int j) {

        return items[pq[i]].compareTo(items[pq[j]])<0;
    }

    //Swap the values at the i and j indexes in the heap
    private void exch(int i, int j) {
        //Exchange data in pq
        int tmp = pq[i];
        pq[i] = pq[j];
        pq[j] = tmp;


        //Update data in qp
        qp[pq[i]]=i;
        qp[pq[j]] =j;

    }

    //Judge whether the element corresponding to k exists
    public boolean contains(int k) {

        return qp[k] !=-1;
    }

    //Index associated with the smallest element
    public int minIndex() {

        return pq[1];
    }


    //Insert an element into the queue and associate the index i
    public void insert(int i, T t) {
        //Judge whether i has been associated. If it has been associated, it will not be inserted

        if (contains(i)){
            return;
        }
        //Number of elements + 1
        N++;
        //Store the data in the i location corresponding to the items
        items[i] = t;
        //Store i in pq
        pq[N] = i;
        //i in pq is recorded by qp
        qp[i]=N;

        //Adjust the heap by floating it up

        swim(N);

    }

    //Deletes the smallest element in the queue and returns the index associated with that element
    public int delMin() {
        //Gets the index associated with the smallest element
        int minIndex = pq[1];

        //Swap elements at index 1 and maximum index in pq
        exch(1,N);
        //Delete the corresponding content in qp
        qp[pq[N]] = -1;
        //Delete content at pq maximum index
        pq[N]=-1;
        //Delete the corresponding content in items
        items[minIndex] = null;
        //Number of elements - 1
        N--;
        //Subsidence adjustment
        sink(1);

        return minIndex;
    }

    //Delete index i associated elements
    public void delete(int i) {
        //Find the index of i in pq
        int k = qp[i];

        //Swap the value at index k and the value at index N in pq
        exch(k,N);
        //Delete content in qp
        qp[pq[N]] = -1;
        //Delete content in pq
        pq[N]=-1;
        //Delete content in items
        items[k]=null;
        //Number of elements - 1
        N--;
        //Heap adjustment
        sink(k);
        swim(k);
    }

    //Change the element associated with index i to t
    public void changeItem(int i, T t) {
        //Modify the element at i position in the items array to t
        items[i] = t;
        //Find where i appears in pq
        int k = qp[i];
        //Heap adjustment
        sink(k);
        swim(k);
    }


    //Using the floating-up algorithm, the element at index k can be in a correct position in the heap
    private void swim(int k) {
        while(k>1){
            if (less(k,k/2)){
                exch(k,k/2);
            }

            k = k/2;
        }
    }


    //Using the sinking algorithm, the element at index k can be in a correct position in the heap
    private void sink(int k) {
        while(2*k<=N){
            //Find the smaller value in the child node
            int min;
            if (2*k+1<=N){
                if (less(2*k,2*k+1)){
                    min = 2*k;
                }else{
                    min = 2*k+1;
                }
            }else{
                min = 2*k;
            }
            //Compare the current node with the smaller value
            if (less(k,min)){
                break;
            }

            exch(k,min);
            k = min;
        }
    }

}


Prim algorithm - PrimMST

package graph.tu;

import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

public class PrimMST {
    //The index represents the vertex, and the value represents the shortest edge between the current vertex and the minimum spanning tree
    private Edge[] edgeTo;
    //The index represents the vertex, and the value represents the weight of the shortest edge between the current vertex and the minimum spanning tree
    private double[] distTo;
    //The index represents the vertex. If the current vertex is already in the tree, the value is true; otherwise, it is false
    private boolean[] marked;
    //Stores valid crosscutting edges between vertices in the tree and non vertices in the tree
    private IndexMinPriorityQueue<Double> pq;

    //According to a weighted undirected graph, the minimum spanning tree calculation object is created
    public PrimMST(EdgeWeightedGraph G) {
        //Initialize edgeTo
        this.edgeTo = new Edge[G.V()];
        //Initialize distTo
        this.distTo = new double[G.V()];
        for (int i = 0; i < distTo.length; i++) {
            distTo[i] = Double.POSITIVE_INFINITY;
        }
        //Initialize marked
        this.marked = new boolean[G.V()];
        //Initialize pq
        pq = new IndexMinPriorityQueue<Double>(G.V());

        //By default, vertex 0 is allowed to enter the tree, but there is only one vertex 0 in the tree. Therefore, vertex 0 is not connected to other vertices by default, so let the value at the corresponding position of distTo store 0.0
        distTo[0] = 0.0;
        pq.insert(0,0.0);

        //Traverse the index minimum priority queue, get the vertex corresponding to the minimum and N cutting edges, and add the vertex to the minimum spanning tree
        while (!pq.isEmpty()){
            visit(G,pq.delMin());
        }


    }


    //Add vertex v to the minimum spanning tree and update the data
    private void visit(EdgeWeightedGraph G, int v) {
        //Add vertex v to the minimum spanning tree
        marked[v] = true;
        //Update data
        for (Edge e : G.adj(v)) {
            //Get another vertex of edge e (current vertex is v)
            int w = e.other(v);
            //Judge whether another vertex is already in the tree. If it is in the tree, no processing will be done. If it is no longer in the tree, the data will be updated
            if (marked[w]){
                continue;
            }


            //Judge whether the weight of edge e is less than the weight from w vertex to the smallest edge already existing in the tree;
            if (e.weight()<distTo[w]){
                //Update data
                edgeTo[w] = e;

                distTo[w] = e.weight();

                if (pq.contains(w)){
                    pq.changeItem(w,e.weight());
                }else{
                    pq.insert(w,e.weight());
                }

            }


        }

    }

    //Gets all edges of the minimum spanning tree
    public Queue<Edge> edges() {
        //Create queue object
        Queue<Edge> allEdges = new ConcurrentLinkedQueue<>();
        //Traverse the edgeTo array to get each edge. If it is not null, it will be added to the queue
        for (int i = 0; i < edgeTo.length; i++) {
            if (edgeTo[i]!=null){
                allEdges.offer(edgeTo[i]);
            }
        }
        return allEdges;
    }
}

Test class


package graph.tu;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Queue;

public class PrimMSTTest {
    public static void main(String[] args) throws Exception{


        //Prepare a weighted undirected graph
        BufferedReader br = new BufferedReader(new InputStreamReader(PrimMSTTest.class.getClassLoader().getResourceAsStream("main/resources/min_create_tree_test.txt")));
        int total = Integer.parseInt(br.readLine());
        graph.tu.EdgeWeightedGraph G = new graph.tu.EdgeWeightedGraph(total);

        int edgeNumbers = Integer.parseInt(br.readLine());
        for (int e = 1;e<=edgeNumbers;e++){
            String line = br.readLine();//4 5 0.35

            String[] strs = line.split(" ");

            int v = Integer.parseInt(strs[0]);
            int w = Integer.parseInt(strs[1]);

            double weight = Double.parseDouble(strs[2]);

            //Building weighted undirected edges
            Edge edge = new Edge(v, w, weight);
            G.addEdge(edge);

        }

        //Create a PrimMST object and calculate the minimum spanning tree in the weighted undirected graph
        graph.tu.PrimMST primMST = new graph.tu.PrimMST(G);


        //Gets all edges in the minimum spanning tree
        Queue<Edge> edges = primMST.edges();

        //Traverse and print all edges
        for (Edge e : edges) {
            int v = e.either();
            int w = e.other(v);
            double weight = e.weight();
            System.out.println(v+"-"+w+" :: "+weight);

        }


    }
}

kruskal Algorithm

Main ideas:

The edges are processed according to their weights (from small to large), and the edges are added to the minimum spanning tree

  • kruskal algorithm is another algorithm to calculate the minimum spanning tree of a weighted undirected graph. Its main idea is to deal with them according to the weight of edges (from small to large). The edges are added to the minimum spanning tree. The added edges will not form a ring with the edges that have been added to the minimum spanning tree until the tree contains V-1 edges.

Differences between kruskal algorithm and prim algorithm:

  • Prim algorithm constructs the minimum spanning tree one by one, and adds an edge to a tree at each step.
  • kruskal algorithm constructs the minimum spanning tree side by side, but its segmentation rules are different. Every time it looks for the edge, it will connect two trees in a forest. If a weighted undirected graph is composed of V vertices, and each vertex forms an independent tree under initialization, then the V vertices correspond to V trees to form a forest. kruskal algorithm will merge the two trees into one tree every time until there is only one tree left in the whole forest.

API design

Implementation principle of kruskal algorithm

  • When designing the API, a MinPriorityQueue pq is used to store all edges in the graph. Each time pq.delMin() is used to take out the edge with the smallest weight and obtain the two vertices V and W associated with the edge. uf.connect(v,w) is used to judge whether V and W are connected. If connected, it is proved that the two vertices are in the same tree, so this edge can no longer be added to the minimum spanning tree, Because adding an edge to any two vertices of a tree will form a ring, and the minimum spanning tree cannot have a ring. If it is not connected, the tree where vertex v is located and the tree where vertex W is located will be merged into a tree through uf.connect(v,w), and the edge will be added to the mst queue. In this way, if all edges are processed, Finally, all the edges of the minimum spanning tree are stored in mst.


Auxiliary class

1.Edge - edge

2.EdgeWeightedGraph - weighted undirected graph

3.UF_Tree_Weighted -- union search set

package graph.tu;

public class UF_Tree_Weighted {
    //Record the node element and the identification of the group in which the element is located
    private int[] eleAndGroup;
    //Record and check the number of data groups in the set
    private int count;


    //It is used to store the number of nodes saved in the tree corresponding to each root node
    private int[] sz;
    //Initialize and query set
    public UF_Tree_Weighted(int N){
        //The number of initialization packets. By default, there are N packets
        this.count = N;
        //Initialize eleAndGroup array
        this.eleAndGroup = new int[N];

        //Initialize the element in eleAndGroup and the identifier of its group, make the index of eleAndGroup array as the element of each node of the query set, and make the value at each index (the identifier of the group in which the element is located) the index

        for (int i = 0; i < eleAndGroup.length; i++) {
            eleAndGroup[i] = i;
        }

        this.sz = new int[N];
        //By default, the value at each index in sz is 1
        for (int i = 0; i < sz.length; i++) {
            sz[i] = 1;
        }

    }

    //How many groups are there in the current query set
    public int count(){
        return count;
    }

    //Judge and check whether element p and element q in the set are in the same group
    public boolean connected(int p,int q){
        return find(p) == find(q);
    }

    //Identifier of the group in which the element p is located
    public int find(int p){
        while(true){

            if (p == eleAndGroup[p]){
                return p;
            }

            p = eleAndGroup[p];
        }

    }

    //Merge the group of p element and the group of q element
    public void union(int p,int q){
        //Find the root node of the tree corresponding to the group of p and q elements

        int pRoot = find(p);
        int qRoot = find(q);

        //If p and q are already in the same group, there is no need to merge
        if (pRoot==qRoot){
            return;
        }

        //To determine whether the tree size corresponding to proot or qroot is large, you need to merge the smaller trees into the larger trees

        if (sz[pRoot]<sz[qRoot]){
            eleAndGroup[pRoot] = qRoot;
            sz[qRoot]+=sz[pRoot];
        }else{
            eleAndGroup[qRoot] = pRoot;
            sz[pRoot]+= sz[qRoot];
        }

        //Number of groups - 1

        this.count--;

    }

}

kruskal algorithm - KruskalMST

  • PriorityQueue—java.util
package graph.tu;

import java.util.PriorityQueue;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedDeque;

public class KruskalMST {
    //Save all edges of the minimum spanning tree
    private Queue<Edge> mst;
    //The index represents the vertex. Use uf.connect(v,w) to judge whether vertex v and vertex W are in the same tree. Use uf.union(v,w) to merge the tree where vertex v and vertex W are located
    private UF_Tree_Weighted uf;
    //All edges in the graph are stored, and the edges are sorted by weight using the minimum priority queue
    private Queue<Edge> pq;

    //According to a weighted undirected graph, the minimum spanning tree calculation object is created
    public KruskalMST(EdgeWeightedGraph G) {

        //Initialize mst
        this.mst = new ConcurrentLinkedDeque<>();
        //Initialize uf
        this.uf = new UF_Tree_Weighted(G.V());
        //Initialize pq
        this.pq = new PriorityQueue<>(G.E()+1);
        //Store all the edges in the graph in pq
        for (Edge e : G.edges()) {
            pq.offer(e);
        }

        //Traverse the pq queue, get the edge with the minimum weight, and process it

        while(!pq.isEmpty() && mst.size()<G.V()-1){
            //Find the edge with the least weight
            Edge e = pq.poll();
            //Find the two vertices of the edge
            int v = e.either();
            int w = e.other(v);

            //Judge whether the two vertices are already in the same tree. If they are in the same tree, the edge will not be processed. If they are not in the same tree, the two trees to which the two vertices belong will be merged into one tree
            if (uf.connected(v,w)){
                continue;
            }

            uf.union(v,w);

            //Get edge e into mst queue
            mst.offer(e);

        }

    }

    //Gets all edges of the minimum spanning tree
    public Queue<Edge> edges() {
        return mst;
    }
}


Tags: Algorithm data structure linear algebra

Posted on Sun, 10 Oct 2021 01:26:11 -0400 by delboy1978uk