常用的10種算法


一、二分查找算法(非遞歸)

●二分查找法只適用於從有序的數列中進行查找(比如數字和字母等),將數列排序后再進行查找
●二分查找法的運行時間為對數時間O(log2 n),即查找到需要的目標位置最多只需要log2 n步,假設從[0,99]的隊列(100個數,即n=100)中 尋到目標數30,則需要查找步數為log2 100 ,即最多需要查找7次(2^6 < 100 <2^7)

package com.xudong.Algorithm;

public class BinarySearchNoRecursion {
    public static void main(String[] args) {
        int[] arr = {1,3,8,10,11,67,100};
        int index = binarySearch(arr,67);
        System.out.println("index=" + index);
    }

    /**
     * @param arr 要查找的升序的數組
     * @param target 目標數
     * @return 找到返回目標數字索引,沒找到返回-1
     */
    public static int binarySearch(int[] arr,int target){
        int left = 0;
        int right = arr.length - 1;
        while (left <= right){
            int mid = (left + right) / 2;
            if (arr[mid] == target){
                return mid;
            }else if (arr[mid] > target){//向左查找
                right = mid - 1;
            }else {//向右查找
                left = mid + 1;
            }
        }
        return -1;
    }
}

分治算法

●分治法是一種很重要的算法。字面上的解釋是“分而治之”,就是把一個復雜的問題分成兩個或更多的相同或相似的子問題,再把子問題分成更小的子問題...到最后子問題可以簡單的直接求解,原問題的解即子問題的解的合並。這個技巧是很多高效算法的基礎,如排序算法(快速排序,歸並排序),傅立葉變換(快速傅立葉變換)...
●分治算法可 以求解的一些經典問題

二分搜索、大整數乘法、棋盤覆蓋、合並排序、快速排序、線性時間選擇、最接近點對問題、循環賽日程表、漢諾塔

1.分治算法的基本步驟

分解:將原問題分解為若千個規模較小,相互獨立,與原問題形式相同的子問題
解決:若子問題規模較小而容易被解決則直接解,否則遞歸地解各個子問題
合並:將各個子問題的解合並為原問題的解。

2.分治算法設計模式


其中|P|表示問題P的規模; n0為一閾值,表示當問題P的規模不超過n0時,問題己容易直接解出,不必再繼續分解。ADHOC(P)是該分治法中的基本子算法,用於直接解小規模的問題P。因此,當P的規模不超過n0時直接用算法ADHOC(P)求解。算法MERE(y1,y2,...,yk)是該分治法中的合並子算法,用於將P的子問題【P1 ,P2,...pk】的相應的解【y1,y2,...,yk】合並為P的解。

分治算法-漢諾塔

漢諾塔:大梵天創造世界的時候做了三根金剛石柱子,在一根柱子上從下往上按照大小順序摞着64片黃金圓盤。大梵天命令婆羅門把圓盤從下面開始按大小順序重新擺放在另一根柱子上。並且規定,在小圓盤上不能放大圓盤,在三根柱子之間一次只能移動一個圓盤。

漢諾塔游戲思路分析:

●如果是有一個盤,A->C
●如果我們有n>=2情況,我們總是可以看做是兩個盤1.最下邊的盤2..上面的盤

➢先把最上面的盤A->B
➢再把最下邊的盤A->C
➢最后把B塔的所有盤從B->C

package com.xudong.Algorithm;

public class HanoiTower {
    public static void main(String[] args) {
        hanoiTower(5,'A','B','C');
    }

    //分治算法完成漢諾塔移動方法
    public static void hanoiTower(int num,char a,char b,char c){
        //如果只有一個盤
        if (num == 1){
            System.out.println("第1個盤從 " + a + "->" + c);
        }else {
            //如果我們有n>=2情況,我們總是可以看做是兩個盤1.最下邊的盤2..上面的盤
            //先把最上面的盤A->B,移動過程中使用到c
            hanoiTower(num - 1,a,c,b);
            //再把最下邊的盤A->C
            System.out.println("第" + num + "個盤從 " + a + "->" + c);
            //最后把B塔的所有盤從B->C,移動過程中使用到a
            hanoiTower(num - 1,b,a,c);
        }
    }

}

動態規划算法

動態規划(Dynamic Programming)算法的核心思想是:將大問題划分為小問題進行解決,從而一步步獲取最優解的處理算法
●動態規划算法與分治算法類似,其基本思想也是將待求解問題分解成若干個子問題,先求解子問題,然后從這些子問題的解得到原問題的解。
●與分治法不同的是,適合於用動態規划求解的問題,經分解得到子問題往往不是互相獨立的。( 即下一個子階段的求解是建立在上一個子階段的解的基礎上,進行進一步的求解)
●動態規划可以通過填表的方式來逐步推進,得到最優解.

背包問題

●背包問題主要是指一個給定容量的背包、若千具有一定價值和重量的物品,如何選擇物品放入背包使物品的價值最大。其中又分01背包和完全背包(完全背包指的是:每種物品都有無限件可用)
●這里的問題屬於01背包,即每個物品最多放一個。而無限背包可以轉化為01背包。
有一個背包,容量為4磅,現有如下物品

➢要求達到的目標為裝入的背包的總價值最大,並且重量不超出
➢要求裝入的物品不能重復

思路分析

●算法的主要思想,利用動態規划來解決。每次遍歷到的第i個物品,根據w[i]和v[i]來確定是否需要將該物品放入背包中。即對於給定的n個物品,設v[i]、w[i]分 別為第i個物品的價值和重量,C為背包的容量。再令v[i][i]表示在前i個物品中能夠裝入容量為j的背包中的最大價值。則我們有下面的結果:

➢v[i][0]=v[0][j]=0;

表示填入表第一行和第一列是0

當w[i]>j時: v[i][j]=v[i-1][i]

當准備加入新增的物品的容量大於當前背包的容量時,就直接使用上一個單元格的裝入策略

當j>=w[i]時:v[i][j]=max{v[i-1][j],v[i]+v[i-1][j-W[i]]}

當准備加入的新增的商品的容量小於等於當前背包的容量時,裝入的方式:
v[i-1][j]:就是上一個單元格的裝入的最大值
v[i]:表示當前商品的價值
v[i-1][j-W[i]]:裝入i-1 商品時,到剩余空間j-w[i]的最大值

package com.xudong.Algorithm;

public class KnapsackProblem {
    public static void main(String[] args) {
        int[] w = {1,4,3};//物品的重量
        int[] val = {1500,3000,2000};//物品的價值
        int m = 4;//背包的容量
        int n = val.length;//物品的個數

        //創建二維數組
        int[][] v = new int[n+1][m+1];
        //記錄放入商品的情況
        int[][] path = new int[n+1][m+1];

        //初始化第一行和第一列
        for (int i = 0; i < v.length; i++) {
            v[i][0] = 0;
        }
        for (int i = 0; i < v[0].length; i++) {
            v[0][i] = 0;
        }

        //動態規划處理
        for (int i = 1; i < v.length; i++) {
            for (int j = 1; j < v[0].length; j++) {
                if (w[i -1] > j){
                    v[i][j] = v[i-1][j];
                }else {
                    //v[i][j] = Math.max(v[i-1][j],val[i-1] + v[i-1][j-w[i-1]]);
                    if (v[i-1][j] < val[i-1] + v[i-1][j-w[i-1]]){
                        v[i][j] = val[i-1] + v[i-1][j-w[i-1]];
                        path[i][j] = 1;
                    }else {
                        v[i][j] = v[i-1][j];
                    }
                }
            }
        }

        //輸出v
        for (int i = 0; i < v.length; i++) {
            for (int j = 0; j < v[i].length; j++) {
                System.out.print(v[i][j] + " ");
            }
            System.out.println();
        }
        System.out.println("=================");

        //遍歷path
        int i = path.length - 1;//行的最大下標
        int j = path[0].length - 1;//列的最大下標
        while (i > 0 && j > 0){
            if (path[i][j] == 1){
                System.out.printf("第%d個物品放入背包\n",i);
                j -= w[i -1];
            }
            i--;
        }
    }
}

KMP算法

●KMP是一個解決模式串在文本串是否出現過,如果出現過,最早出現的位置的經典算法
●Knuth-Morris-Pratt字符串查找算法,簡稱為“KMP算法” ,常用於在一個文本串S內查找一個模式串P的出現位置,這個算法由Donald Knuth、Vaughan Pratt、James H. Morris三人於1977年聯合發表,故取這3人的姓氏命名此算法.
●KMP方法算法就利用之前判斷過信息,通過一個next數組, 保存模式串中前后最長公共子序列的長度,每次回溯時,通過next數組找到,前面匹配過的位置,省去了大量的計算時間

package com.xudong.Algorithm;

import java.util.Arrays;

public class KMPAlgorithm {
    public static void main(String[] args) {
        String str1 = "BBC ABCDAB ABCDABCDABDE";
        String str2 = "ABCDABD";

        int[] next = kmpNext(str2);
        System.out.println("next=" + Arrays.toString(next));

        int index = kmpSearch(str1,str2,next);
        System.out.println("index =" + index);
    }

    //KMP搜索算法
    public static int kmpSearch(String str1,String str2,int[] next){
        //遍歷源字符串
        for (int i = 0,j = 0; i < str1.length(); i++) {
            while (j > 0 && str1.charAt(i) != str2.charAt(j)){
                j = next[j - 1];
            }
            if (str1.charAt(i) == str2.charAt(j)){
                j++;
            }
            if (j == str2.length()){
                return i - j + 1;
            }
        }
        return -1;
    }

    //獲取到一個字符串(子串)的部分匹配值表
    public static int[] kmpNext(String dest){
        //創建next數組保存部分匹配值
        int[] next = new int[dest.length()];
        next[0] = 0;//如果字符串長度為1,部分匹配值就是0
        for (int i = 1, j = 0; i < dest.length(); i++) {
            //KMP算法核心
            while (j > 0 && dest.charAt(i) != dest.charAt(j)){
                j = next[j - 1];//回溯
            }

            if (dest.charAt(i) == dest.charAt(j)){
                j++;
            }
            next[i] = j;
        }
        return next;
    }
}

貪心算法

●貪婪算法(貪心算法)是指在對問題進行求解時,在每一步選擇中都采取最好或者最優(即最有利)的選擇,從而希望能夠導致結果是最好或者最優的算法
●貪婪算法所得到的結果不一定是最優的結果(有時候會是最優解),但是都是相對近似(接近)最優解的結果

貪心算法應用

●假設存在如下表的需要付費的廣播台,以及廣播台信號可以覆蓋的地區。如何選擇最少的廣播台,讓所有的地區都可以接收到信號

思路分析:

●目前並沒有算法可以快速計算得到准備的值,使用貪婪算法,則可以得到非常接近的解,並且效率高。選擇策略上,因為需要覆蓋全部地區的最小集合:

➢遍歷所有的廣播電台,找到一個覆蓋了最多未覆蓋的地區的電台(此電台可能包含一些已覆蓋的地區,但沒有關系)
➢將這個電台加入到一個集合中(比如ArrayList),想辦法把該電台覆蓋的地區在下次比較時去掉。
➢重復第1步直到覆蓋了全部的地區

package com.xudong.Algorithm;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;

public class GreedyAlgorithm {
    public static void main(String[] args) {
        //創建廣播電台,放入Map
        HashMap<String, HashSet<String>> broadcasts = new HashMap<>();
        //將各個電台放入到broadcasts
        HashSet<String> hashSet1 = new HashSet<>();
        hashSet1.add("北京");
        hashSet1.add("上海");
        hashSet1.add("天津");

        HashSet<String> hashSet2 = new HashSet<>();
        hashSet2.add("廣州");
        hashSet2.add("北京");
        hashSet2.add("深圳");

        HashSet<String> hashSet3 = new HashSet<>();
        hashSet3.add("成都");
        hashSet3.add("上海");
        hashSet3.add("杭州");

        HashSet<String> hashSet4 = new HashSet<>();
        hashSet4.add("上海");
        hashSet4.add("天津");

        HashSet<String> hashSet5 = new HashSet<>();
        hashSet5.add("杭州");
        hashSet5.add("大連");

        //加入到Map
        broadcasts.put("K1",hashSet1);
        broadcasts.put("K2",hashSet2);
        broadcasts.put("K3",hashSet3);
        broadcasts.put("K4",hashSet4);
        broadcasts.put("K5",hashSet5);

        //存放所有的地區
        HashSet<String> allAreas = new HashSet<>();
        allAreas.add("北京");
        allAreas.add("上海");
        allAreas.add("天津");
        allAreas.add("廣州");
        allAreas.add("深圳");
        allAreas.add("成都");
        allAreas.add("杭州");
        allAreas.add("大連");

        //存放選擇的電台集合
        ArrayList<String> selects = new ArrayList<>();

        //保存遍歷過程中電台覆蓋的地區和當前沒有覆蓋地區的交集
        HashSet<String> tempSet = new HashSet<>();

        //定義maxKey,保存再一次遍歷中,能夠覆蓋最大未覆蓋地區對應電台的key
        String maxKey = null;
        while (allAreas.size() != 0){
            //每進行一次循環
            maxKey = null;
            //遍歷broadcasts,取出對應的key
            for(String key : broadcasts.keySet()){
                //每次循環清空tempSet
                tempSet.clear();
                //當前這個key能夠覆蓋的地區
                HashSet<String> areas = broadcasts.get(key);
                tempSet.addAll(areas);
                //求出tempSet和allAreas集合的交集,並賦給tempSet
                tempSet.retainAll(allAreas);
                //如果當前集合包含的未覆蓋地區的數量大於maxKey指向的集合地區還多,就需要重置maxKey
                if (tempSet.size() > 0 && (maxKey == null || tempSet.size() > broadcasts.get(maxKey).size())){
                    maxKey = key;
                }
            }
            //此時將maxKey加入selects
            if (maxKey != null){
                selects.add(maxKey);
                //將maxKey指向的廣播電台覆蓋的地區從allAreas去掉
                allAreas.removeAll(broadcasts.get(maxKey));
            }
        }
        System.out.println("得到的選擇結果:" + selects);
    }
}

普里姆算法

最小生成樹MST

●給定一個帶權的無向連通圖,選取一顆生成樹,使樹上所有邊上權的總和最小,這就叫最小生成樹,N個頂點,一定有N-1條邊

普利姆算法介紹

●普利姆(Prim)算法求最小生成樹,也就是在包含n個頂點的連通圖中,找出只有(n-1)條邊包含所有n個頂點的連通子圖,也就是所謂的極小連通子圖
普利姆的算法如下:

➢① 設G=(V,E)是連通網,T=(U,D)是最小生成樹,V,U是頂點集合,E,D是邊的集合
➢② 若從頂點u開始構造最小生成樹,則從集合V中取出頂點u放入集合U中,標記項點v的visited[u]=1
➢③ 若集合U中頂點ui與集合V-U中的頂點vj之間存在邊,則尋找這些邊中權值最小的邊,但不能構成回路,將頂點vj加入集合U中,將邊(ui,vj) 加入集合D中,標記visited[vj]=1
➢④ 重復步驟②,直到U與V相等,即所有頂點都被標記為訪問過,此時D中有n-1條邊

package com.xudong.Algorithm;

import java.util.Arrays;

public class PrimAlgorithm {
    public static void main(String[] args) {
        char[] data = new char[]{'A','B','C','D','E','F','G'};
        int vertexes = data.length;
        //鄰接矩陣關系使用二維數組表示,使用變量保存大數,表示兩點間不聯通
        int m = 10000;
        int[][] weight = new int[][]{
                {m,5,7,m,m,m,2},
                {5,m,m,9,m,m,3},
                {7,m,m,m,8,m,m},
                {m,9,m,m,m,4,m},
                {m,m,8,m,m,5,4},
                {m,m,m,4,5,m,6},
                {2,3,m,m,4,6,m}
        };
        //創建MGraph對象
        MGraph graph = new MGraph(vertexes);
        //創建MinTree對象
        MinTree minTree = new MinTree();
        minTree.createGraph(graph,vertexes,data,weight);
        //輸出
        minTree.showGraph(graph);

        minTree.prim(graph,0);
    }
}

//創建最小生成樹
class MinTree{
    /**創建圖的鄰接矩陣
     * @param graph 圖對象
     * @param vertexes 圖對應頂點的個數
     * @param data 圖的各個頂點的值
     * @param weight 圖的鄰接矩陣
     */
    public void createGraph(MGraph graph,int vertexes,char data[],int[][] weight){
        int i,j;
        for (i = 0; i < vertexes; i++) {
            graph.data[i] = data[i];
            for (j = 0; j < vertexes; j++) {
                graph.weight[i][j] = weight[i][j];
            }
        }
    }

    //顯示圖的鄰接矩陣
    public void showGraph(MGraph graph){
        for (int[] link : graph.weight){
            System.out.println(Arrays.toString(link));
        }
    }

    //編寫prim算法,得到最小生成樹,v是起點
    public void prim(MGraph graph,int v){
        //標記頂點是否被訪問過,默認是0,表示沒有訪問過
        int visited[] = new int[graph.vertexes];
        //把當前節點標記為已訪問
        visited[v] = 1;
        //h1和h2記錄兩個頂點的下標
        int h1 = -1;
        int h2 = -1;
        int minWeight = 10000;
        for (int k = 1; k < graph.vertexes; k++) {
            //確定每一次生成的子圖和哪個節點的距離最近
            for (int i = 0; i < graph.vertexes; i++) {//i節點表示被訪問過的節點
                for (int j = 0; j < graph.vertexes; j++) {//j節點表示還沒有訪問過的節點
                    if (visited[i] == 1 && visited[j] == 0 && graph.weight[i][j] < minWeight){
                        //找到已經訪問過的節點和未訪問過的節點間的權值最小的邊
                        minWeight = graph.weight[i][j];
                        h1 = i;
                        h2 = j;
                    }
                }
            }
            //找到最小的邊
            System.out.println("邊<" + graph.data[h1] + "," + graph.data[h2] + "> 權值:" + minWeight);
            //將當前找到的節點標記為已經訪問
            visited[h2] = 1;
            //重新設置minWeight
            minWeight = 10000;
        }
    }

}

class MGraph{
    int vertexes;//表示圖的節點個數
    char[] data;//存放節點數據
    int[][] weight;//存放邊,即鄰接矩陣

    public MGraph(int vertexes) {
        this.vertexes = vertexes;
        data = new char[vertexes];
        weight = new int[vertexes][vertexes];
    }
}

克魯斯卡爾算法

●克魯斯卡爾(Kruskal)算法,是用來求加權連通圖的最小生成樹的算法。
基本思想:按照權值從小到大的順序選擇n-1條邊,並保證這n-1條邊不構成回路
具體做法:首先構造一個只含n個頂點的森林,然后依權值從小到大從連通網中選擇邊加入到森林中並使森林中不產生回路,直至森林變成一棵樹為止

package com.xudong.Algorithm;

import java.util.Arrays;

public class KruskalCase {
    private int edgeNum;//邊的個數
    private char[] vertexes;//頂點數組
    private int[][] matrix;//鄰接矩陣
    //使用INF表示兩點不能連桶
    private static final int INF = Integer.MAX_VALUE;

    public static void main(String[] args) {
        char[] vertexes = {'A','B','C','D','E','F','G'};
        int[][] matrix = {
                {0,12,INF,INF,INF,16,14},
                {12,0,10,INF,INF,7,INF},
                {INF,10,0,3,5,6,INF},
                {INF,INF,3,0,4,INF,INF},
                {INF,INF,5,4,0,2,8},
                {16,7,6,INF,2,0,9},
                {14,INF,INF,INF,8,9,0}
        };
        //創建KruskalCase實例
        KruskalCase kruskalCase = new KruskalCase(vertexes, matrix);
        //輸出
        kruskalCase.print();

        kruskalCase.kruskal();
    }

    //構造器
    public KruskalCase(char[] vertexes,int[][] matrix){
        //初始化頂點和邊的個數
        int vlen = vertexes.length;
        //用復制拷貝的方式初始化頂點
        this.vertexes = new char[vlen];
        for (int i = 0; i < vertexes.length; i++) {
            this.vertexes[i] = vertexes[i];
        }
        //用復制拷貝的方式初始化邊
        this.matrix = new int[vlen][vlen];
        for (int i = 0; i < vlen; i++) {
            for (int j = 0; j < vlen; j++) {
                this.matrix[i][j] = matrix[i][j];
            }
        }
        //統計邊的條數
        for (int i = 0; i < vlen; i++) {
            for (int j = i+1; j < vlen; j++) {
                if (this.matrix[i][j] != INF){
                    edgeNum++;
                }
            }
        }
    }

    public void kruskal(){
        int index = 0;//表示結果最后的索引
        int[] ends = new int[edgeNum];//保存已有的最小生成樹中每個頂點在生成樹中的終點
        //保存最后的最小生成樹
        EData[] rets = new EData[edgeNum];
        //獲取途中所有邊的集合
        EData[] edges = getEdges();
        System.out.println("圖的邊的集合:" + Arrays.toString(edges) + "共" + edges.length + "條");
        //按照邊的權值大小進行排序
        sortEdges(edges);
        //遍歷edges數組,將邊添加到最小生成樹中時,判斷准備加入的邊是否形成了回路
        for (int i = 0; i < edgeNum; i++) {
            //獲取第i條邊的第1個頂點
            int p1 = getPosition(edges[i].start);
            //獲取第i條邊的第2個頂點
            int p2 = getPosition(edges[i].end);
            //判斷p1,p2頂點在已有最小生成樹中的終點
            int m = getEnd(ends,p1);
            int n = getEnd(ends,p2);
            //是否構成回路
            if (m != n){//沒有構成回路
                ends[m] = n;//設置m在已有最小生成樹中的終點
                rets[index++] = edges[i];
            }
        }
        //統計並打印最小生成樹
        System.out.print("最小生成樹為:");
        for (int i = 0; i < index; i++) {
            System.out.print(rets[i]);
        }
    }

    //輸出鄰接矩陣
    public void print(){
        System.out.println("鄰接矩陣為:");
        for (int i = 0; i < vertexes.length; i++) {
            for (int j = 0; j < vertexes.length; j++) {
                System.out.printf("%12d",matrix[i][j]);
            }
            System.out.println();
        }
    }

    //對邊進行冒泡排序處理
    private void sortEdges(EData[] edges){
        for (int i = 0; i < edges.length - 1; i++) {
            for (int j = 0; j < edges.length - 1 - i; j++) {
                if (edges[j].weight > edges[j+1].weight){
                    EData tmp = edges[j];
                    edges[j] = edges[j+1];
                    edges[j+1] = tmp;
                }
            }
        }
    }

    /**
     * @param ch 頂點的值
     * @return 返回頂點對應的下標
     */
    private int getPosition(char ch){
        for (int i = 0; i < vertexes.length; i++) {
            if (vertexes[i] == ch){
                return i;
            }
        }
        return -1;
    }

    private EData[] getEdges(){
        int index = 0;
        EData[] edges = new EData[edgeNum];
        for (int i = 0; i < vertexes.length; i++) {
            for (int j = i + 1; j < vertexes.length; j++) {
                if (matrix[i][j] != INF){
                    edges[index++] = new EData(vertexes[i],vertexes[j],matrix[i][j]);
                }
            }
        }
        return edges;
    }

    /**獲取下標為i的頂點的終點,用於后面判斷兩個頂點的終點是否相同
     * @param ends ends數組記錄了各個頂點對應的終點。
     * @param i 傳入頂點對應的下標
     * @return 下標為i的這個頂點對應的終點的下標
     */
    private int getEnd(int[] ends,int i){
        while (ends[i] != 0){
            i = ends[i];
        }
        return i;
    }
}

//創建一個類EData,他的對象實例就是一條邊
class EData{
    char start;//邊的一個點
    char end;//邊的另外一個點
    int weight;//邊的權值

    public EData(char start,char end,int weight){
        this.start = start;
        this.end = end;
        this.weight = weight;
    }

    @Override
    public String toString() {
        return "EData [<" + start + ", " + end + "> = " + weight + "] ";
    }
}

迪傑斯特拉算法

●迪傑斯特拉(Dijkstra)算法是典型最短路徑算法,用於計算一個節點到其他節點的最短路徑。它的主要特點是以起始點為中心向外層層擴展(廣度優先搜索思想),直到擴展到終點為止。

迪傑斯特拉(Dijkstra)算法過程

●設置出發頂點為v,頂點集合V{v1,2v.,v..},v到V中各頂點的距離構成距離集合Dis, Dis{d1,d2,di...},Dis集合記錄着v到圖中各頂點的距離(到自身可以看作0,v到vi距離對應為di)

1)從Dis中選 擇值最小的di並移出Dis集合,同時移出V集合中對應的頂點vi,此時的v到vi即為最短路徑

  1. 更新Dis集合,更新規則為:比較v到V集合中頂點的距離值,與v通過vi到V集合中頂點的距離值,保留值較小的一個(同時也應該更新頂點的前驅節點為vi,表明是通過vi到達的)
  2. 重復執行兩步驟,直到最短路徑頂點為目標頂點即可結束
package com.xudong.Algorithm;

import java.util.Arrays;

public class DijkstraAlgorithm {
    public static void main(String[] args) {
        char[] vertex = {'A','B','C','D','E','F','G'};
        int[][] matrix = new int[vertex.length][vertex.length];
        final int N = 65535;//表示不可連接
        matrix[0] = new int[]{N,5,7,N,N,N,2};
        matrix[1] = new int[]{5,N,N,9,N,N,2};
        matrix[2] = new int[]{7,N,N,N,8,N,N};
        matrix[3] = new int[]{N,9,N,N,N,4,N};
        matrix[4] = new int[]{N,N,8,N,N,5,4};
        matrix[5] = new int[]{N,N,N,4,5,N,6};
        matrix[6] = new int[]{2,3,N,N,4,6,N};
        //創建Graph對象
        Graph graph = new Graph(vertex, matrix);

        graph.showGraph();

        graph.dsj(6);
        graph.showDijkstra();
    }
}

class Graph{
    private char[] vertex;//頂點數組
    private int[][] matrix;//鄰接矩陣
    private VisitedVertex vv;//已經訪問頂點的集合

    //構造器
    public Graph(char[] vertex,int[][] matrix){
        this.vertex = vertex;
        this.matrix = matrix;
    }

    //顯示圖
    public void showGraph(){
        for (int[] link : matrix){
            System.out.println(Arrays.toString(link));
        }
    }

    //顯示結果
    public void showDijkstra(){
        vv.show();
    }

    /**迪傑斯特拉算法實現
     * @param index 出發頂點對應的下標
     */
    public void dsj(int index){
        vv = new VisitedVertex(vertex.length, index);
        update(index);
        for (int j = 1; j < vertex.length; j++) {
            index = vv.updateArr();
            update(index);
        }
    }

    //更新index下標頂點到周圍頂點的距離和周圍頂點的前驅頂點
    private void update(int index){
        int len = 0;
        //遍歷鄰接矩陣的matrix[index]行
        for (int j = 0; j < matrix[index].length; j++) {
            //len:出發頂點到index頂點的距離 + 從index頂點到j頂點的距離之和
            len = vv.getDis(index) + matrix[index][j];
            //如果j頂點沒有被訪問過,並且len小於出發頂點到j頂點的距離,就需要更新
            if (!vv.in(j) && len < vv.getDis(j)){
                vv.updatePre(j,index);//更新j頂點的前驅為index的頂點
                vv.updateDis(j,len);//更新出發頂點到j頂點的距離
            }
        }
    }
}

//已訪問頂點的集合
class VisitedVertex{
    //記錄各個頂點是否訪問過,0:未訪問,1:已訪問
    public int[] already_arr;
    //每個下標對應的值為前一個頂點的下標
    public int[] pre_visited;
    //記錄出發頂點到其他所有頂點的距離,求出的最短距離會存放到dis
    public int[] dis;

    /**構造器
     * @param length 頂點的個數
     * @param index 出發頂點對應的下標。如G,下標為6
     */
    public VisitedVertex(int length,int index){
        this.already_arr = new int[length];
        this.pre_visited = new int[length];
        this.dis = new int[length];
        //初始化dis數組,初始點的訪問距離為0,對於其他點為65535
        Arrays.fill(dis,65535);
        this.already_arr[index] = 1;//設置出發頂點被訪問過
        this.dis[index] = 0;
    }

    /**判斷index頂點是否被訪問過
     * @param index 索引
     * @return 如果訪問過,返回true,否則為false
     */
    public boolean in(int index){
        return already_arr[index] == 1;
    }

    /** 更新出發頂點到index頂點的距離
     * @param index 更新的哪一個數
     * @param len 要更新的值
     */
    public void updateDis(int index,int len){
        dis[index] = len;
    }

    /**更新pre頂點的前驅節點為index節點
     * @param pre 前驅節點索引
     * @param index 當前節點索引
     */
    public void updatePre(int pre,int index){
        pre_visited[pre] = index;
    }

    /**返回出發頂點到index頂點的距離
     * @param index
     */
    public int getDis(int index){
        return dis[index];
    }

    /**繼續選擇並返回新的訪問頂點
     * @return
     */
    public int updateArr(){
        int min = 65535,index = 0;
        for (int i = 0; i < already_arr.length; i++) {
            if (already_arr[i] == 0 && dis[i] < min){
                min = dis[i];
                index = i;
            }
        }
        //更新index頂點被訪問過
        already_arr[index] = 1;
        return index;
    }

    //顯示最終結果
    public void show(){
        System.out.println("------------------");
        for(int i : already_arr){
            System.out.print(i + " ");
        }
        System.out.println();
        for (int i : pre_visited){
            System.out.print(i + " ");
        }
        System.out.println();
        for (int i : dis){
            System.out.print(i + " ");
        }
        System.out.println();
        char[] vertex = {'A','B','C','D','E','F','G'};
        int count = 0;
        for (int i : dis){
            if (i != 65535){
                System.out.print(vertex[count] + "(" + i + ") ");
            }else {
                System.out.println("N ");
            }
            count++;
        }
        System.out.println();
    }
}

弗洛伊德算法

●和Dijkstra算法一 樣,弗洛伊德(Floyd)算法也是一種用於尋找給定的加權圖中頂點間最短路徑的算法。該算法名稱以創始人之一、1978年圖靈獎獲得者、斯坦福大學計算機科學系教授羅伯特弗洛伊德命名
●弗洛伊德算法(Floyd)計算圖中各個頂點之間的最短路徑
●迪傑斯特拉算法用於計算圖中某一個頂點到其他頂點的最短路徑。
迪傑斯特拉算法通過選定的被訪問頂點,求出從出發訪問頂點到其他項點的最短路徑;弗洛伊德算法中每一個頂點都是出發訪問點,所以需要將每一個頂點看做被訪問頂點,求出從每一個頂點到其他頂點的最短路徑。

弗洛伊德算法思想

●設置頂點vi到頂點vk的最短路徑已知為Lik,頂點vk到vj的最短路徑己知為Lkj,頂點vi到vj的路徑為Lij,則vi到vj的最短路徑為: min((ik+Lkj),Lij), vk的取值為圖中所有頂點,則可獲得vi到vj的最短路徑
●至於vi到vk的最短路徑Lik或者vk到vj的最短路徑Lkj,是以同樣的方式獲得

package com.xudong.Algorithm;

import java.util.Arrays;

public class FloydAlgorithm {
    public static void main(String[] args) {
        char[] vertex = {'A','B','C','D','E','F','G'};
        int[][] matrix = new int[vertex.length][vertex.length];
        final int N = 65535;//表示不可連接
        matrix[0] = new int[]{N,5,7,N,N,N,2};
        matrix[1] = new int[]{5,N,N,9,N,N,2};
        matrix[2] = new int[]{7,N,N,N,8,N,N};
        matrix[3] = new int[]{N,9,N,N,N,4,N};
        matrix[4] = new int[]{N,N,8,N,N,5,4};
        matrix[5] = new int[]{N,N,N,4,5,N,6};
        matrix[6] = new int[]{2,3,N,N,4,6,N};

        Graph1 graph1 = new Graph1(vertex.length, matrix, vertex);
        graph1.floyd();
        graph1.show();
    }
}

class Graph1{
    private char[] vertex;//存放頂點數組
    private int[][] dis;//保存從各個頂點出發到其他頂點的距離
    private int[][] pre;//保存到達目標頂點的前驅頂點

    /**構造器
     * @param length 大小
     * @param matrix 鄰接矩陣
     * @param vertex 頂點數組
     */
    public Graph1(int length,int[][] matrix,char[] vertex){
        this.vertex = vertex;
        this.dis = matrix;
        this.pre = new int[length][length];
        //對pre數組初始化,存放前驅頂點的下標
        for (int i = 0; i < length; i++) {
            Arrays.fill(pre[i],i);
        }
    }

    //顯示pre數組和dis數組
    public void show(){
        char[] vertex = {'A','B','C','D','E','F','G'};
        for (int k = 0; k < dis.length; k++) {
            //將pre數組輸出到一行
            for (int i = 0; i < dis.length; i++) {
                System.out.print(vertex[pre[k][i]] + " ");
            }
            System.out.println();
            //將pre數組輸出到一行
            for (int i = 0; i < dis.length; i++) {
                System.out.print("(" + vertex[k] + "到" + vertex[i] + "的最短路徑是:" + dis[k][i] + ") ");
            }
            System.out.println();
        }
    }

    //弗洛伊德算法。容易理解,容易操作,但時間復雜度高O(n^3)。
    public void floyd(){
        int len = 0;//保存距離
        //對中間頂點遍歷,k就是下標
        for (int k = 0; k < dis.length; k++) {
            //從i頂點出發
            for (int i = 0; i < dis.length; i++) {
                //到達j頂點
                for (int j = 0; j < dis.length; j++) {
                    len = dis[i][k] + dis[k][j];//從i頂點出發,經過k中間頂點,到的j頂點距離
                    if (len < dis[i][j]){
                        dis[i][j] = len;//更新距離
                        pre[i][j] = pre[k][j];//更新前驅節點
                    }
                }
            }
        }
    }
}

馬踏棋盤算法(圖的深度優先搜索DFS應用)

package com.xudong.Algorithm;

import java.awt.*;
import java.util.ArrayList;
import java.util.Comparator;

public class HorseChessboard {
    private static int X; //棋盤的列數
    private static int Y; //棋盤的行數
    //創建一個數組,標記棋盤的各個位置是否被訪問過
    private static boolean visited[];
    //使用一個屬性,標記棋盤的所有位置是否都被訪問
    private static boolean finished;

    public static void main(String[] args) {
        X = 8;
        Y = 8;
        int row = 1;
        int column = 1;

        int[][] chessboard = new int[X][Y];
        visited = new boolean[X * Y];

        long start = System.currentTimeMillis();
        traversalChessboard(chessboard,row - 1,column - 1,1);
        long end = System.currentTimeMillis();
        System.out.println("共耗時:" + (end - start) + "ms");

        //輸出棋盤最后的情況
        for (int[] rows : chessboard){
            for (int step : rows){
                System.out.print(step + "\t");
            }
            System.out.println();
        }
    }

    /**騎士周游問題算法
     * @param chessboard 棋盤
     * @param row 馬當前位置的行
     * @param column 馬當前位置的列
     * @param step 第幾步,初始位置就是第一步
     */
    public static void traversalChessboard(int[][] chessboard,int row,int column,int step){
        chessboard[row][column] = step;
        visited[row * X + column] = true;//標記當前位置已經訪問
        //獲取當前位置可以走的下一個位置的集合
        ArrayList<Point> ps = next(new Point(column, row));
        sort(ps);
        //遍歷ps
        while (!ps.isEmpty()){
            Point p = ps.remove(0);//取出下一個可以走的位置
            //判斷該點是否已經訪問過
            if (!visited[p.y * X + p.x]){
                traversalChessboard(chessboard,p.y,p.x,step + 1);
            }
        }
        //判斷馬是否完成任務,若沒有完成,將棋盤置零
        if (step < X * Y && !finished){
            chessboard[row][column] = 0;
            visited[row * X + column] = false;
        }else {
            finished = true;
        }

    }

    public static ArrayList<Point> next(Point curPoint){
        //創建ArrayList
        ArrayList<Point> ps = new ArrayList<>();
        //創建一個Point
        Point p1 = new Point();
        //判斷馬可以走5這個位置
        if ((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y - 1) >= 0){
            ps.add(new Point(p1));
        }
        //判斷馬可以走6這個位置
        if ((p1.x = curPoint.x - 1) >= 0 && (p1.y = curPoint.y - 2) >= 0){
            ps.add(new Point(p1));
        }
        //判斷馬可以走7這個位置
        if ((p1.x = curPoint.x + 1) < X && (p1.y = curPoint.y - 2) >= 0){
            ps.add(new Point(p1));
        }
        //判斷馬可以走0這個位置
        if ((p1.x = curPoint.x + 2) < X && (p1.y = curPoint.y - 1) >= 0){
            ps.add(new Point(p1));
        }
        //判斷馬可以走1這個位置
        if ((p1.x = curPoint.x + 2) < X && (p1.y = curPoint.y + 1) < Y){
            ps.add(new Point(p1));
        }
        //判斷馬可以走2這個位置
        if ((p1.x = curPoint.x + 1) < X && (p1.y = curPoint.y + 2) < Y){
            ps.add(new Point(p1));
        }
        //判斷馬可以走3這個位置
        if ((p1.x = curPoint.x - 1) >= 0 && (p1.y = curPoint.y + 2) < Y){
            ps.add(new Point(p1));
        }
        //判斷馬可以走4這個位置
        if ((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y + 1) < Y){
            ps.add(new Point(p1));
        }
        return ps;
    }

    //根據當前這一步的所有的下一步的選擇位置,進行非遞減排序,減少回溯的次數
    public static void sort(ArrayList<Point> ps){
        ps.sort(new Comparator<Point>() {
            @Override
            public int compare(Point o1, Point o2) {
                //獲取到o1下一步所有位置的個數
                int count1 = next(o1).size();
                //獲取到o2下一步所有位置的個數
                int count2 = next(o2).size();
                if (count1 < count2){
                    return -1;
                }else if (count1 == count2){
                    return 0;
                }else {
                    return 1;
                }
            }
        });
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM