0
点赞
收藏
分享

微信扫一扫

[XJTUSE]数据结构学习——4.4 最小支撑树(完整代码实现——java)

文章目录

4.4 最小支撑树(Minimum-cost Spanning Tree)

概念

定义

给定一个连通无向图G,且它的每条边均有相应的长度或权值,则MST是一个包括G中的所有顶点及其边子集的图,边的子集满足下列条件:

环性质

环性质

假设T是一个有权无向图G=(V,E)的MST,如果选择一条属于E,但不属于T的边e加入到MST,从而使T形成一个环时,那么这个环中的任意一条边f都洞足如下关系

image-20220212115325019

分割性质

设集合U和W是图G=(V,E)的顶点集合的两个子集,这两个顶点子集将图分成了两部分,其中e是所有能够连接两个部分中权最小的边,那么e将是MST的一条边
image-20220212120423461

Prim算法——从点出发

算法步骤

1️⃣ 选择图中的任意一个顶点N开始,初始化MST为N

2️⃣ 计算MST中每个顶点到不在MST中的每个顶点之间的距离

3️⃣ 选择这些距离中最小的那条边,并将这条边中的不在MST中的顶点加入到MST中

4️⃣ 重复步骤2和3,直到没有可以加入到MST中的顶点为止

示例

image-20220212121744471

代码实现

相邻矩阵实现带权无向图

public class GraphMatrix {
    //用相邻矩阵实现无向图
    private ArrayList<String> V;//顶点
    private int E;//边数
    private int[][] matrix;//相邻矩阵
    private int[] mark;//判定一个结点是否被访问过
    public static final int VISITED = -2;//已经访问
    public static final int UNVISITED = -3;//未访问
    public static final int UNCONNECTED = 1000;//未访问

    /**
     * 初始化无向图
     *
     * @param n 结点数
     */
    public GraphMatrix(int n) {
        matrix = new int[n][n];
        mark = new int[n];
        for (int i = 0; i < n; i++) {
            mark[i] = UNVISITED;
            for (int j = 0; j < n; j++) {
                matrix[i][j] = UNCONNECTED;
            }
        }
        V = new ArrayList<String>(n);

    }

    /**
     * 添加结点
     *
     * @param vertex 结点值
     */
    public void insertVertex(String vertex) {
        V.add(vertex);
    }

    /**
     * 插入边
     *
     * @param v1     起点下标
     * @param v2     终点下标
     * @param weight 权值,这里0表示不相邻,1表示相邻
     */
    public void insertEdge(int v1, int v2, int weight) {
        matrix[v1][v2] = weight;
        matrix[v2][v1] = weight;
        E++;//边数+1
    }

    /**
     * 获得v1->v2的权值
     *
     * @param v1
     * @param v2
     * @return
     */
    public int getWeight(int v1, int v2) {
        return matrix[v1][v2];
    }

    /**
     * 获得边的数量
     *
     * @return
     */
    public int getEdgeNum() {
        return E;
    }

    /**
     * 获得结点数
     *
     * @return
     */
    public int getVertexNum() {
        return V.size();
    }

    public String getVertexValue(int index) {
        return V.get(index);
    }

    /**
     * 获得某个结点的第一个邻接点下标
     *
     * @param v 某个顶点下标
     * @return 第一个邻接点下标
     */
    public int getFirst(int v) {
        for (int i = 0; i < V.size(); i++) {
            if (matrix[v][i] > 0) {
                return i;
            }
        }
        return -1;
    }

    /**
     * @param v1 某个顶点坐标
     * @param v2 前一个邻接结点坐标
     * @return 下一个邻接结点坐标
     */
    public int getNext(int v1, int v2) {
        for (int i = v2 + 1; i < V.size(); i++) {
            if (matrix[v1][i] > 0) {
                return i;
            }
        }
        return -1;
    }

    public boolean isEdge(int v1, int v2) {
        return isLegalIndex(v1) && isLegalIndex(v2) && matrix[v1][v2] > 0;
    }

    public boolean isLegalIndex(int v) {
        return v >= 0 && v < V.size();
    }

    public void DFS() {
        for (int i = 0; i < getVertexNum(); i++) {
            setMark(i, UNVISITED);
        }//初始化所有顶点
        for (int i = 0; i < getVertexNum(); i++) {
            if (getMark(i) == UNVISITED) {
                DFSHelp(i);
            }
        }
    }

    public void DFSHelp(int v) {
        System.out.print(V.get(v) + " ");
        setMark(v, VISITED);
        for (int edge = getFirst(v); isEdge(v, edge); edge = getNext(v, edge)) {
            if (getMark(edge) == UNVISITED) {
                DFSHelp(edge);
            }
        }
    }

    public void BFS() {
        for (int i = 0; i < getVertexNum(); i++) {
            setMark(i, UNVISITED);
        }//初始化所有顶点
        for (int i = 0; i < getVertexNum(); i++) {
            if (getMark(i) == UNVISITED) {
                BFSHelp(i);
            }
        }
    }

    public void BFSHelp(int v) {
        LQueue<Integer> queue = new LQueue<>();
        queue.enqueue(v);
        setMark(v, VISITED);
        while (!queue.isEmpty()) {
            int temp = queue.dequeue();
            System.out.print(V.get(temp) + " ");
            for (int index = getFirst(temp); isEdge(temp, index); index = getNext(temp, index)) {
                if (getMark(index) == UNVISITED) {
                    queue.enqueue(index);
                    setMark(index, VISITED);
                }
            }
        }
    }

    public void setMark(int v, int val) {
        mark[v] = val;
    }

    public int getMark(int v) {
        return mark[v];
    }

    //打印邻接矩阵
    public void print() {
        for (int[] edge : matrix) {
            System.out.println(Arrays.toString(edge));
        }
    }

    public static void main(String[] args) {
        //定义图的所有顶点
        String[] vertexs = {"A", "B", "C", "D", "E", "F"};
        //创建图
        GraphMatrix graph = new GraphMatrix(vertexs.length);
        //添加顶点到图中
        for (String vertex : vertexs) {
            graph.insertVertex(vertex);
        }
        //添加边到图中
        graph.insertEdge(0, 2, 1);
        graph.insertEdge(0, 4, 1);
        graph.insertEdge(1, 2, 1);
        graph.insertEdge(1, 5, 1);
        graph.insertEdge(2, 3, 1);
        graph.insertEdge(2, 5, 1);
        graph.insertEdge(3, 5, 1);
        graph.insertEdge(4, 5, 1);
        graph.print();
        graph.DFS();
        System.out.println();
        graph.BFS();
    }
}
public class Prim {
    private GraphMatrix originalGraph;//用相邻矩阵实现的原图
    private ArrayList<Integer> N;//不在MST集合中的元素下标
    private ArrayList<Integer> S;//在MST集合中的元素下标
    private int vertexNum;//顶点数


    /**
     * 初始化
     * @param originalGraph
     */
    public void createOriginalGraph(GraphMatrix originalGraph) {
        this.originalGraph = originalGraph;
        this.vertexNum = originalGraph.getVertexNum();
        this.N = new ArrayList<>(vertexNum);
        for (int i = 0; i < vertexNum; i++) {
            N.add(i);
        }
        this.S = new ArrayList<>(0);
    }

    public void showOriginalGraph() {
        originalGraph.print();
    }

    /**
     * 从start开始使用prim算法生成MST
     * @param start 起点的下标
     */
    public void prim(int start) {
        int v1 = start;
        int v2 = originalGraph.getFirst(v1);
        originalGraph.setMark(v1, GraphMatrix.VISITED);
        S.add(v1);//加入MST
        N.remove(v1);//同时从N中移除
        for (int i = 0; i < vertexNum - 1; i++) {
            int miniWeight = GraphMatrix.UNCONNECTED;
            for (int j = 0; j < S.size(); j++) {//从MST中的顶点
                for (int k = 0; k < N.size(); k++) {//到非MST中的顶点
                    if (originalGraph.getMark(S.get(j)) == GraphMatrix.VISITED && 
                            originalGraph.getMark(N.get(k)) == GraphMatrix.UNVISITED
                            && originalGraph.getWeight(S.get(j), N.get(k)) < miniWeight) {
                        //只需要看从已访问结点到未访问结点的边
                        miniWeight = originalGraph.getWeight(S.get(j), N.get(k));
                        v1 = S.get(j);
                        v2 = N.get(k);
                    }
                }
            }
            System.out.println(originalGraph.getVertexValue(v1) + "-" + miniWeight + "->"
                    + originalGraph.getVertexValue(v2));
            //更新
            S.add(v2);//加入MST
            N.remove((Object) v2);//同时从N中移除
            originalGraph.setMark(v2, GraphMatrix.VISITED);
        }
    }

    public static void main(String[] args) {
        //定义图的所有顶点
        String[] vertexs = {"A", "B", "C", "D", "E", "F"};
        //创建图
        GraphMatrix graph = new GraphMatrix(vertexs.length);
        //添加顶点到图中
        for (String vertex : vertexs) {
            graph.insertVertex(vertex);
        }
        //添加边到图中
        graph.insertEdge(0, 2, 7);
        graph.insertEdge(0, 4, 9);
        graph.insertEdge(1, 2, 5);
        graph.insertEdge(1, 5, 6);
        graph.insertEdge(2, 3, 1);
        graph.insertEdge(2, 5, 2);
        graph.insertEdge(3, 5, 2);
        graph.insertEdge(4, 5, 1);
        Prim test = new Prim();
        test.createOriginalGraph(graph);
        test.showOriginalGraph();
        test.prim(0);
    }
}
[1000, 1000, 7, 1000, 9, 1000]
[1000, 1000, 5, 1000, 1000, 6]
[7, 5, 1000, 1, 1000, 2]
[1000, 1000, 1, 1000, 1000, 2]
[9, 1000, 1000, 1000, 1000, 1]
[1000, 6, 2, 2, 1, 1000]
A-7->C
C-1->D
C-2->F
F-1->E
C-5->B

Kruskal算法——从边出发

算法步骤

1️⃣设连通网 N = (V, E ),令最小生成树初始状态为只有 n 个顶点而无边的非连通图 T=(V, { }),每个顶点自成一个连通分量。

2️⃣在 E 中选取代价最小的边,若该边依附 的顶点落在 T 中不同的连通分量上(即: 不能形成环),则将此边加入到 T 中;否 则,舍去此边,选取下一条代价最小的边。

3️⃣ 依此类推,直至 T 中所有顶点都在同一 连通分量上为止。

示例

image-20220212142137979

代码实现

1️⃣ 利用并查集树判断结点是否连通,以及连通两个非连通分量

public class GTNodeA {
    private int parent;//父结点下标
    private Object element;//存储元素内容

    public GTNodeA() {
        this(null, -1);
    }

    public GTNodeA(Object element) {
        this(element, -1);
    }

    public GTNodeA(Object element, int parent) {
        this.element = element;
        this.parent = parent;
    }

    public int getParent() {
        return parent;
    }

    public int setParent(int parent) {
       return this.parent = parent;
    }

    public Object getElement() {
        return element;
    }

    public void setElement(Object element) {
        this.element = element;
    }
}

public class UnionFindTree {
    //用树实现并查集
    public GTNodeA[] set;

    public UnionFindTree(GTNodeA[] set) {
        this.set = set;
    }

    /**
     * 返回包含给定元素的集合名字
     *
     * @param i 元素下标
     * @return 以根结点下标作为集合名字
     */
    public int find(int i) {
        GTNodeA current = set[i];
        if (current.getParent()<0) return i;
        return current.setParent(find(current.getParent()));
    }//使用路劲压缩法:在查找某个元素是否属于某个集合时,将该结点到根结点路径上
    // 所有结点的父指针全部改为指向根结点,这种方式可以产生极浅的树

    /**
     * 生成一个新的集合,该集合是i所属的集合set1和j所属的集合set2的并集
     *
     * @param i
     * @param j
     */
    public void union(int i, int j) {
        int root1 = find(i);
        int num1 = set[root1].getParent();
        int root2 = find(j);
        int num2 = set[root2].getParent();
        if (num1 <= num2) {
            set[root1].setParent(num1+num2);
            set[root2].setParent(root1);
            //将其中结点数少的一棵树的根结点的父结点设置为
            //结点数多的一棵树的根结点
        } else {
            set[root2].setParent(num1+num2);
            set[root1].setParent(root2);
            //将其中结点数少的一棵树的根结点的父结点设置为
            //结点数多的一棵树的根结点
        }
    }//使用了重量平衡原则


    /**
     * 判断元素i和元素j是否在同一个分组中
     * @param i
     * @param j
     * @return
     */
    public boolean isConnected(int i,int j){
        return find(i)==find(j);
    }
    public void print() {
        for (int i = 0; i < set.length; i++) {
            System.out.print(set[i].getElement() + " ");
        }
    }

    public static void main(String[] args) {
        GTNodeA node_A = new GTNodeA("A");
        GTNodeA node_C = new GTNodeA("C");
        GTNodeA node_H = new GTNodeA("H");
        GTNodeA node_K = new GTNodeA("K");
        GTNodeA node_E = new GTNodeA("E");
        GTNodeA node_B = new GTNodeA("B");
        GTNodeA node_D = new GTNodeA("D");
        GTNodeA node_F = new GTNodeA("F");
        GTNodeA node_J = new GTNodeA("J");
        GTNodeA node_L = new GTNodeA("L");
        GTNodeA node_N = new GTNodeA("N");
        GTNodeA node_M = new GTNodeA("M");
        GTNodeA node_I = new GTNodeA("I");
        GTNodeA node_G = new GTNodeA("G");
        GTNodeA[] test = {node_A, node_B, node_C, node_D,
                node_E, node_F, node_G, node_H,
                node_I, node_J, node_K, node_L,
                node_M, node_N};
        UnionFindTree testTree = new UnionFindTree(test);
        System.out.print("initialized forest: ");
        testTree.print();
        System.out.println();
        System.out.println("find(1): " + testTree.find(1));
        System.out.println("find(3): " + testTree.find(3));
        System.out.println("find(4): " + testTree.find(4));
        testTree.union(0, 4);
        testTree.union(0, 1);
        testTree.union(0, 3);
        System.out.println("union(0,4), union(0,1), union(0,3)");
        System.out.println("find(1): " + testTree.find(1));
        System.out.println("find(3): " + testTree.find(3));
        System.out.println("find(4): " + testTree.find(4));
    }
}

2️⃣ 利用最小优先队列优化边的查找与删除

public class Edge implements Comparable<Edge> {
    private int v1;//起点
    private int v2;//终点
    private int weight;//权重

    public Edge(int v1, int v2) {
        this.v1 = v1;
        this.v2 = v2;
    }

    public Edge(int v1, int v2, int weight) {
        this.v1 = v1;
        this.v2 = v2;
        this.weight = weight;
    }

    public int getV1() {
        return v1;
    }

    public void setV1(int v1) {
        this.v1 = v1;
    }

    public int getV2() {
        return v2;
    }

    public void setV2(int v2) {
        this.v2 = v2;
    }

    public int getWeight() {
        return weight;
    }

    public void setWeight(int weight) {
        this.weight = weight;
    }


    @Override
    public int compareTo(@NotNull Edge edge) {
        if (this.weight < edge.weight) {
            return -1;
        } else if (this.weight == edge.weight) {
            return 0;
        } else {
            return 1;
        }
    }

    @Override
    public String toString() {
        return "Edge{" +
                 v1 +
                "->" + v2 +
                " weight=" + weight +
                '}';
    }
}

public class MinPriorityQueue<T extends Comparable<T>> {
    //最小优先队列
    private static final int DEFAULT_CAPACITY = 10;//默认大小
    private int currentSize;//当前堆的大小
    private T[] array;//堆数组

    public MinPriorityQueue() {
        this.array = (T[]) new Comparable[DEFAULT_CAPACITY + 1];
        currentSize = 0;
    }

    public MinPriorityQueue(int size) {
        this.array = (T[]) new Comparable[size + 1];
        currentSize = 0;
    }

    public MinPriorityQueue(T[] array) {

        this.array = (T[]) new Comparable[array.length + 1];
        for (int i = 0; i < array.length; i++) {
            this.array[i + 1] = array[i];
        }//从1开始算
        currentSize = array.length;
        buildHeap();
    }

    /**
     * 往堆中插入元素
     *
     * @param element 元素值
     */
    public void insert(T element) {
        try {
            if (isFull()) {
                throw new Exception("Array is full!");
            }
            int temp = ++currentSize;
            array[temp] = element;//将element放在数组最后
            while ((temp != 1) && (array[temp].compareTo(array[getParent(temp)]) < 0)) {
                swap(array, temp, getParent(temp));
                temp = getParent(temp);//如果比父结点的值小则于其交换
            }//注意根结点的下标为1,不是0
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public T findMin() {
        return array[1];
    }

    /**
     * 删除堆顶元素
     */
    public void deleteMin() {
        try {
            if (isEmpty()) {
                throw new Exception("Array is empty!");
            } else {
                swap(array, 1, currentSize--);//将堆顶元素放到最后,同时删除
                if (currentSize != 0) siftDown(1);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 大的值下沉
     *
     * @param pos 当前位置
     */
    private void siftDown(int pos) {
        try {
            if (pos < 0 || pos > currentSize) {
                throw new Exception("Illegal position!");
            }
            while (!isLeaf(pos)) {
                int j = getLeft(pos);
                if ((j < currentSize) && (array[j].compareTo(array[j + 1])) > 0) j++;
                //跟子树中最小的值交换
                if (array[pos].compareTo(array[j]) < 0) return;
                //当前值已经比子树中的值都小,则返回
                swap(array, pos, j);//交换
                pos = j;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void print() {
        for (int i = 1; i <= currentSize; i++) {
            System.out.print(array[i] + " ");
        }
    }

    public void heapSort() {
        while (currentSize != 0) {
            System.out.print(findMin() + " ");
            deleteMin();
        }
    }

    public boolean isEmpty() {
        return currentSize == 0;
    }

    public boolean isFull() {
        return currentSize == array.length - 1;
    }


    private boolean isLeaf(int i) {
        return i > currentSize / 2;
    }

    private void buildHeap() {
        for (int i = currentSize / 2; i > 0; i--) {
            siftDown(i);//对每个非叶子结点进行下沉操作
            //从右到左,从下到上
        }
    }

    private int getLeft(int i) {
        return 2 * i;
    }

    private int getRight(int i) {
        return 2 * i + 1;
    }

    private int getParent(int i) {
        return i / 2;
    }

    private void swap(T[] array, int x, int y) {
        T temp = array[y];
        array[y] = array[x];
        array[x] = temp;
        return;
    }

    public static void main(String[] args) throws Exception {
        Edge edge1 = new Edge(1, 2, 1);
        Edge edge2 = new Edge(1, 2, 2);
        Edge edge3 = new Edge(1, 2, 6);
        Edge edge4 = new Edge(1, 2, 4);
        Edge edge5 = new Edge(1, 2, 5);
        Edge edge6 = new Edge(1, 2, 7);
        Edge edge7 = new Edge(1, 2, 3);
//        Integer[] elements = {1, 2, 6, 4, 5, 7, 3};
//        MinPriorityQueue<Integer> test = new MinPriorityQueue<Integer>(elements);
        Edge[] edges = {edge1, edge2, edge3, edge4, edge5, edge6, edge7};

        MinPriorityQueue<Edge> test = new MinPriorityQueue<Edge>(edges);

        test.print();
        System.out.println();
        test.heapSort();
        System.out.println();
        test.print();
    }
}

3️⃣ 算法具体实现

public class Kruskal {
    private ArrayList<Edge> MST;//最小生成树中的所有边
    private UnionFindTree ufTree;//并查集树,放索引
    private MinPriorityQueue<Edge> allEdges;//图中所有的边
    private GraphMatrix originalGraph;//用相邻矩阵实现的原图
    private int vertexNum;//结点数

    public Kruskal(GraphMatrix originalGraph) {
        this.originalGraph = originalGraph;
        this.vertexNum = originalGraph.getVertexNum();
    }

    public void generateMST() {
        this.MST = new ArrayList<Edge>();//初始化MST
        GTNodeA[] nodes = new GTNodeA[vertexNum];
        for (int i = 0; i < vertexNum; i++) {
            nodes[i] = new GTNodeA(i);
        }
        this.ufTree = new UnionFindTree(nodes);//初始化并查集
        this.allEdges = new MinPriorityQueue<>(originalGraph.getEdgeNum());//初始化优先队列
        for (int i = 0; i < vertexNum; i++) {
            for (int j = i + 1; j < vertexNum; j++) {
                if (originalGraph.getWeight(i, j) != GraphMatrix.UNCONNECTED) {
                    allEdges.insert(new Edge(i, j, originalGraph.getWeight(i, j)));
                }
            }
        }//将所有的边加入优先队列中
        while (!allEdges.isEmpty() && MST.size() < vertexNum - 1) {
            Edge e = allEdges.findMin();
            allEdges.deleteMin();
            int v1 = e.getV1();
            int v2 = e.getV2();
            //判断v1和v2是否已经连通
            if (ufTree.isConnected(v1, v2)) continue;
            ufTree.union(v1, v2);//不连通则连接
            MST.add(e);//将边并入MST
        }
    }

    public ArrayList<Edge> getMST() {
        return MST;
    }

    /**
     * 打印MST的所有边
     */
    public void printMST() {
        for (Edge edge : MST) {
            System.out.println(originalGraph.getVertexValue(edge.getV1()) + "-"
                    + originalGraph.getWeight(edge.getV1(), edge.getV2())
                    + "->" + originalGraph.getVertexValue(edge.getV2()));
        }
    }

    public static void main(String[] args) {
        String[] vertexs = {"A", "B", "C", "D", "E", "F"};
        //创建图
        GraphMatrix graph = new GraphMatrix(vertexs.length);
        //添加顶点到图中
        for (String vertex : vertexs) {
            graph.insertVertex(vertex);
        }
        //添加边到图中
        graph.insertEdge(0, 2, 7);
        graph.insertEdge(0, 4, 9);
        graph.insertEdge(1, 2, 5);
        graph.insertEdge(1, 5, 6);
        graph.insertEdge(2, 3, 1);
        graph.insertEdge(2, 5, 2);
        graph.insertEdge(3, 5, 2);
        graph.insertEdge(4, 5, 1);
        Kruskal test = new Kruskal(graph);
        test.generateMST();
        test.printMST();
    }
}

C-1->D
E-1->F
C-2->F
B-5->C
A-7->C
举报

相关推荐

0 条评论