1. 程式人生 > >大話資料結構--圖的最小生成樹-java實現

大話資料結構--圖的最小生成樹-java實現

這裡寫圖片描述

  • 普利姆(Prim)演算法
    這裡寫圖片描述
    這裡寫圖片描述
    這裡寫圖片描述
    這裡寫圖片描述
    這裡寫圖片描述
最小生成樹
 *    A
 * /  |  \
 * B- -F- -E
 * \  / \  /
 * C --  D
 * A B C D E F
 * 0 1 2 3 4 5
 * 
 * A-B 6   A-E 5   A-F 1
 * B-C 3   B-F 2
 * C-F 8   C-D 7
 * D-F 4   D-E 2
 * E-F 9

仔細看最小生成樹的邊比點少一個。
普利姆(Prim)演算法是先根據一個點找出與這個點相連線的邊來,在從待選邊的集合中找到權值最小的邊,依次往下找。

  • 克魯斯卡爾演算法
    這裡寫圖片描述

    這裡寫圖片描述
    這裡寫圖片描述
    這裡寫圖片描述
    這裡寫圖片描述
    克魯斯卡爾演算法是先遍歷所有的邊,在從邊中找到最小權值的邊。

就需要定義一個Edge類

package graph;

/**
 * Created by yxf on 2018/4/6.
 */
public class Edge {

    private int nodeIndexA; //第一個點
    private int nodeIndexB; //第二個點
    private int wegiht; //權重
    private boolean selected=false; //是否被選擇


    public Edge() {
    }

    public
Edge(int nodeIndexA, int nodeIndexB, int wegiht) { this.nodeIndexA = nodeIndexA; this.nodeIndexB = nodeIndexB; this.wegiht = wegiht; } public int getNodeIndexA() { return nodeIndexA; } public void setNodeIndexA(int nodeIndexA) { this.nodeIndexA = nodeIndexA; } public
int getNodeIndexB() { return nodeIndexB; } public void setNodeIndexB(int nodeIndexB) { this.nodeIndexB = nodeIndexB; } public int getWegiht() { return wegiht; } public void setWegiht(int wegiht) { this.wegiht = wegiht; } public boolean isSelected() { return selected; } public void setSelected(boolean selected) { this.selected = selected; } }
  • graph類
package graph;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * Created by yxf on 2018/4/6.
 * <p>
 *   A
 * /   \
 * B     D
 * / \   / \
 * C  F   G- H
 * \  /
 *  E
 * <p>
 *     A  B  C  D  E  F  G  H
 * A      1     1
 * B   1     1        1
 * C      1        1  1
 * D   1                 1  1
 * E         1
 * F      1  1
 * G            1           1
 * H            1        1
 * 最小生成樹
 *    A
 * /  |  \
 * B- -F- -E
 * \  / \  /
 * C --  D
 * A B C D E F
 * 0 1 2 3 4 5
 *
 * A-B 6   A-E 5   A-F 1
 * B-C 3   B-F 2
 * C-F   8   C-D 7
 * D-F 4   D-E 2
 * E-F 9
 */
public class Graph<T> {

    private final int DEFAULT_SIZE = 8;//設定預設尺寸
    private int capacity; //圖中最多可容納的頂點數
    private int nodeCount;//已經新增的頂點(結點)個數
    private Node[] nodeArray; //用來存放頂點陣列
    private int[] matrix;  //用來存放鄰接矩陣
    private final int value = 1; //預設權重
    private Edge[] pEdge;  //最小生成樹邊的集合

    public static class Node<T> {
        private T data;   //資料
        private boolean m_bIsVisted = false; //當前結點是否訪問

        public Node(T data) {
            this.data = data;
        }

        public Node(T data, boolean m_bIsVisted) {
            this.data = data;
            this.m_bIsVisted = m_bIsVisted;
        }
    }

    public Graph() {
        capacity = DEFAULT_SIZE;
        nodeArray = new Node[capacity];
        matrix = new int[capacity * capacity];
        pEdge = new Edge[capacity - 1];
    }

    public Graph(int capacity) {
        this.capacity = capacity;
        nodeArray = new Node[capacity];
        matrix = new int[this.capacity * this.capacity];
        pEdge = new Edge[capacity - 1];
    }

    //向圖中加入頂點(結點)
    public boolean addNode(T element) {
        if (nodeCount < 0 && nodeCount > capacity)
            throw new IndexOutOfBoundsException("陣列異常出界..");
        Node node = new Node(element);
        nodeArray[nodeCount] = node;
        nodeCount++;
        return true;
    }

    //重置結點
    public void resetNode() {
        for (int i = 0; i < nodeCount; i++) {
            nodeArray[i].m_bIsVisted = false;
        }
    }

    /**
     * 為有向圖設定鄰接矩陣
     *
     * @param row
     * @param col
     * @param val 權值 預設為1
     * @return
     */
    public boolean setValueToMatrixForDirectedGraph(int row, int col, int val) {
        if (row < 0 || row > capacity || col < 0 || col > capacity) {
            return false;
        }
        matrix[row * capacity + col] = val;
        return true;
    }

    public boolean setValueToMatrixForDirectedGraph(int row, int col) {
        if (row < 0 || row > capacity || col < 0 || col >= capacity) {
            return false;
        }
        matrix[row * capacity + col] = value;
        return true;
    }

    /**
     * 為無向圖設定鄰接矩陣
     *
     * @param row
     * @param col
     * @param val 權值 預設為1
     * @return
     */
    public boolean setValueToMatrixForUndirectedGraph(int row, int col, int val) {
        if (row < 0 || row > capacity || col < 0 || col >= capacity) {
            return false;
        }
        matrix[row * capacity + col] = val;
        matrix[col * capacity + row] = val;
        return true;
    }

    public boolean setValueToMatrixForUndirectedGraph(int row, int col) {
        if (row < 0 || row > capacity || col < 0 || col >= capacity) {
            return false;
        }
        matrix[row * capacity + col] = value;
        matrix[col * capacity + row] = value;
        return true;
    }

    //從矩陣中獲取權值
    private int getValueFromMatrix(int row, int col) {
        if (row < 0 || row > capacity || col < 0 || col >= capacity) {
            return -1;
        }
        return matrix[row * capacity + col];
    }

    // 深度優先遍歷
    public void depthFirstTraverse(int nodeIndex) {
        int value;
        System.out.print(nodeArray[nodeIndex].data + " ");
        nodeArray[nodeIndex].m_bIsVisted = true;

        for (int i = 0; i < capacity; i++) {
            value = getValueFromMatrix(nodeIndex, i);
            if (value != 0) {
                //訪問過了就退出
                if (nodeArray[i].m_bIsVisted) {
                    continue;
                }
                depthFirstTraverse(i);
            }
        }
    }

    //廣度優先遍歷
    public void breadthFirstTraverse(int nodeIndex) {
        System.out.println();
        System.out.print(nodeArray[nodeIndex].data + " ");
        nodeArray[nodeIndex].m_bIsVisted = true;
        ArrayList list = new ArrayList();
        list.add(nodeIndex);
        breadthFirstTraverseImpl(list);
    }

    public void breadthFirstTraverseImpl(ArrayList list) {
        int value;
        ArrayList curList = new ArrayList();
        for (int i = 0; i < list.size(); i++) {  //上一層結點
            for (int j = 0; j < capacity; j++) {  //上一層的結點與其他點是否有連線
                value = getValueFromMatrix((Integer) list.get(i), j);
                if (value != 0) {
                    if (nodeArray[j].m_bIsVisted) {  //訪問過了就退出
                        continue;
                    }
                    System.out.print(nodeArray[j].data + " ");
                    nodeArray[j].m_bIsVisted = true;
                    curList.add(j);
                }
            }
        }
        if (curList.size() == 0)
            return;
        else {
            breadthFirstTraverseImpl(curList);
        }
    }

    //普里姆生成樹
    public void primTree(int nodeIndex) {
        int value;
        int edgeCount = 0; //儲存邊
        LinkedList<Integer> nodeList = new LinkedList();//儲存點集合
        List<Edge> edgeList = new ArrayList<Edge>();//儲存待選邊集合

        System.out.println("點:" + nodeArray[nodeIndex].data);
        nodeList.add(nodeIndex);
        nodeArray[nodeIndex].m_bIsVisted = true;
        //找出的邊比點少1個
        while (edgeCount < capacity - 1) {
            int temp = nodeList.getLast();
            for (int i = 0; i < capacity; i++) {
                value = getValueFromMatrix(temp, i);
                if (value != 0) {
                    if (nodeArray[i].m_bIsVisted) {
                        continue;
                    }
                    Edge edge = new Edge(temp, i, value);
                    //存入待選邊集合
                    edgeList.add(edge);
                }
            }
            //從待選邊集合中找出最小的邊
            int edgeIndex = getMinEdge(edgeList);
            //將最小邊設定為true
            edgeList.get(edgeIndex).setSelected(true);

            System.out.print("最小邊:" + edgeList.get(edgeIndex).getNodeIndexA() + "----" + edgeList.get(edgeIndex).getNodeIndexB() + " ");
            System.out.print("權值: " + edgeList.get(edgeIndex).getWegiht());
            System.out.println();
            //將選出來的邊放到最小生成樹邊的集合中
            pEdge[edgeCount] = edgeList.get(edgeIndex);
            edgeCount++;
            //找出下一個點
            int nextNodeIndex = edgeList.get(edgeIndex).getNodeIndexB();
            nodeList.add(nextNodeIndex);
            //將這個點標記為true
            nodeArray[nextNodeIndex].m_bIsVisted = true;
            System.out.println("下一個點:" + nodeArray[nextNodeIndex].data);
        }
    }

    //從待選邊集合中找出最小的邊
    private int getMinEdge(List<Edge> edgeList) {
        int minWeight = 0; //最小權重
        int edgeIndex = 0;
        int i = 0;
        for (; i < edgeList.size(); i++) {
            //邊都沒有被選擇過
            if (!edgeList.get(i).isSelected()) {
                minWeight = edgeList.get(i).getWegiht();
                edgeIndex = i;
                break;
            }
        }
        if (minWeight == 0)
            return -1;
        for (; i < edgeList.size(); i++) {
            if (edgeList.get(i).isSelected()) {
                continue;
            }
            if (minWeight > edgeList.get(i).getWegiht()) {
                minWeight = edgeList.get(i).getWegiht();
                edgeIndex = i;
            }
        }
        return edgeIndex;
    }

    //克魯斯卡爾演算法
    public void kruskalTree(int nodeIndex) {
        int value;
        int edgeCount = 0; //儲存邊
        ArrayList<ArrayList<Integer>> nodeList = new ArrayList();//儲存點集合
        List<Edge> edgeList = new ArrayList<Edge>();//儲存待選邊集合

        //取出所有邊
        for (int i = 0; i < capacity; i++) {
            for (int j = i + 1; j < capacity; j++) {
                value = getValueFromMatrix(i, j);
                if (value != 0) {
                    Edge edge = new Edge(i, j, value);
                    //存入待選邊集合
                    edgeList.add(edge);
                }
            }
        }

        //1.找到演算法結束條件
        while (edgeCount < capacity - 1) {
            //2.從集合中找到最小邊
            int minEdgeIndex = getMinEdge(edgeList);
            //將最小邊設定為true
            edgeList.get(minEdgeIndex).setSelected(true);
            //3.找到最小邊連線的點
            int nodeIndexA = edgeList.get(minEdgeIndex).getNodeIndexA();
            int nodeIndexB = edgeList.get(minEdgeIndex).getNodeIndexB();
            //4.找出點所在的點集合
            int nodeALabel = -1;
            int nodeBLabel = -1;
            boolean nodeA = false;
            boolean nodeB = false;

            for (int i = 0; i < nodeList.size(); i++) {
                nodeA = isInList(nodeList.get(i), nodeIndexA);
                if (nodeA)
                    nodeALabel = i;
            }
            for (int i = 0; i < nodeList.size(); i++) {
                nodeB = isInList(nodeList.get(i), nodeIndexB);
                if (nodeB)
                    nodeBLabel = i;
            }

            //5.根據點所在集合的不同做出不同處理
            if (nodeALabel == -1 && nodeBLabel == -1) {
                ArrayList<Integer> nodeList1 = new ArrayList<Integer>();
                nodeList1.add(nodeIndexA);
                nodeList1.add(nodeIndexB);
                nodeList.add(nodeList1);
            } else if (nodeALabel == -1 && nodeBLabel != -1) {
                nodeList.get(nodeBLabel).add(nodeIndexA);
            } else if (nodeALabel != -1 && nodeBLabel == -1) {
                nodeList.get(nodeALabel).add(nodeIndexB);
            } else if (nodeALabel != -1 && nodeBLabel != -1 && nodeALabel != nodeBLabel) {
                mergeNodeList(nodeList.get(nodeALabel), nodeList.get(nodeBLabel));
                for (int i = nodeBLabel; i < nodeList.size() - 1; i++) {
                    nodeList.remove(i);
                }
            } else if (nodeALabel != -1 && nodeBLabel != -1 && nodeALabel == nodeBLabel) {
                continue;
            }
            pEdge[edgeCount] = edgeList.get(minEdgeIndex);
            edgeCount++;
            System.out.print("最小邊:" + edgeList.get(minEdgeIndex).getNodeIndexA() + "----" + edgeList.get(minEdgeIndex).getNodeIndexB() + " ");
            System.out.print("權值: " + edgeList.get(minEdgeIndex).getWegiht());
            System.out.println();
        }
    }

    private void mergeNodeList(ArrayList<Integer> nodeListA, ArrayList<Integer> nodeListB) {
        for (int i=0;i<nodeListB.size();i++){
            nodeListA.add(nodeListB.get(i));
        }
    }

    //判斷target是否在列表中
    private boolean isInList(ArrayList<Integer> nodeList, int target) {

        for (int i=0;i<nodeList.size();i++){
            if(nodeList.get(i)==target){
                return true;
            }
        }
        return false;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < capacity; i++) {
            for (int j = 0; j < capacity; j++) {
                sb.append(matrix[i * capacity + j] + " ");
            }
            sb.append("\r\n");
        }
        int len = sb.length();
        return sb.delete(len - 2, len).toString();
    }
}
  • 測試類
 @Test
    public void minSpanTreePrimTest(){
        Graph graph = new Graph(6);
        graph.addNode("A");
        graph.addNode("B");
        graph.addNode("C");
        graph.addNode("D");
        graph.addNode("E");
        graph.addNode("F");


        graph.setValueToMatrixForUndirectedGraph(0,1,6);
        graph.setValueToMatrixForUndirectedGraph(0,4,5);
        graph.setValueToMatrixForUndirectedGraph(0,5,1);
        graph.setValueToMatrixForUndirectedGraph(1,2,3);
        graph.setValueToMatrixForUndirectedGraph(1,5,2);
        graph.setValueToMatrixForUndirectedGraph(2,5,8);
        graph.setValueToMatrixForUndirectedGraph(2,3,7);
        graph.setValueToMatrixForUndirectedGraph(3,5,4);
        graph.setValueToMatrixForUndirectedGraph(3,4,2);
        graph.setValueToMatrixForUndirectedGraph(4,5,9);


        graph.primTree(0);
        //graph.kruskalTree(0);
    }

輸出

普里姆:
點:A
最小邊:0----5 權值: 1
下一個點:F
最小邊:5----1 權值: 2
下一個點:B
最小邊:1----2 權值: 3
下一個點:C
最小邊:5----3 權值: 4
下一個點:D
最小邊:3----4 權值: 2
下一個點:E
克魯斯卡爾演算法:
最小邊:0----5 權值: 1
最小邊:1----5 權值: 2
最小邊:3----4 權值: 2
最小邊:1----2 權值: 3
最小邊:3----5 權值: 4

Process finished with exit code 0