viterbi算法:利用動態規劃尋找最短路徑

動態規劃是運籌學的一個分支,是求解決策過程最優化的數學方法,通常情況下應用于最優化問題,這類問題一般有很多個可行的解,每個解有一個值,而我們希望從中找到最優的答案。
在計算機科學領域,應用動態規劃的思想解決的最基本的一個問題就是:尋找有向無環圖(籬笆網絡)當中兩個點之間的最短路徑(實際應用于地圖導航、語音識別、分詞、機器翻譯等等)。

下面舉一個比較簡單的例子做說明:求S到E的最短路徑。如下圖(各點之間距離不相同):

我們知道,要找到S到E之間最短路徑,最容易想到的方法就是窮舉法。也就是把所有可能的路徑都例舉出來。從S走向A層共有4種走法,從A層走向B層又有4種走法,從B層走向C層又有4種走法,然后C層走向E點只有一種選擇。所以最終我們窮舉出了4X4X4=64種可能。顯然,這種方法必定可行。但在實際的應用當中,對于數量極其龐大的結點數和邊數的圖,其計算復雜度也將會變得非常大,而計算效率也會隨之降低。


因此,這里選擇使用一種基于動態規劃的方式來尋找最佳路徑。
所謂動態規劃。其核心就是“動態”的概念,把大的問題細分為多個小的問題,基于每一步的結果再去尋找下一步的策略,通過每一步走過之后的局部最優去尋找全局最優。這樣解釋比較抽象,下面直接用回剛剛的例子說明。如下圖:

首先,我們假設S到E之間存在一條最短路徑(紅色),且這條路徑經過C2點,那么我們便一定能夠確定從S到C2的64條(4X4X4=64)子路徑當中,該子路徑一定最短。(證明:反證法。如果S到C2之間存在一條更短的子路徑,那么便可以用它來代替原先的路徑,而原先的路徑顯然就不是最短了,這與原假設自相矛盾)。
同理,我們也可以得出從S到B2點為兩點間最短子路徑的結論。這時候,真相便慢慢浮出水面:既然如此,我們計算從S點出發到點C2的最短路徑,是不是只要考慮從S出發到B層所有節點的最短路徑就可以了?答案是肯定的!因為,從S到E的“全局最短”路徑必定經過在這些“局部最短”子路徑。沒錯!這就是上面提及到的通過局部最優的最優去尋找全局最優,問題的規模被不斷縮小!

接下來,要揭曉答案了!繼續看下圖:

回顧之前的分析:我們計算從S起到C2點的最短路徑時候只需要考慮從S出發到B層所有節點的最短路徑,B層也如是。對B2來說,一共有4條路線可以到達,分別是A1→B2,A2→B2,A3→B2,A4→B2。我們需要做的就是把A2→B2這條最短路線保留,而其他3條刪除掉(因為根據以上的分析,它們不可能構成全程的最短路線)。OK,來到這里,我們會發現一個小“漏洞”,這段S→A2→B2→C2→E的路線只是我一廂情愿的假設,最短路徑不一定是經過以上這些點。所以,我們要把每層的每個節點都考慮進來。

以下是具體的做法:
step1:從點S出發。對于第一層的3個節點,算出它們的距離d(S,A1),d(S,A2),d(S,A3),d(S,A4),因為只有一步,所以這些距離都是S到它們各自的最短距離。

step2:對于B層的所有節點(B1,B2,B3,B4),要計算出S到它們的最短距離。我們知道,對于特定的節點B2,從S到它的路徑可以經過A層的任何一個節點(A1,A2,A3,A4)。對應的路徑長就是d(S,B2)=d(S,Ai)+d(Ai,B2)(其中i=1,2,3,4)。由于A層有4個節點(即i有4個取值),我們要一一計算,然后找到最小值。這樣,對于B層的每個節點,都需要進行4次運算,而B層有4個節點,所以共有4X4=16次運算。

step3:這一步是該算法的核心。我們從step2計算得出的結果只保留4個最短路徑值(每個節點保留一個)。那么,若從B層走向C層來說,該步驟的基數已經不再是4X4,而是變成了4!也就是說,從B層到C層的最短路徑只需要基于B層得出的4個結果來計算。這種方法一直持續到最后一個狀態,每一步計算的復雜度為相鄰兩層的計算復雜度為4X4乘積的正比!再通俗點說,連接這兩兩相鄰層的計算符合變成了“+”號,取代了原先的“X”號。用這種方法,只需進行4X4X2=32次計算!

其實上述的算法就是著名的維特比算法,事實上非常簡單!
若假設整個網格的寬度為D,網格長度為N,那么若使用窮舉法整個最短路徑的算法復雜度為O(DN),而使用這種算法的計算復雜度為O(ND2)。試想一下,若D與N都非常大,使用維特比算法的效率將會提高幾個數量級!

代碼實現(C語言版):

同樣是實現從S到E的最短路徑。不過這次把剛剛的情況簡化了一下,原理是相同的。

#include<stdlib.h> 
#include<stdio.h>  
#define x 9999
#define max 9999
int data[10][10];
int dist[10];//記錄最短路徑為多少
int path[10];//記錄最短路徑
int kmin(int,int);
void fpath(int a[][10]);
int froute(int a[][10]);
void main()
{
    int i,m;
    int a[10][10]=
  {
    {x,4,2,3,x,x,x,x,x,x}, 
    {x,x,x,x,10,9,x,x,x,x},
    {x,x,x,x,6,7,10,x,x,x},
    {x,x,x,x,x,3,8,x,x,x},
    {x,x,x,x,x,x,x,4,8,x},
    {x,x,x,x,x,x,x,9,6,x},
    {x,x,x,x,x,x,x,5,4,x},
    {x,x,x,x,x,x,x,x,x,8},
    {x,x,x,x,x,x,x,x,x,4},
    {x,x,x,x,x,x,x,x,x,x}
  };
    /*for (i=0;i<10;i++)
    {
    for(j=0;j<10;j++)
    printf("%d ",a[i][j]);
    printf("\n"); 
    }*/
    fpath(a);
      printf("最短路徑大小為: %d\n",dist[9]);

    m=froute(a);
    for(i=m-1;i>=0;i--)
      printf("最短路徑經過: %d\n",path[i]);
    }
    void fpath(int a[][10])
    {
      int i,j,k;42 dist[0]=0;
      for(i=1;i<10;i++)
      {
        k=max;
        for(j=0;j<i;j++)
        {
          if(a[j][i]!=x)
            if((dist[j]+a[j][i])<k)
            k=dist[j]+a[j][i];  
        } 
      dist[i]=k;
      }
    }
    int froute(int a[][10])
    {
     int j,b,k=1,i=9;
     path[0]=10;
     while(i>0)
     {
      for(j=i-1;j>=0;j--)
      { 
       if(a[j][i]!=x)
       {
        b=dist[i]-a[j][i];
        if(b==dist[j])
        {
          path[k++]=j+1;
          i=j; 
          break;
        }
      }
    } 
  }
  return k;
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容