Java 經典演算法分析總彙

艾陽丶發表於2017-05-03

前言


    在計算機軟體專業中,演算法分析與設計是一門非常重要的課程,很多人為它如痴如醉。很多問題的解決,程式的編寫都要依賴它,在軟體還是程式導向的階段,就有‘程式=演算法+資料結構’這個公式。演算法的學習對於培養一個人的邏輯思維能力是有極大幫助的,它可以培養 我們養成思考分析問題,解決問題的能力。    如果一個演算法有缺陷,或不適合某個問題,執行這個演算法將不會解決這個問題。不同的演算法可能用不同的時間、空間或效率來完成同樣的任務。一個演算法的優劣可以用空間複雜性和時間複雜度來衡量。演算法可以使用自然語言、虛擬碼、流程圖等多種不同的方法來描述。計算機系統中的作業系統、語言編譯系統、資料庫管理系統以及各種各樣的計算機應用系統中的軟體,都必須使用具體的演算法來實現。演算法設計與分析是電腦科學與技術的一個核心問題。因此,學習演算法無疑會增強自己的競爭力,提高自己的修為,為自己增彩。


演算法概念:

    演算法簡單來說就是指解題方案的準確而完整的描述,是一系列解決問題的清晰指令,也就是說演算法告訴計算機怎麼做,以此來解決問題。同一個問題存在多種演算法來解決它,但是這些演算法存在著優劣之分,好的演算法速度快,效率高,佔用空間小,差的演算法不僅複雜難懂,而且效率低,對機器要求還高,當然,有時候演算法之間存在一種互補關係,有些演算法效率高,節省時間,但浪費空間,另外一些演算法可能速度上慢些,但是空間比較節約,這時候 我們就應該根據實際要求,和具體情況來採取相應的演算法來解決問題。


一、約瑟夫演算法

    約瑟夫環:已知n個人(以編號1,2,3...n分別表示)圍坐在一張圓桌周圍。從編號為k的人開始報數,數到m的那個人出列;他的下一個人又從1開始報數,數到m的那個人又出列;依此規律重複下去,直到圓桌周圍的人全部出列。

[java] view plain co
  1. public class YuesefuTest {  
  2.       
  3.     public static void main(String[] args) {  
  4.         int totalNum = 10;  
  5.         int countNum = 3;  
  6.           
  7.         yuesefuByMyself(totalNum, countNum);  
  8.         yuesefu(totalNum, countNum);  
  9.     }  
  10.       
  11.     /** 
  12.      * 此方法 k 為 list 的下標 
  13.      * @param totalNum 
  14.      * @param countNum 
  15.      */  
  16.     public static void yuesefuByMyself( int totalNum,int countNum){  
  17. //      初始化人數  
  18.         List<Integer> start = new ArrayList<Integer>();  
  19.           
  20.         for(int i=1; i<=totalNum; i++){  
  21.             start.add(i);  
  22.         }  
  23.           
  24. //      此處的k為list的下標,開始報數人的下標,第一個人為0,第n個人為n-1  
  25.         int k = 0;  
  26.         while(start.size()>0){  
  27. //          下一個出列人的下標,因為是從當前報數人開始數,所以減1為下一個出列人的下標  
  28.             k = k + countNum -1;  
  29.               
  30. //          當 下標+1 超過了 list 的size  
  31.             if(k + 1>start.size()){  
  32. //              當size 為 10 ,下標要取 10 ,最大下標為9,應該取 list 的 第一個,即下標為0,  
  33. //              同理,直接取餘即可為正確下標  
  34.                 k = k % start.size();  
  35.             }  
  36.               
  37.             System.out.print(start.get(k)+",");  
  38.               
  39. //          出列,下一個開始報數人的下標即為出列人的下標  
  40.             start.remove(k);  
  41.         }  
  42.           
  43.         System.out.println();  
  44.     }  
  45.       
  46.     public static void yuesefu(int totalNum,int countNum){  
  47. //      初始化人數  
  48.         List<Integer> start = new ArrayList<Integer>();  
  49.           
  50.         for(int i=1; i<=totalNum; i++){  
  51.             start.add(i);  
  52.         }  
  53.           
  54. //      從第K個開始計數  
  55.         int k = 0;  
  56.         while(start.size()>0){  
  57.             k = k + countNum;  
  58. //          第m人的索引位置  
  59.             k = k % (start.size()) -1;  
  60. //          判斷是否到隊尾  
  61.             if(k<0){  
  62.                 System.out.print(start.get(start.size()-1)+",");  
  63.                 start.remove(start.size()-1);  
  64.                 k = 0;  
  65.             } else {  
  66.                 System.out.print(start.get(k)+",");  
  67.                 start.remove(k);  
  68.             }  
  69.         }  
  70.           
  71.         System.out.println();  
  72.     }  
  73.       
  74.   
  75. }  


 

二、二叉樹的構建及遍歷 

有一些部落格構建出來的二叉樹是完全二叉樹,這篇部落格什麼樣的二叉樹都能構建,圖為程式碼所示的一棵二叉樹


public class BinaryTree {  
      
    private static String [] array = {"A","B","D","H","","","I","","","E","","J","","",  
            "C","F","","K","","","G","",""};  
    private static int arrayIndex = 0;  
      
//  建立一棵二叉樹,約定使用者遵照前序遍歷的方式輸入資料  
//  不使用迭代是因為迭代必須要知道這棵樹有多深,  
//  遞迴只需要輸入就可以自行決定深度  
//  type:結點型別 0 根節點 1左孩子 2右孩子  
    public static TreeNode createBinaryTree(int type,String parentData) {  
        switch (type) {  
        case 0:  
            System.out.print("根節點:");  
            break;  
        case 1:  
            System.out.print(parentData+"的左孩子:");  
            break;  
        case 2:  
            System.out.print(parentData+"的右孩子:");  
            break;  
        }  
          
//      可以使用手動輸入也可以放到陣列裡  
//      Scanner sc = new Scanner(System.in);  
//      String data = sc.nextLine();  
          
        String data = "";  
        if(arrayIndex<array.length){  
            data = array[arrayIndex];  
            System.out.println(data);  
              
            arrayIndex++;  
        }else{  
            System.out.println();  
        }  
          
        TreeNode node = null;  
          
//      data為空表示沒有這個孩子  
        if(data==null||data.equals("")){  
            return node;  
        }else{  
            node = new TreeNode(data);  
            node.setLchild(createBinaryTree(1,node.getData()));  
            node.setRchild(createBinaryTree(2,node.getData()));  
              
            return node;  
        }  
          
    }  
      
      
//  前序遍歷  
    public static void preOrderTraverse(TreeNode node){  
        if(node != null){  
//          根,左,右  
            System.out.print(node.getData());  
            preOrderTraverse(node.getLchild());  
            preOrderTraverse(node.getRchild());  
        }  
    }  
      
//  中序遍歷  
    public static void inOrderTraverse(TreeNode node){  
        if(node != null){  
//          左,根,右  
            inOrderTraverse(node.getLchild());  
            System.out.print(node.getData());  
            inOrderTraverse(node.getRchild());  
        }  
    }  
      
//  後序遍歷  
    public static void postOrderTraverse(TreeNode node){  
        if(node != null){  
//          左,右,根  
            postOrderTraverse(node.getLchild());  
            postOrderTraverse(node.getRchild());  
            System.out.print(node.getData());  
        }  
    }  
      
//    
      
  
    public static void main(String[] args) {  
        TreeNode rootNode = createBinaryTree(0,"");  
          
        System.out.println();  
        System.out.print("前序遍歷:");  
        preOrderTraverse(rootNode);  
          
        System.out.println();  
        System.out.print("中序遍歷:");  
        inOrderTraverse(rootNode);  
          
        System.out.println();  
        System.out.print("後序遍歷:");  
        postOrderTraverse(rootNode);  
    }  
  
}  
  
/** 
 * 二叉樹結點 
 * @author cmdsm 
 * 
 */  
class TreeNode{  
    private String data;  
      
    private TreeNode lchild;  
    private TreeNode rchild;  
      
      
      
    public TreeNode() {  
        super();  
    }  
  
    public TreeNode(String data) {  
        this.data = data;  
    }  
  
    public String getData() {  
        return data;  
    }  
      
    public void setData(String data) {  
        this.data = data;  
    }  
  
    public TreeNode getLchild() {  
        return lchild;  
    }  
  
    public void setLchild(TreeNode lchild) {  
        this.lchild = lchild;  
    }  
  
    public TreeNode getRchild() {  
        return rchild;  
    }  
  
    public void setRchild(TreeNode rchild) {  
        this.rchild = rchild;  
    }  
  
    @Override  
    public String toString() {  
        return "TreeNode [data=" + data + ", lchild=" + lchild + ", rchild=" + rchild + "]";  
    }  
      
      
      
}  

結果:

 

三、索二叉樹(中序)

程式碼所示為下圖二叉樹

中序遍歷:CBDAEF

C,D,F有兩個空指標域,E有一個


步驟如下:

1.建立二叉樹

2.建立頭結點

3.中序遍歷線索化

4.中序遍歷此線索二叉樹(非遞迴方式)

public class ThreadedBinaryTree {  
      
    private static String [] array = {"A","B","C","","","D","","","E","","F","",""};  
    private static int arrayIndex = 0;  
      
    /** 
     * 全域性node,始終指向剛剛訪問過的結點 
     */  
    private static ThreadedBinaryNode preNode;  
      
    /** 
     * 1.參考建立二叉樹,前序遍歷輸入 
     */  
    public static ThreadedBinaryNode createThreadedBinaryTree(){  
        String data = "";  
        if(arrayIndex<array.length){  
            data = array[arrayIndex];  
              
            arrayIndex++;  
        }  
          
        ThreadedBinaryNode node = null;  
          
//      data為空表示沒有這個孩子  
        if(data==null||data.equals("")){  
            return node;  
        }else{  
            node = new ThreadedBinaryNode(data);  
            node.setLchild(createThreadedBinaryTree());  
            node.setRchild(createThreadedBinaryTree());  
              
            node.setLtag(PointerTag.LINK);  
            node.setRtag(PointerTag.LINK);  
              
            return node;  
        }  
    }  
      
    /** 
     * 2.建立頭結點,左孩子指向根節點 
     * @param rootNode 
     */  
    public static ThreadedBinaryNode createHeadNode(ThreadedBinaryNode rootNode){  
        ThreadedBinaryNode headNode = new ThreadedBinaryNode();  
          
        headNode.setLtag(PointerTag.LINK);  
        headNode.setRtag(PointerTag.THREAD);  
          
//      右孩子先指向自己,如果根節點不為null,指向中序遍歷的最後一個結點,為null不用變  
        headNode.setRchild(headNode);  
          
        if(rootNode != null){  
//          根結點不為null,頭結點的左孩子指向根結點  
            headNode.setLchild(rootNode);  
              
            preNode = headNode;  
              
//          開始中序遍歷根結點  
            inOrderTraverse(rootNode);  
              
//          中序遍歷的最後一個結點的後繼指向頭結點  
            preNode.setRtag(PointerTag.THREAD);  
            preNode.setRchild(headNode);  
              
//          頭結點的右孩子指向最後一個結點  
            headNode.setRchild(preNode);  
              
        }else{  
//          根節點為null 左孩子指向自己  
            headNode.setLchild(headNode);  
        }  
          
        return headNode;  
    }  
      
    /** 
     * 3.中序遍歷線索化 
     */  
    public static void inOrderTraverse(ThreadedBinaryNode node){  
        if(node != null){  
//          遞迴左孩子線索化  
            inOrderTraverse(node.getLchild());  
              
//          結點處理  
            if(null == node.getLchild()){  
//              如果左孩子為空,設定tag為線索 THREAD,並把lchild指向剛剛訪問的結點  
                node.setLtag(PointerTag.THREAD);  
                node.setLchild(preNode);  
            }  
              
            if(null == preNode.getRchild()){  
//              如果preNode的右孩子為空,設定tag為線索THREAD  
                preNode.setRtag(PointerTag.THREAD);  
                preNode.setRchild(node);  
            }  
              
//          此處和前後兩個遞迴的順序不能改變,和結點處理同屬一個級別  
            preNode = node;  
//          System.out.print(node.getData());  
              
//          遞迴右孩子線索化  
            inOrderTraverse(node.getRchild());  
              
        }  
    }  
      
    /** 
     * 4.中序遍歷 非遞迴方式 
     * @param headNode 
     */  
    public static void inOrderTraverseNotRecursion(ThreadedBinaryNode headNode){  
        ThreadedBinaryNode node = headNode.getLchild();  
          
        while(headNode != node){  
              
//          最左  
            while(node.getLtag() == PointerTag.LINK){  
                node = node.getLchild();  
            }  
              
            System.out.print(node.getData());  
              
//          根  
            while(node.getRtag() == PointerTag.THREAD && node.getRchild() !=headNode){  
                node = node.getRchild();  
                  
                System.out.print(node.getData());  
            }  
              
//          右,不能列印是因為該子樹下可能還存在最左  
            node = node.getRchild();  
        }  
    }  
      
      
      
    public static void main(String[] args) {  
//      建立二叉樹,約定前序輸入  
        ThreadedBinaryNode rootNode = createThreadedBinaryTree();  
//      建立頭結點,並中序遍歷線索化  
        ThreadedBinaryNode headNode = createHeadNode(rootNode);  
//      中序遍歷 非遞迴方式輸出  
        inOrderTraverseNotRecursion(headNode);  
    }  
  
}  
  
class ThreadedBinaryNode{  
    private String data;  
      
    private ThreadedBinaryNode lchild;  
    private ThreadedBinaryNode rchild;  
      
    private PointerTag ltag;  
    private PointerTag rtag;  
      
      
    public String getData() {  
        return data;  
    }  
    public void setData(String data) {  
        this.data = data;  
    }  
    public ThreadedBinaryNode getLchild() {  
        return lchild;  
    }  
    public void setLchild(ThreadedBinaryNode lchild) {  
        this.lchild = lchild;  
    }  
    public ThreadedBinaryNode getRchild() {  
        return rchild;  
    }  
    public void setRchild(ThreadedBinaryNode rchild) {  
        this.rchild = rchild;  
    }  
    public PointerTag getLtag() {  
        return ltag;  
    }  
    public void setLtag(PointerTag ltag) {  
        this.ltag = ltag;  
    }  
    public PointerTag getRtag() {  
        return rtag;  
    }  
    public void setRtag(PointerTag rtag) {  
        this.rtag = rtag;  
    }  
      
      
    public ThreadedBinaryNode(String data) {  
        super();  
        this.data = data;  
    }  
    public ThreadedBinaryNode() {  
        super();  
    }  
    @Override  
    public String toString() {  
        return "ThreadedBinaryNode [data=" + data + ", ltag=" + ltag  
                + ", rtag=" + rtag + "]";  
    }  
      
      
      
}  
  
/** 
 * LINK :表示指向左右孩子的指標 
 * THREAD:表示指向前驅後繼的線索 
 * @author cmdsm 
 * 
 */  
enum PointerTag{  
    LINK , THREAD  
}  


四、普里姆(Prim)演算法

個人認為此演算法遍歷順序的決定條件:

1.確定第一個頂點

2.下一個頂點可到(小於正無窮)

3.取可到頂點中最小權值的一個


程式碼中的圖


最小生成樹:99



程式碼(參考其他文章):


public class MinSpanTree {  
    /** 鄰接矩陣*/  
    int[][] matrix;  
    /** 表示正無窮*/  
    int MAX_WEIGHT = Integer.MAX_VALUE;  
    /** 頂點個數*/  
    int size;  
  
    /** 
     * 普里姆演算法實現最小生成樹:先初始化拿到第一個頂點相關聯的權值元素放到陣列中-》找到其中權值最小的頂點下標-》再根據該下標,將該下標頂點相關聯的權值加入到陣列中-》迴圈遍歷處理 
     */  
    public void prim() {  
        /**存放當前到全部頂點最小權值的陣列,如果已經遍歷過的頂點權值為0,無法到達的為正無窮*/  
        int[] tempWeight = new int[size];  
        /**當前到下一個最小權值頂點的最小權值*/  
        int minWeight;  
        /**當前到下一個最小權值的頂點*/  
        int minId;  
        /**權值總和*/  
        int sum = 0;  
          
        //第一個頂點時,到其他頂點的權值即為鄰接矩陣的第一行  
        for (int i = 0; i < size; i++) {  
            tempWeight[i] = matrix[0][i];  
        }  
  
        System.out.println("從頂點v0開始查詢");  
        for (int i = 1; i < size; i++) {  
            // 每次迴圈找出當前到下一個最小權值的頂點極其最小權值   
            minWeight = MAX_WEIGHT;  
            minId = 0;  
            for (int j = 1; j < size; j++) {  
                //權值為0的頂點已經遍歷過,不再計入  
                if (tempWeight[j] > 0 && tempWeight[j] < minWeight) {  
                    minWeight = tempWeight[j];  
                    minId = j;  
                }  
            }  
              
            // 找到目標頂點minId,他的權值為minweight。  
            System.out.println("找到頂點:v" + minId + " 權值為:" + minWeight);  
            sum += minWeight;  
              
              
            // 演算法核心所在:將目標頂點到各個頂點的權值與當前tempWeight陣列中的權值做比較,如果前者比後者到某個頂點的權值更小,將前者到這個頂點的權值更新入後者。  
            tempWeight[minId] = 0;  
            for (int j = 1; j < size; j++) {  
                if (tempWeight[j] != 0 && matrix[minId][j] < tempWeight[j]) {  
                    tempWeight[j] = matrix[minId][j];  
                }  
            }  
        }  
        System.out.println("最小權值總和為:" + sum);  
    }  
  
    private void createGraph(int index) {  
        size = index;  
        matrix = new int[index][index];  
        int[] v0 = { 0, 10, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 11, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT };  
        int[] v1 = { 10, 0, 18, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 16, MAX_WEIGHT, 12 };  
        int[] v2 = { MAX_WEIGHT, 18, 0, 22, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 8 };  
        int[] v3 = { MAX_WEIGHT, MAX_WEIGHT, 22, 0, 20, MAX_WEIGHT, MAX_WEIGHT, 16, 21 };  
        int[] v4 = { MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 20, 0, 26, MAX_WEIGHT, 7, MAX_WEIGHT };  
        int[] v5 = { 11, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 26, 0, 17, MAX_WEIGHT, MAX_WEIGHT };  
        int[] v6 = { MAX_WEIGHT, 16, MAX_WEIGHT, 24, MAX_WEIGHT, 17, 0, 19, MAX_WEIGHT };  
        int[] v7 = { MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 16, 7, MAX_WEIGHT, 19, 0, MAX_WEIGHT };  
        int[] v8 = { MAX_WEIGHT, 12, 8, 21, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 0 };  
        matrix[0] = v0;  
        matrix[1] = v1;  
        matrix[2] = v2;  
        matrix[3] = v3;  
        matrix[4] = v4;  
        matrix[5] = v5;  
        matrix[6] = v6;  
        matrix[7] = v7;  
        matrix[8] = v8;  
    }  
  
    public static void main(String[] args) {  
        MinSpanTree graph = new MinSpanTree();  
        graph.createGraph(9);  
        graph.prim();  
    }  
  
}  

五、克魯斯卡爾(Kruskal)演算法

判斷是否為迴路的機制沒有理解

程式碼所示圖和邊集陣列


程式碼:

public class MiniSpanTreeKruskal {  
  
    /** 鄰接矩陣 */  
    private int[][] matrix;  
    /** 表示正無窮 */  
    private int MAX_WEIGHT = Integer.MAX_VALUE;  
    /**邊集陣列*/  
    private List<Edge> edgeList = new ArrayList<Edge>();  
    /** 
     * 建立圖 
     */  
    private void createGraph(int index) {  
        matrix = new int[index][index];  
        int[] v0 = { 0, 10, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 11, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT };  
        int[] v1 = { 10, 0, 18, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 16, MAX_WEIGHT, 12 };  
        int[] v2 = { MAX_WEIGHT, 18, 0, 22, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 8 };  
        int[] v3 = { MAX_WEIGHT, MAX_WEIGHT, 22, 0, 20, MAX_WEIGHT, MAX_WEIGHT, 16, 21 };  
        int[] v4 = { MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 20, 0, 26, MAX_WEIGHT, 7, MAX_WEIGHT };  
        int[] v5 = { 11, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 26, 0, 17, MAX_WEIGHT, MAX_WEIGHT };  
        int[] v6 = { MAX_WEIGHT, 16, MAX_WEIGHT, 24, MAX_WEIGHT, 17, 0, 19, MAX_WEIGHT };  
        int[] v7 = { MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 16, 7, MAX_WEIGHT, 19, 0, MAX_WEIGHT };  
        int[] v8 = { MAX_WEIGHT, 12, 8, 21, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 0 };  
        matrix[0] = v0;  
        matrix[1] = v1;  
        matrix[2] = v2;  
        matrix[3] = v3;  
        matrix[4] = v4;  
        matrix[5] = v5;  
        matrix[6] = v6;  
        matrix[7] = v7;  
        matrix[8] = v8;  
    }  
      
    /** 
     * 建立邊集陣列,並且對他們按權值從小到大排序(順序儲存結構也可以認為是陣列吧) 
     */  
    public void createEdages() {  
  
        Edge v0 = new Edge(4, 7, 7);  
        Edge v1 = new Edge(2, 8, 8);  
        Edge v2 = new Edge(0, 1, 10);  
        Edge v3 = new Edge(0, 5, 11);  
        Edge v4 = new Edge(1, 8, 12);  
        Edge v5 = new Edge(3, 7, 16);  
        Edge v6 = new Edge(1, 6, 16);  
        Edge v7 = new Edge(5, 6, 17);  
        Edge v8 = new Edge(1, 2, 18);  
        Edge v9 = new Edge(6, 7, 19);  
        Edge v10 = new Edge(3, 4, 20);  
        Edge v11 = new Edge(3, 8, 21);  
        Edge v12 = new Edge(2, 3, 22);  
        Edge v13 = new Edge(3, 6, 24);  
        Edge v14 = new Edge(4, 5, 26);  
  
        edgeList.add(v0);  
        edgeList.add(v1);  
        edgeList.add(v2);  
        edgeList.add(v3);  
        edgeList.add(v4);  
        edgeList.add(v5);  
        edgeList.add(v6);  
        edgeList.add(v7);  
        edgeList.add(v8);  
        edgeList.add(v9);  
        edgeList.add(v10);  
        edgeList.add(v11);  
        edgeList.add(v12);  
        edgeList.add(v13);  
        edgeList.add(v14);  
    }  
  
    // 克魯斯卡爾演算法  
    public void kruskal() {  
        //建立圖和邊集陣列  
        createGraph(9);  
        //可以由圖轉出邊集陣列並按權從小到大排序,這裡為了方便觀察直接寫出來了  
        createEdages();  
          
        //定義一個陣列用來判斷邊與邊是否形成環路  
        int[] parent = new int[9];  
          
        /**權值總和*/  
        int sum = 0;  
          
        int n, m;  
          
        //遍歷邊  
        for (int i = 0; i < edgeList.size(); i++) {  
            Edge edge= edgeList.get(i);  
            n = find(parent, edge.getBegin());  
            m = find(parent, edge.getEnd());  
              
            //說明形成了環路或者兩個結點都在一棵樹上  
            //注:書上沒有講解為什麼這種機制可以保證形成環路,思考了半天,百度了也沒有什麼好的答案,研究的時間不多,就暫時就放一放吧  
            if (n != m) {  
                parent[n] = m;  
                System.out.println("(" + edge.getBegin() + "," + edge.getEnd() + ")" +edge.getWeight());  
                  
                sum += edge.getWeight();  
            }  
        }  
          
        System.out.println("權值總和為:" + sum);  
    }  
  
    public int find(int[] parent, int index) {  
        while (parent[index] > 0) {  
            index = parent[index];  
        }  
        return index;  
    }  
  
    public static void main(String[] args) {  
        MiniSpanTreeKruskal graph = new MiniSpanTreeKruskal();  
        graph.kruskal();  
    }  
  
}  
  
class Edge {  
  
    private int begin;  
    private int end;  
    private int weight;  
  
    public Edge(int begin, int end, int weight) {  
        super();  
        this.begin = begin;  
        this.end = end;  
        this.weight = weight;  
    }  
  
    public int getBegin() {  
        return begin;  
    }  
  
    public void setBegin(int begin) {  
        this.begin = begin;  
    }  
  
    public int getEnd() {  
        return end;  
    }  
  
    public void setEnd(int end) {  
        this.end = end;  
    }  
  
    public int getWeight() {  
        return weight;  
    }  
  
    public void setWeight(int weight) {  
        this.weight = weight;  
    }  
  
    @Override  
    public String toString() {  
        return "Edge [begin=" + begin + ", end=" + end + ", weight=" + weight + "]";  
    }  
}  

結果:

 

六、迪傑斯特拉(Dijkstra)演算法

基本思想

     通過Dijkstra計算圖G中的最短路徑時,需要指定起點vs(即從頂點vs開始計算)。

     此外,引進兩個集合S和U。S的作用是記錄已求出最短路徑的頂點,而U則是記錄還未求出最短路徑的頂點(以及該頂點到起點vs的距離)。

     初始時,S中只有起點vs;U中是除vs之外的頂點,並且U中頂點的路徑是"起點vs到該頂點的路徑"。然後,從U中找出路徑最短的頂點,並將其加入到S中;接著,更新U中的頂點和頂點對應的路徑。 然後,再從U中找出路徑最短的頂點,並將其加入到S中;接著,更新U中的頂點和頂點對應的路徑。 ... 重複該操作,直到遍歷完所有頂點。


操作步驟

(1) 初始時,S只包含起點vs;U包含除vs外的其他頂點,且U中頂點的距離為"起點vs到該頂點的距離"[例如,U中頂點v的距離為(vs,v)的長度,然後vs和v不相鄰,則v的距離為∞]。

(2) 從U中選出"距離最短的頂點k",並將頂點k加入到S中;同時,從U中移除頂點k。

(3) 更新U中各個頂點到起點vs的距離。之所以更新U中頂點的距離,是由於上一步中確定了k是求出最短路徑的頂點,從而可以利用k來更新其它頂點的距離;例如,(vs,v)的距離可能大於(vs,k)+(k,v)的距離。

(4) 重複步驟(2)和(3),直到遍歷完所有頂點。



程式碼示例圖:

圖一:


圖二:


程式碼:

public class ShortestPathDijkstra {  
    /** 鄰接矩陣 */  
    private int[][] matrix;  
    /** 表示正無窮 */  
    private int MAX_WEIGHT = Integer.MAX_VALUE;  
    /** 頂點集合 */  
    private String[] vertexes;  
  
    /** 
     * 建立圖2 
     */  
    private void createGraph2(int index) {  
        matrix = new int[index][index];  
        vertexes = new String[index];  
          
        int[] v0 = { 0, 1, 5, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT };  
        int[] v1 = { 1, 0, 3, 7, 5, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT };  
        int[] v2 = { 5, 3, 0, MAX_WEIGHT, 1, 7, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT };  
        int[] v3 = { MAX_WEIGHT, 7, MAX_WEIGHT, 0, 2, MAX_WEIGHT, 3, MAX_WEIGHT, MAX_WEIGHT };  
        int[] v4 = { MAX_WEIGHT, 5, 1, 2, 0, 3, 6, 9, MAX_WEIGHT };  
        int[] v5 = { MAX_WEIGHT, MAX_WEIGHT, 7, MAX_WEIGHT, 3, 0, MAX_WEIGHT, 5, MAX_WEIGHT };  
        int[] v6 = { MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 3, 6, MAX_WEIGHT, 0, 2, 7 };  
        int[] v7 = { MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 9, 5, 2, 0, 4 };  
        int[] v8 = { MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 7, 4, 0 };  
        matrix[0] = v0;  
        matrix[1] = v1;  
        matrix[2] = v2;  
        matrix[3] = v3;  
        matrix[4] = v4;  
        matrix[5] = v5;  
        matrix[6] = v6;  
        matrix[7] = v7;  
        matrix[8] = v8;  
          
        vertexes[0] = "v0";  
        vertexes[1] = "v1";  
        vertexes[2] = "v2";  
        vertexes[3] = "v3";  
        vertexes[4] = "v4";  
        vertexes[5] = "v5";  
        vertexes[6] = "v6";  
        vertexes[7] = "v7";  
        vertexes[8] = "v8";  
    }  
      
    /** 
     * 建立圖1 
     */  
    private void createGraph1(int index) {  
        matrix = new int[index][index];  
        vertexes = new String[index];  
  
        int[] v0 = { 0, 1, MAX_WEIGHT, MAX_WEIGHT, 2, MAX_WEIGHT };  
        int[] v1 = { 1, 0, 1, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT };  
        int[] v2 = { MAX_WEIGHT, 1, 0, 1, MAX_WEIGHT, MAX_WEIGHT };  
        int[] v3 = { MAX_WEIGHT, MAX_WEIGHT, 1, 0, 1, MAX_WEIGHT };  
        int[] v4 = { MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 1, 0, 1 };  
        int[] v5 = { MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 1, 1, 0 };  
  
        matrix[0] = v0;  
        matrix[1] = v1;  
        matrix[2] = v2;  
        matrix[3] = v3;  
        matrix[4] = v4;  
        matrix[5] = v5;  
  
        vertexes[0] = "A";  
        vertexes[1] = "B";  
        vertexes[2] = "C";  
        vertexes[3] = "D";  
        vertexes[4] = "E";  
        vertexes[5] = "F";  
    }  
  
    /** 
     * Dijkstra最短路徑。 
     *  
     * vs -- 起始頂點(start vertex) 即,統計圖中"頂點vs"到其它各個頂點的最短路徑。 
     */  
    public void dijkstra(int vs) {  
        // flag[i]=true表示"頂點vs"到"頂點i"的最短路徑已成功獲取  
        boolean[] flag = new boolean[vertexes.length];  
        // U則是記錄還未求出最短路徑的頂點(以及該頂點到起點s的距離),與 flag配合使用,flag[i] == true 表示U中i頂點已被移除  
        int[] U = new int[vertexes.length];  
        // 前驅頂點陣列,即,prev[i]的值是"頂點vs"到"頂點i"的最短路徑所經歷的全部頂點中,位於"頂點i"之前的那個頂點。  
        int[] prev = new int[vertexes.length];  
        // S的作用是記錄已求出最短路徑的頂點  
        String[] S = new String[vertexes.length];  
  
        // 步驟一:初始時,S中只有起點vs;U中是除vs之外的頂點,並且U中頂點的路徑是"起點vs到該頂點的路徑"。  
        for (int i = 0; i < vertexes.length; i++) {  
            flag[i] = false; // 頂點i的最短路徑還沒獲取到。  
            U[i] = matrix[vs][i]; // 頂點i與頂點vs的初始距離為"頂點vs"到"頂點i"的權。也就是鄰接矩陣vs行的資料。  
              
            prev[i] = 0; //頂點i的前驅頂點為0  
        }  
  
        // 將vs從U中“移除”(U與flag配合使用)  
        flag[vs] = true;  
        U[vs] = 0;  
        // 將vs頂點加入S  
        S[0] = vertexes[vs];  
        // 步驟一結束  
          
        //步驟四:重複步驟二三,直到遍歷完所有頂點。  
        // 遍歷vertexes.length-1次;每次找出一個頂點的最短路徑。  
        int k = 0;  
        for (int i = 1; i < vertexes.length; i++) {  
            // 步驟二:從U中找出路徑最短的頂點,並將其加入到S中(如果vs頂點到x頂點還有更短的路徑的話,那麼  
            // 必然會有一個y頂點到vs頂點的路徑比前者更短且沒有加入S中  
            // 所以,U中路徑最短頂點的路徑就是該頂點的最短路徑)  
            // 即,在未獲取最短路徑的頂點中,找到離vs最近的頂點(k)。  
            int min = MAX_WEIGHT;  
            for (int j = 0; j < vertexes.length; j++) {  
                if (flag[j] == false && U[j] < min) {  
                    min = U[j];  
                    k = j;  
                }  
            }  
              
            //將k放入S中  
            S[i] = vertexes[k];  
              
            //步驟二結束  
              
              
            //步驟三:更新U中的頂點和頂點對應的路徑  
            //標記"頂點k"為已經獲取到最短路徑(更新U中的頂點,即將k頂點對應的flag標記為true)  
            flag[k] = true;  
              
            //修正當前最短路徑和前驅頂點(更新U中剩餘頂點對應的路徑)  
            //即,當已經"頂點k的最短路徑"之後,更新"未獲取最短路徑的頂點的最短路徑和前驅頂點"。  
            for (int j = 0; j < vertexes.length; j++) {  
                //以k頂點所在位置連線其他頂點,判斷其他頂點經過最短路徑頂點k到達vs頂點是否小於目前的最短路徑,是,更新入U,不是,不做處理  
                int tmp = (matrix[k][j] == MAX_WEIGHT ? MAX_WEIGHT : (min + matrix[k][j]));  
                if (flag[j] == false && (tmp < U[j])) {  
                    U[j] = tmp;  
                    //更新 j頂點的最短路徑前驅頂點為k  
                    prev[j] = k;  
                }  
            }  
            //步驟三結束  
        }  
        //步驟四結束  
  
        // 列印dijkstra最短路徑的結果  
        System.out.println("起始頂點:" + vertexes[vs]);  
        for (int i = 0; i < vertexes.length; i++) {  
            System.out.print("最短路徑(" + vertexes[vs] + "," + vertexes[i] + "):" + U[i] + "  ");  
              
            List<String> path = new ArrayList<>();  
            int j = i;  
            while (true) {  
                path.add(vertexes[j]);  
                  
                if (j == 0)  
                    break;  
                  
                j = prev[j];  
            }  
              
            for (int x = path.size()-1; x >= 0; x--) {  
                if (x == 0) {  
                    System.out.println(path.get(x));  
                } else {  
                    System.out.print(path.get(x) + "->");  
                }  
            }  
              
        }  
          
        System.out.println("頂點放入S中的順序:");  
        for (int i = 0; i< vertexes.length; i++) {  
              
            System.out.print(S[i]);  
              
            if (i != vertexes.length-1)   
                System.out.print("-->");  
        }  
              
    }  
  
    public static void main(String[] args) {  
        ShortestPathDijkstra dij = new ShortestPathDijkstra();  
        dij.createGraph1(6);  
//        dij.createGraph2(9);  
        dij.dijkstra(0);  
    }  
  
}  

執行結果:

圖一


圖二

七、弗洛伊德(Floyd)演算法

程式碼所示圖:

圖1:


圖2:


程式碼:

public class ShortestPathFloyd {  
    /** 鄰接矩陣 */  
    private int[][] matrix;  
    /** 表示正無窮 */  
    private int MAX_WEIGHT = Integer.MAX_VALUE;  
    /**路徑矩陣*/  
    private int[][] pathMatirx;  
    /**前驅表*/  
    private int[][] preTable;  
    /** 
     * 建立圖2 
     */  
    private void createGraph2(int index) {  
        matrix = new int[index][index];  
  
        int[] v0 = { 0, 1, 5, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT };  
        int[] v1 = { 1, 0, 3, 7, 5, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT };  
        int[] v2 = { 5, 3, 0, MAX_WEIGHT, 1, 7, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT };  
        int[] v3 = { MAX_WEIGHT, 7, MAX_WEIGHT, 0, 2, MAX_WEIGHT, 3, MAX_WEIGHT, MAX_WEIGHT };  
        int[] v4 = { MAX_WEIGHT, 5, 1, 2, 0, 3, 6, 9, MAX_WEIGHT };  
        int[] v5 = { MAX_WEIGHT, MAX_WEIGHT, 7, MAX_WEIGHT, 3, 0, MAX_WEIGHT, 5, MAX_WEIGHT };  
        int[] v6 = { MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 3, 6, MAX_WEIGHT, 0, 2, 7 };  
        int[] v7 = { MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 9, 5, 2, 0, 4 };  
        int[] v8 = { MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 7, 4, 0 };  
        matrix[0] = v0;  
        matrix[1] = v1;  
        matrix[2] = v2;  
        matrix[3] = v3;  
        matrix[4] = v4;  
        matrix[5] = v5;  
        matrix[6] = v6;  
        matrix[7] = v7;  
        matrix[8] = v8;  
  
    }  
  
    /** 
     * 建立圖1 
     */  
    private void createGraph1(int index) {  
        matrix = new int[index][index];  
  
        int[] v0 = { 0, 1, MAX_WEIGHT, MAX_WEIGHT, 2, MAX_WEIGHT };  
        int[] v1 = { 1, 0, 1, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT };  
        int[] v2 = { MAX_WEIGHT, 1, 0, 1, MAX_WEIGHT, MAX_WEIGHT };  
        int[] v3 = { MAX_WEIGHT, MAX_WEIGHT, 1, 0, 1, MAX_WEIGHT };  
        int[] v4 = { MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 1, 0, 1 };  
        int[] v5 = { MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 1, 1, 0 };  
  
        matrix[0] = v0;  
        matrix[1] = v1;  
        matrix[2] = v2;  
        matrix[3] = v3;  
        matrix[4] = v4;  
        matrix[5] = v5;  
  
    }       
    public void floyd(){  
        //路徑矩陣(D),表示頂點到頂點的最短路徑權值之和的矩陣,初始時,就是圖的鄰接矩陣。  
        pathMatirx = new int[matrix.length][matrix.length];  
        //前驅表(P),P[m][n] 的值為 m到n的最短路徑的前驅頂點,如果是直連,值為n。也就是初始值  
        preTable = new int[matrix.length][matrix.length];  
          
        //初始化D,P  
        for (int i = 0; i < matrix.length; i++) {  
            for (int j = 0; j < matrix.length; j++) {  
                pathMatirx[i][j] = matrix[i][j];  
                preTable[i][j] = j;  
            }  
        }  
          
        //迴圈 中間經過頂點  
        for (int k = 0; k < matrix.length; k++) {  
            //迴圈所有路徑  
            for (int m = 0; m < matrix.length; m++) {  
                  
                for (int n = 0; n < matrix.length; n++) {  
                      
                    int mn = pathMatirx[m][n];  
                    int mk = pathMatirx[m][k];  
                    int kn = pathMatirx[k][n];  
                    int addedPath = (mk == MAX_WEIGHT || kn == MAX_WEIGHT)? MAX_WEIGHT : mk + kn;  
                      
                    if (mn > addedPath) {  
                        //如果經過k頂點路徑比原兩點路徑更短,將兩點間權值設為更小的一個  
                        pathMatirx[m][n] = addedPath;  
                        //前驅設定為經過下標為k的頂點  
                        preTable[m][n] = preTable[m][k];  
                    }        
                }  
            }  
        }  
    }  
      
    /** 
     * 列印 所有最短路徑 
     */  
    public void print() {  
        for (int m = 0; m < matrix.length; m++) {  
            for (int n = m + 1; n < matrix.length; n++) {  
                  
                int k = preTable[m][n];  
                System.out.print("(" + m + "," + n + ")" + pathMatirx[m][n] + ":  ");  
                System.out.print(m);  
                while (k != n) {  
                    System.out.print("->" + k);  
                    k = preTable[k][n];  
                }         
                System.out.println("->" + n);  
            }  
            System.out.println();  
        }  
    }  
    public static void main(String[] args) {  
        ShortestPathFloyd floyd = new ShortestPathFloyd();  
        floyd.createGraph2(9);  
//        floyd.createGraph1(6);  
        floyd.floyd();  
        floyd.print();  
    } 

結果:

圖1:

圖2:

(0,1)1:  0->1  
(0,2)4:  0->1->2  
(0,3)7:  0->1->2->4->3  
(0,4)5:  0->1->2->4  
(0,5)8:  0->1->2->4->5  
(0,6)10:  0->1->2->4->3->6  
(0,7)12:  0->1->2->4->3->6->7  
(0,8)16:  0->1->2->4->3->6->7->8  
  
(1,2)3:  1->2  
(1,3)6:  1->2->4->3  
(1,4)4:  1->2->4  
(1,5)7:  1->2->4->5  
(1,6)9:  1->2->4->3->6  
(1,7)11:  1->2->4->3->6->7  
(1,8)15:  1->2->4->3->6->7->8  
  
(2,3)3:  2->4->3  
(2,4)1:  2->4  
(2,5)4:  2->4->5  
(2,6)6:  2->4->3->6  
(2,7)8:  2->4->3->6->7  
(2,8)12:  2->4->3->6->7->8  
  
(3,4)2:  3->4  
(3,5)5:  3->4->5  
(3,6)3:  3->6  
(3,7)5:  3->6->7  
(3,8)9:  3->6->7->8  
  
(4,5)3:  4->5  
(4,6)5:  4->3->6  
(4,7)7:  4->3->6->7  
(4,8)11:  4->3->6->7->8  
  
(5,6)7:  5->7->6  
(5,7)5:  5->7  
(5,8)9:  5->7->8  
  
(6,7)2:  6->7  
(6,8)6:  6->7->8  
  
(7,8)4:  7->8  


相關文章