學(xué)習(xí)總結(jié)(算法:Lcs、Kmp、Floyd、Dijkstra)

的> 轉(zhuǎn)載請(qǐng)標(biāo)明出處,謝謝!
http://www.itdecent.cn/p/873f2d5b9ca1

關(guān)聯(lián)文章
冒泡、選擇排序 http://www.itdecent.cn/p/176b0b892591
棧和隊(duì)列 http://www.itdecent.cn/p/8cb602ef4e21
順序表、單雙鏈表 http://www.itdecent.cn/p/3aeb5998e79e
二叉樹(shù) http://www.itdecent.cn/p/de829eab944c
圖論 http://www.itdecent.cn/p/cf03e51a3ca2
算法 http://www.itdecent.cn/p/873f2d5b9ca1

Lcs算法

動(dòng)態(tài)規(guī)劃思想
經(jīng)常會(huì)遇到復(fù)雜問(wèn)題不能簡(jiǎn)單地分解成幾個(gè)子問(wèn)題,而會(huì)分解出一系列的子問(wèn)題。簡(jiǎn)單地采用把大問(wèn)題分解成子問(wèn)題,并綜合子問(wèn)題的解導(dǎo)出大問(wèn)題的解的方法,問(wèn)題求解耗時(shí)會(huì)按問(wèn)題規(guī)模呈冪級(jí)數(shù)增加。
為了節(jié)約重復(fù)求相同子問(wèn)題的時(shí)間,引入一個(gè)數(shù)組,不管它們是否對(duì)最終解有用,把所有子問(wèn)題的解存于該數(shù)組 中,這就是動(dòng)態(tài)規(guī)劃法采用的基本方法

image.png

問(wèn)題描述
什么是最長(zhǎng)公共子序列呢?好比一個(gè)數(shù)列 S,如果分別是兩個(gè)或多個(gè)已知數(shù)列的子序列,且是所有符合此條件序列中最長(zhǎng)的,則S 稱為已知序列的最長(zhǎng)公共子序列。

舉個(gè)例子,如:有兩條隨機(jī)序列,如 1 3 4 5 5 ,and 2 4 5 5 7 6,則它們的最長(zhǎng)公共子序列便是:4 5 5。

注意最長(zhǎng)公共子串(Longest CommonSubstring)和最長(zhǎng)公共子序列(LongestCommon Subsequence, LCS)的區(qū)別:子串(Substring)是串的一個(gè)連續(xù)的部分,子序列(Subsequence)則是從不改變序列的順序,而從序列中去掉任意的元素而獲得的新序列;更簡(jiǎn)略地說(shuō),前者(子串)的字符的位置必須連續(xù),后者(子序列LCS)則不必。比如字符串a(chǎn)cdfg同akdfc的最長(zhǎng)公共子串為df,而他們的最長(zhǎng)公共子序列是adf。LCS可以使用動(dòng)態(tài)規(guī)劃法解決。

解最長(zhǎng)公共子序列問(wèn)題時(shí)最容易想到的算法是窮舉搜索法,即對(duì)X的每一個(gè)子序列,檢查它是否也是Y的子序列,從而確定它是否為X和Y的公共子序列,并且在檢查過(guò)程中選出最長(zhǎng)的公共子序列。X和Y的所有子序列都檢查過(guò)后即可求出X和Y的最長(zhǎng)公共子序列。X的一個(gè)子序列相應(yīng)于下標(biāo)序列{1, 2, …, m}的一個(gè)子序列,因此,X共有2m個(gè)不同子序列(Y亦如此,如為2n),從而窮舉搜索法需要指數(shù)時(shí)間(2m * 2^n)??紤]到搜索性能我們今天引出了一個(gè)動(dòng)態(tài)規(guī)劃算法來(lái)解決這個(gè)lcs算法的問(wèn)題。
下面開(kāi)始舉個(gè)例子說(shuō)明:
假設(shè)有兩個(gè)序列分別是X = ABCBDAB,Y = BDCABA
我們通過(guò)一個(gè)矩陣來(lái)說(shuō)明:

image.png

我們可以看到矩陣中有很多數(shù)字,暫且先不看灰色背景以及箭頭,我們先看下圖中矩陣的數(shù)字代表的是什么意思呢?

根據(jù)公式
公式

第一行和第一列統(tǒng)一填0,其他的相同的取左上角數(shù)字+1,不同的取左邊和上邊的最大者。

分析完數(shù)字的填寫(xiě)規(guī)則,現(xiàn)在討論下灰色數(shù)字。我們從右下角的4開(kāi)始分析,4所在的位置是Yj的A 和Xi的B,A≠B 所以4是左邊或者右邊推出來(lái)的(逆向推出數(shù)據(jù))我們選擇頭部的4繼續(xù)分析,它的位置是Yj的A 和Xi的A,A=A ,所以它是由左上角的3推出的。繼續(xù)分析3的由來(lái),3的位置是Yj的B 和Xi的D,B≠D。所以它是由頭部的3推出的。。。。。以此類推。就得到了圖中灰色區(qū)域。
那么我們要怎么得出他的最長(zhǎng)公共子序列呢?
我們通過(guò)第一輪已經(jīng)得到了灰色的區(qū)域,我們接下去通過(guò)灰色區(qū)域去分析:
右下角的4所在的位置表示A、B.因?yàn)锳≠B.所以舍棄。 頭部的4表示的是A和A,他們相等。所以得到一個(gè)A。以此類推,我們可以最終得到 A、B、C、B。那么倒序輸出就是BCBA,那么他們的最長(zhǎng)公共子序列就是BCBA.我們用肉眼去檢查下,結(jié)果也是對(duì)的。

我們一開(kāi)始分析的位置是最右小角的4,當(dāng)然你也可以從其他幾個(gè)4的位置開(kāi)始。圖中箭頭表示的就是不同的路徑分析的結(jié)果。

代碼分析:

public void getLCS(String x, String y) {
        /*將字符串轉(zhuǎn)為字母保存*/
        char[] a = x.toCharArray();
        char[] b = y.toCharArray();

        /*創(chuàng)建矩陣*/
        int[][] array = new int[a.length + 1][b.length + 1];

        int column = b.length + 1;
        int row = a.length + 1;

        /*將第一列、第一行設(shè)置0*/
        for (int i = 0; i < column; i++) {
            array[0][i] = 0;
        }
        for (int i = 1; i < row; i++) {
            array[i][0] = 0;
        }

        for (int i = 1; i < row; i++) {
            for (int j = 1; j < column; j++) {
                if(a[i-1]==b[j-1]){//如果相等,左上角加1填入
                    array[i][j]=array[i-1][j-1]+1;
                }else{
                    array[i][j]=max(array[i-1][j],array[i][j-1]);
                }
            }
        }

        //打印
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < column; j++) {
                System.out.print(array[i][j] + " ");
            }
            System.out.println();
        }

    }

以上的操作是根據(jù)動(dòng)態(tài)規(guī)劃的方式填入數(shù)據(jù)
打印的結(jié)果:


打印

和顯然和我們一開(kāi)始矩陣圖是相同的


image.png

接著將灰色部分的填入并輸出相應(yīng)的字母

 Stack result = new Stack();

        int i = row - 2;
        int j = column -2 ;

        while (i>=0 && j>=0){
            if(a[i]==b[j]){
                /*將當(dāng)前的字母儲(chǔ)存*/
                result.push(a[i]);
                i--;
                j--;
            }else{
                /*注意數(shù)組和String中的位置有一位差*/
                if(array[i+1][j+1-1]>array[i+1-1][j+1]){
                    j--;
                }else{
                    i--;
                }

            }
        }
        System.out.println("-----");
        while(!result.isEmpty()){
            System.out.println(result.pop()+" ");
        }

Kmp算法

KMP算法是一種改進(jìn)的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt同時(shí)發(fā)現(xiàn),因此人們稱它為克努特——莫里斯——普拉特操作(簡(jiǎn)稱KMP算法)。
字符串匹配。給你兩個(gè)字符串,尋找其中一個(gè)字符串是否包含另一個(gè)字符串,如果包含,返回包含的起始位置。
我們用一個(gè)簡(jiǎn)單的例子說(shuō)明一般的字符串匹配做法(暴力匹配)
以下例子來(lái)源:http://www.ruanyifeng.com/blog/2013/05/Knuth–Morris–Pratt_algorithm.html

image

首先,字符串"BBC ABCDAB ABCDABCDABDE"的第一個(gè)字符與搜索詞"ABCDABD"的第一個(gè)字符,進(jìn)行比較。因?yàn)锽與A不匹配,所以搜索詞后移一位。

image

因?yàn)锽與A不匹配,搜索詞再往后移。

image

就這樣,直到字符串有一個(gè)字符,與搜索詞的第一個(gè)字符相同為止。

image

接著比較字符串和搜索詞的下一個(gè)字符,還是相同。

image

直到字符串有一個(gè)字符,與搜索詞對(duì)應(yīng)的字符不相同為止。

image

這時(shí),最自然的反應(yīng)是,將搜索詞整個(gè)后移一位,再?gòu)念^逐個(gè)比較。這樣做雖然可行,但是效率很差,因?yàn)槟阋?搜索位置"移到已經(jīng)比較過(guò)的位置,重比一遍。

image

一個(gè)基本事實(shí)是,當(dāng)空格與D不匹配時(shí),你其實(shí)知道前面六個(gè)字符是"ABCDAB"。KMP算法的想法是,設(shè)法利用這個(gè)已知信息,不要把"搜索位置"移回已經(jīng)比較過(guò)的位置,繼續(xù)把它向后移,這樣就提高了效率。
一般匹配字符串時(shí),我們從目標(biāo)字符串str(假設(shè)長(zhǎng)度為n)的第一個(gè)下標(biāo)選取和ptr長(zhǎng)度(長(zhǎng)度為m)一樣的子字符串進(jìn)行比較,如果一樣,就返回開(kāi)始處的下標(biāo)值,不一樣,選取str下一個(gè)下標(biāo),同樣選取長(zhǎng)度為n的字符串進(jìn)行比較,直到str的末尾(實(shí)際比較時(shí),下標(biāo)移動(dòng)到n-m)。這樣的時(shí)間復(fù)雜度是 O(n*m) 。

KMP算法:可以實(shí)現(xiàn)復(fù)雜度為O(m+n)
為何簡(jiǎn)化了時(shí)間復(fù)雜度:
充分利用了目標(biāo)字符串ptr的性質(zhì)(比如里面部分字符串的重復(fù)性,即使不存在重復(fù)字段,在比較時(shí),實(shí)現(xiàn)最大的移動(dòng)量)。
比如我們現(xiàn)在有一個(gè)字符串是"ababcabcbababcabacaba"
考察目標(biāo)字符串ptr:
ababcaba
這里我們要計(jì)算一個(gè)長(zhǎng)度為m的轉(zhuǎn)移函數(shù)next。

next數(shù)組的含義就是一個(gè)固定字符串的最長(zhǎng)前綴和最長(zhǎng)后綴相同的長(zhǎng)度。

比如:abcjkdabc,那么這個(gè)數(shù)組的最長(zhǎng)前綴和最長(zhǎng)后綴相同必然是abc。
cbcbc,最長(zhǎng)前綴和最長(zhǎng)后綴相同是cbc。
abcbc,最長(zhǎng)前綴和最長(zhǎng)后綴相同是不存在的。

注意最長(zhǎng)前綴:是說(shuō)以第一個(gè)字符開(kāi)始,但是不包含最后一個(gè)字符。
比如aaaa相同的最長(zhǎng)前綴和最長(zhǎng)后綴是aaa。

對(duì)于目標(biāo)字符串ptr,ababcaba ,長(zhǎng)度是8,所以next[0],next[1],next[2],next[3],next[4],next[5],next[6],next[7]分別計(jì)算的是
a,ab,aba,abab,ababc,ababca,ababcab,ababcaba 的相同的最長(zhǎng)前綴和最長(zhǎng)后綴的長(zhǎng)度。由于a,ab,aba,abab,ababc,ababca,ababcab,ababcaba 的相同的最長(zhǎng)前綴和最長(zhǎng)后綴是“”,“”,“a”,“ab”,“”,“a”,“ab”,“aba”,所以next數(shù)組的值是[0,0,1,2,0,1,2,3],這里0表示不存在,1,2,3分別代表匹配到的個(gè)數(shù)。
我們用圖例來(lái)進(jìn)一步說(shuō)明下:
(可以用一個(gè)count值統(tǒng)計(jì)相同的個(gè)數(shù))
1、我們需要2個(gè)指針j 和 i。j指向第一個(gè)位置,i指向第二個(gè)位置。
默認(rèn)next[0] = 0

image.png

2、因?yàn)閍≠b,所以b的next值next[1]是=0,此時(shí)j直接退回到第一個(gè)位置,i往后進(jìn)一位


image.png

3、因?yàn)樽詈笠晃缓偷谝晃坏腶相等,所以a的next值next[2]是 1,此時(shí)count = 1


image.png

此時(shí)j和i指針同時(shí)向前移動(dòng)

4、同理b的next值next[3]是 2 , 此時(shí)count = 2


image.png
此時(shí)j和i指針同時(shí)向前移動(dòng)

5、因?yàn)閍≠c,所以b的next值next[4]是=0,此時(shí)j直接退回到第一個(gè)位置,i往后進(jìn)一位 此時(shí)count = 0

image.png

image.png

6、兩個(gè)a相等,所以next[5]=1,此時(shí)count=1


image.png

7、同理對(duì)next[6],nexy[7]是同樣的操作


image.png

那么我得到上面這幾個(gè)數(shù)字的作用是什么呢? 上面我們提過(guò)字符串匹配的暴力做法就是逐一匹配,遇到不同的重新回到原來(lái)的位置。而kmp算法就是為了盡可能的移動(dòng)最大偏移量。
我們的原來(lái)字符串是 "ababcabcbababcabacaba"
考察目標(biāo)字符串ptr是:"ababcaba"。


image.png

用我們的肉眼幾乎一眼就可以看出目標(biāo)串一直匹配到倒數(shù)第二位,直到最后一位a與原字符串的c是不匹配的,所以移動(dòng)的時(shí)候并不需要一位一位的移動(dòng)。但是計(jì)算機(jī)畢竟是傻的,他怎么直到要移動(dòng)多少位置呢?
實(shí)際上我們有個(gè)規(guī)則。

移動(dòng)位數(shù) = 已匹配的字符數(shù) - 對(duì)應(yīng)的部分匹配值
根據(jù)這個(gè)規(guī)則,我們進(jìn)行第一次移動(dòng)。移動(dòng)位數(shù) = 已匹配的個(gè)數(shù)7 - 對(duì)應(yīng)部分匹配值b對(duì)應(yīng)的next是2= 5
所以我們需要一次性移動(dòng)5位。

image.png

實(shí)際上kmp最核心的就是回退策略。用一句代碼表示就是 j = next[j-1].當(dāng)遇到不同的時(shí)候j的指針就回退到該位置。舉個(gè)例子:剛剛在比對(duì)過(guò)程中我們發(fā)現(xiàn)了a和c是不同的 那么此時(shí)比對(duì)的j的指針就是 j = next[7-1] = 2.所以我們就開(kāi)始有了上圖的從第二個(gè)位置開(kāi)始重新比對(duì)。
等下我們可以用代碼來(lái)表示。

好了繼續(xù)上面的移動(dòng),上圖中我們發(fā)現(xiàn)a≠c,根據(jù)公式:移動(dòng)的位數(shù)= 2 - 0 = 2;

image.png

如果遇到第一個(gè)就不匹配的直接后移一位


image.png

繼續(xù)移動(dòng)一位


image.png

逐位比較,直到搜索詞的最后一位,發(fā)現(xiàn)完全匹配,于是搜索完成。如果還要繼續(xù)搜索(即找出全部匹配),移動(dòng)位數(shù) = 7 - 0,再將搜索詞向后移動(dòng)7位,這里就不再重復(fù)了。

代碼:

/**
     *KMP算法得到數(shù)組
     */
    public static int[] kmpNext(String dest){
        int[] next=new int[dest.length()];
        next[0]=0;
        //開(kāi)始推出next
        for(int i=1,j=0;i<dest.length();i++){
            //3
            while(j>0 && dest.charAt(j) != dest.charAt(i)){
                j=next[j-1];
            }
            //1
            if(dest.charAt(i)==dest.charAt(j)){
                j++;
            }
            //2
            next[i]=j;
        }
        return next;
    }
image.png

從代碼上我們可以到 如果是相同的 j++,加上外層的i++就相當(dāng)于剛剛圖例中的兩個(gè)指針同時(shí)后移一位。
如果是不同的j = next[i-1]。
完整代碼

 @Test
    public void addition_isCorrect() throws Exception {

        String str="ababcabcbababcabacaba";
        String dest="ababcaba";
        int[] array=kmpNext(dest);
        for (int i = 0; i <array.length ; i++) {
            System.out.print(array[i]+" ");
        }
       // System.out.println(kmp(str,dest,array));
    }
    /**
     *KMP算法得到數(shù)組
     */
    public static int[] kmpNext(String dest){
        int[] next=new int[dest.length()];
        next[0]=0;
        //開(kāi)始推出next
        for(int i=1,j=0;i<dest.length();i++){
            //3
            while(j>0 && dest.charAt(j) != dest.charAt(i)){
                j=next[j-1];
            }
            //1
            if(dest.charAt(i)==dest.charAt(j)){
                j++;
            }
            //2
            next[i]=j;
        }
        return next;
    }
    public static int kmp(String str,String dest,int[] next){
        for(int i=0,j=0;i<str.length();i++){
            while(j>0 && str.charAt(i) != dest.charAt(j)){
                j=next[j-1];
            }
            if(str.charAt(i)==dest.charAt(j)){
                j++;
            }
            if(j==dest.length()){//結(jié)束
                return i-j+1;
            }
        }
        return 0;
    }

image.png

輸出結(jié)果是9,說(shuō)明在源字符串第幾個(gè)位置開(kāi)始匹配到數(shù)據(jù)。

Floyd算法

Floyd算法(Floyd-Warshall algorithm)又稱為弗洛伊德算法、插點(diǎn)法,是解決給定的加權(quán)圖中頂點(diǎn)間的最短路徑的一種算法,可以正確處理有向圖或負(fù)權(quán)的最短路徑問(wèn)題,同時(shí)也被用于計(jì)算有向圖的傳遞閉包。該算法名稱以創(chuàng)始人之一、1978年圖靈獎(jiǎng)獲得者、斯坦福大學(xué)計(jì)算機(jī)科學(xué)系教授羅伯特·弗洛伊德命名。
舉個(gè)例子說(shuō)明下:

image.png

我們用一個(gè)圖表示


image.png

生成他們的鄰接矩陣和路徑矩陣


D矩陣

P矩陣

其中d矩陣中的inf表示不能直接達(dá)到,比如v1->v3

接著我們修改d矩陣,然后把受影響的地方儲(chǔ)存在p矩陣中(修改p矩陣中對(duì)應(yīng)的數(shù)據(jù))
什么意思呢?
首先我們以v0為跳板,從圖中我們可以看出v1->v2直接到達(dá)權(quán)重是4,如果以v0為跳板我們可以看到,v1->v0是2 v0->v2是1 3<4那么就修改d矩陣


image.png

再修改p矩陣,被0矩陣作為跳板修改過(guò),則修改成0.同樣的inf位到達(dá)的也可以通過(guò)v0修改
image.png
image.png

接著我們以v1為跳板,發(fā)現(xiàn)沒(méi)有要修改的數(shù)據(jù)
以v2為跳板,我們發(fā)現(xiàn)v0->v3 = 5,而v0->v2->v3 = 4 v1->v3=7,而 v1->v2->v3 =6 同樣上面修改的原則對(duì)d,p矩陣進(jìn)行修改.


image.png
image.png

最后v3同樣不需要修改。所以以上的圖就是最短路徑d,p矩陣圖
那么這個(gè)代碼要怎么寫(xiě)呢?其實(shí)看懂了邏輯就很簡(jiǎn)單了。我們先拿v0為跳板的例子:

 public static void printArray(int[][] array) {
        for (int i = 0; i < array.length; i++) {
            for (int j = 0; j < array[i].length; j++) {
                System.out.print(array[i][j] + " ");
            }
            System.out.println();
        }
        System.out.println("---------------------");
    }

    //鄰接距陣
    public static int[][] d = new int[][]{
            {0, 2, 1, 5},
            {2, 0, 4, I},
            {1, 4, 0, 3},
            {5, I, 3, 0}
    };
    public static int[][] p = new int[][]{
            {0, 1, 2, 3},
            {0, 1, 2, 3},
            {0, 1, 2, 3},
            {0, 1, 2, 3}
    };

    private static void floyd() {
        for (int i = 0; i < d.length; i++) {
            for (int j = 0; j < d[0].length; j++) {
                if (d[i][j] > d[i][0] + d[0][j]) {
                    d[i][j] = d[i][0] + d[0][j];
                    p[i][j] = p[i][0];
                }
            }
        }
    }

打印的結(jié)果是


image.png

和我們一開(kāi)始做的兩個(gè)矩陣圖是一樣的
接著只要把0 再外面套個(gè)循環(huán)修改成動(dòng)態(tài)的就可以了
完整代碼是:

public class FloydTest {
    private final static int I = Integer.MAX_VALUE;

    @Test
    public void test() {
        floyd();
        printArray(d);
        printArray(p);
    }

    public static void printArray(int[][] array) {
        for (int i = 0; i < array.length; i++) {
            for (int j = 0; j < array[i].length; j++) {
                System.out.print(array[i][j] + " ");
            }
            System.out.println();
        }
        System.out.println("---------------------");
    }

    //鄰接距陣
    public static int[][] d = new int[][]{
            {0, 2, 1, 5},
            {2, 0, 4, I},
            {1, 4, 0, 3},
            {5, I, 3, 0}
    };
    public static int[][] p = new int[][]{
            {0, 1, 2, 3},
            {0, 1, 2, 3},
            {0, 1, 2, 3},
            {0, 1, 2, 3}
    };

    private static void floyd() {
        for (int k = 0; k < d.length; k++) {
            for (int i = 0; i < d.length; i++) {
                for (int j = 0; j < d[0].length; j++) {
                    if (d[i][j] > d[i][k] + d[k][j]) {
                        d[i][j] = d[i][k] + d[k][j];
                        p[i][j] = p[i][k];
                    }
                }
            }
        }
    }
}

輸出的結(jié)果是:


image.png

Dijkstra算法

迪杰斯特拉(Dijkstra)算法是典型最短路徑算法,用于計(jì)算一個(gè)節(jié)點(diǎn)到其他節(jié)點(diǎn)的最短路徑。
它的主要特點(diǎn)是以起始點(diǎn)為中心向外層層擴(kuò)展(廣度優(yōu)先搜索思想),直到擴(kuò)展到終點(diǎn)為止。
Dijkstra算法介紹
算法特點(diǎn):

迪科斯徹算法使用了廣度優(yōu)先搜索解決賦權(quán)有向圖或者無(wú)向圖的單源最短路徑問(wèn)題,算法最終得到一個(gè)最短路徑樹(shù)。該算法常用于路由算法或者作為其他圖算法的一個(gè)子模塊。

算法的思路

Dijkstra算法采用的是一種貪心的策略,聲明一個(gè)數(shù)組dis來(lái)保存源點(diǎn)到各個(gè)頂點(diǎn)的最短距離和一個(gè)保存已經(jīng)找到了最短路徑的頂點(diǎn)的集合:T,初始時(shí),原點(diǎn) s 的路徑權(quán)重被賦為 0 (dis[s] = 0)。若對(duì)于頂點(diǎn) s 存在能直接到達(dá)的邊(s,m),則把dis[m]設(shè)為w(s, m),同時(shí)把所有其他(s不能直接到達(dá)的)頂點(diǎn)的路徑長(zhǎng)度設(shè)為無(wú)窮大。初始時(shí),集合T只有頂點(diǎn)s。
然后,從dis數(shù)組選擇最小值,則該值就是源點(diǎn)s到該值對(duì)應(yīng)的頂點(diǎn)的最短路徑,并且把該點(diǎn)加入到T中,OK,此時(shí)完成一個(gè)頂點(diǎn),
然后,我們需要看看新加入的頂點(diǎn)是否可以到達(dá)其他頂點(diǎn)并且看看通過(guò)該頂點(diǎn)到達(dá)其他點(diǎn)的路徑長(zhǎng)度是否比源點(diǎn)直接到達(dá)短,如果是,那么就替換這些頂點(diǎn)在dis中的值。
然后,又從dis中找出最小值,重復(fù)上述動(dòng)作,直到T中包含了圖的所有頂點(diǎn)。
我們用圖來(lái)表示下算法實(shí)現(xiàn)的過(guò)程


image.png

假設(shè)我們有以上這個(gè)圖,圖中的數(shù)字表示的是權(quán)重。我們用一個(gè)矩陣來(lái)表示下:
{0,1,5,I,I,I,I,I,I},
{1,0,3,7,5,I,I,I,I},
{5,3,0,I,1,7,I,I,I},
{I,7,I,0,2,I,3,I,I},
{I,5,1,2,0,3,6,I,I},
{I,I,7,I,3,0,I,5,I},
{I,I,I,3,6,I,0,2,7},
{I,I,I,I,9,5,2,0,4},
{I,I,I,I,I,I,7,4,0}

其中數(shù)字代表的是權(quán)重,I表示無(wú)法到達(dá)。比如第一行的1,表示的是v0->v1的路徑是1,v0->v3無(wú)法到達(dá)。以此類推。
我們用兩個(gè)數(shù)組表示下:
權(quán)重?cái)?shù)組、路徑數(shù)組
以v0作為定點(diǎn),v0可以直接到達(dá)的是v1和v2,我們選擇權(quán)重小的v1。

繼續(xù),一旦頂點(diǎn)被選擇了,就往左邊推


image.png
image.png

圖中以v1為例子,v0->v1的權(quán)重是1,前驅(qū)是v0。

然后從左邊到右邊我們可以看到v1-v2是最小路徑,故刪除入度到v2比較大的路徑:


image.png
image.png

繼續(xù)往下操作,以下直接貼圖。
選擇v4

image.png
image.png

選擇v3


image.png
image.png

選擇5或6,這里我們選擇5


image.png
image.png

選擇6


image.png
image.png

選擇7


image.png
image.png

最后剩一個(gè)8


image.png

image.png

以上就是Dijkstra思想的圖例,如果還不懂,我們用一個(gè)簡(jiǎn)單的圖和一些簡(jiǎn)答的描述來(lái)說(shuō)明, Dijkstra的核心算法計(jì)算最短路徑實(shí)際上就是不斷選擇一個(gè)最短的路徑頂點(diǎn)歸到左邊,如下圖


image.png

比如v0到v4是最短的,那么把v4標(biāo)記成已選擇放入左邊


image.png

然后重新計(jì)算出各個(gè)權(quán)重,最后不斷的選擇出最短路徑歸入左邊已選擇的數(shù)組中。所以這個(gè)就是他的核心思想,通過(guò)這個(gè)思想我們可以通過(guò)代碼來(lái)表示

代碼:

 public static final int I = 100;
    int[][] array=new int[][]{
            {0,1,5,I,I,I,I,I,I},
            {1,0,3,7,5,I,I,I,I},
            {5,3,0,I,1,7,I,I,I},
            {I,7,I,0,2,I,3,I,I},
            {I,5,1,2,0,3,6,I,I},
            {I,I,7,I,3,0,I,5,I},
            {I,I,I,3,6,I,0,2,7},
            {I,I,I,I,9,5,2,0,4},
            {I,I,I,I,I,I,7,4,0}
    };
    public void dijkstar(){
        int k=0;//表示當(dāng)前正要處理的頂點(diǎn)Vk

        //初始化相關(guān)的信息
        int[] path=new int[9];//保存路徑
        int[] weight=array[0];//保存權(quán)重
        //定義一個(gè)數(shù)組來(lái)存放U和V兩個(gè)集合的信息
        int[] flag=new int[9];
        flag[0]=1;
        //開(kāi)始邏輯,求VO到某個(gè)頂點(diǎn)的最短路徑
        for(int v=1;v<9;v++){
            //在能走的路徑中找到最短的一條
            int min=I;
            for(int i=0;i<9;i++){
                if(flag[i]==0 && weight[i]<min){
                    k=i;//K為U集合到V集合中找到的頂點(diǎn)
                    min=weight[i];//min找到了最小值的位置
                }
            }
            //從這個(gè)最短的路徑對(duì)應(yīng)的頂點(diǎn)開(kāi)始找下一輪
            flag[k]=1;
            //修正當(dāng)前最短路徑
            for(int i=0;i<9;i++){
                //如果經(jīng)過(guò)V頂點(diǎn)的路徑比現(xiàn)在的路徑短,新更新
                if(flag[i]==0 && (min+array[k][i])<weight[i]){
                    weight[i]=min+array[k][i];//修改路徑長(zhǎng)度
                    path[i]=k;//保存前驅(qū)
                }
            }
        }
        for(int i=0;i<path.length;i++){
            System.out.print(path[i]+" ");
        }
        System.out.println();
        for(int i=0;i<weight.length;i++){
            System.out.print(weight[i]+" ");
        }


        //打印結(jié)果
        int v=8;
        while(v!=0){
            System.out.print(path[v]);
            v=path[v];
        }
    }
    @Test
    public void test(){
        dijkstar();
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容