1. 程式人生 > >演算法之單源最短路徑問題

演算法之單源最短路徑問題

1.問題描述:給出一個有向圖G,圖中的每一條邊都有一個非負邊權,要求找出從圖的源頂點s到目標頂點t之間的最短路徑。

例圖:從左到右從上到下,序號從0開始依次增大,即頂點個數n=11,A=s,E=t


2.問題分析:

(1)分支限界法:

演算法從G的源點s和空佇列開始。結點s被擴充套件之後,他的兒子結點2,3,4被一次插入隊列當中。然後取出隊頭元素,進行下一步擴充套件。保證每一次擴充套件時,源到當前節點的和都是最小的。具體的解空間圖如下:



演算法過程

演算法先從源節點s開始擴充套件,3個子結點2,3,4被插入到隊列當中,如下圖所示。

取出結點2,它有3個子樹。結點2沿邊f擴充套件到3時,路徑長度為5,而結點3的當前路徑為3(s->6),沒有得到優化,該子樹被剪掉。.結點2沿邊d,e擴充套件值5,6時,將他們加入優先佇列,如圖

取出頭結點3,它有兩個子樹。結點3沿f邊擴充套件到結點6時,該路徑長度為12,而結點6的當前路徑為4,該路徑沒有被優化,該子樹被剪枝。結點3沿g擴充套件到7時,將7加入到優先佇列。如下如所示

重複上面操作直到佇列為空。s到各個結點的最短路徑。

(2Dijkstra演算法:

將圖G中所有的頂點V分成兩個頂點集合S和T。以v為源點已經確定了最短路徑的終點併入S
集合
中,S初始時只含頂點v,T則是尚未確定到源點v最短路徑的頂點集合。然後每次從T集合中選擇S集合點中到T路徑最短的那個點,並加入到集合S中,並把這個點從集合T刪除。直到T集合為空為止。
具體步驟 1、選一頂點v為源點,並視從源點v出發的所有邊為到各頂點的最短路徑(確定資料結構:因為求的是最短路徑,所以①就要用一個記錄從源點v到其它各頂點的路徑長度陣列dist[],開始時,dist是源點v到頂點i的直接邊長度,即dist中記錄的是鄰接陣的第v行。②設一個用來記錄從源點到其它頂點的路徑陣列path[],path中存放路徑上第i個頂點的前驅頂點)。 2、在上述的最短路徑dist[]中選一條最短的,並將其終點(即<v,k>)k加入到集合s中。
3、調整T中各頂點到源點v的最短路徑。 因為當頂點k加入到集合s中後,源點v到T中剩餘的其它頂點j就又增加了經過頂點k到達j的路徑,這條路徑可能要比源點v到j原來的最短的還要短。調整方法是比較dist[k]+g[k,j]與dist[j],取其中的較小者。 4、再選出一個到源點v路徑長度最小的頂點k,從T中刪去後加入S中,再回去到第三步,如此重複,直到集合S中的包含圖G的所有頂點。
#include <iostream>
using namespace std;
#define NoEdge -1 //表示無邊
#define MAXVALUE 10000

int n=11;//頂點個數
int dist[11];//記錄從某源頂點v到各頂點的最短距離
int s[11];//已經確定了最短路徑的終點
int t[11];//尚未確定到源點v最短路徑的頂點集合
int prev[11];//記錄每個頂點最短路徑上的前驅頂點,根據該陣列可以找到任何一個頂點到達源頂點v的最短路徑
int e[11][11];//記錄頂點之間邊的權值(-1表示無邊)

int FindIndexOfMin(int dist[])//找到最短的路徑
{
    int index=0;
    int minc=MAXVALUE;
    for(int i=0; i<n; i++)
    {
        if(minc>dist[i]&&dist[i]!=NoEdge&&s[i]==0)
        {
            minc=dist[i];
            index=i;
        }
    }
    return index;
}
bool isEmpty(int t[])//判斷未確定最短路徑的頂點集合t是否為空
{
    for(int i=0; i<n; i++)
        if(t[i]==1)return false;
    return true;
}

void Init()
{
    for(int i=0; i<n; i++)
        for(int j=0; j<n; j++)
            e[i][j]=-1;
    e[0][1]=5;
    e[0][2]=3;
    e[1][3]=1;
    e[1][4]=3;
    e[1][5]=6;
    e[2][4]=8;
    e[2][5]=7;
    e[2][6]=6;
    e[3][7]=6;
    e[3][8]=8;
    e[4][7]=3;
    e[4][8]=5;
    e[5][8]=3;
    e[5][9]=3;
    e[6][8]=8;
    e[6][9]=4;
    e[7][10]=3;
    e[8][10]=2;
    e[9][10]=2;
}

void ShortestPath2(int v)//分支限界法
{
    //活結點表x[n],各頂點到源頂點最短距離dis[n],最短路徑的前驅頂點pre[n]
    int x[n],dis[n],pre[n];
    for(int i=0; i<n; i++)  //將與源頂點v相連的頂點加入活結點表
    {
        if(e[v][i]!=NoEdge)
        {
            x[i]=1;
            pre[i]=v;
        }
        else
        {
            x[i]=0;
            pre[i]=i;
        }
        dis[i]=e[v][i];
    }
    while(true)
    {
        int index=NoEdge;
        for(int i=0; i<n; i++)//從當前活結點表取出一個新的擴充套件結點
        {
            if(x[i]==1)
            {
                index=i;
                x[i]=0;
                break;
            }
        }
        if(index==NoEdge)break;//活結點表空則退出迴圈
        for(int i=0; i<n; i++)
        {//判斷經過該擴充套件結點到達它的子結點的最短路徑是否比原記錄的最短路徑小
            if(e[index][i]!=NoEdge&&(dis[index]+e[index][i]<dis[i]||dis[i]==NoEdge))
            {
                x[i]=1;
                dis[i]=dis[index]+e[index][i];
                pre[i]=index;
            }
        }
    }
    for(int i=0; i<n; i++)
        cout<<dis[i]<<" ";
    cout<<endl;
    for(int i=0; i<n; i++)
        cout<<pre[i]<<" ";
    cout<<endl;
}

void ShortestPath(int v)//Dijkstra演算法
{
    for(int i=0; i<n; i++)
    {
        prev[i]=i;
        dist[i]=e[v][i];
        t[i]=1;
        s[i]=0;
    }
    int m=n;
    while(--m>0)//(isEmpty(t)==false)
    {
        int index=FindIndexOfMin(dist);
        s[index]=1;
        t[index]=0;
        if(prev[index]==index)prev[index]=v;
        for(int i=0; i<n; i++)
        {
            if(t[i]==1&&e[index][i]!=NoEdge&&dist[index]!=NoEdge&&(e[index][i]+dist[index]<dist[i]||dist[i]==NoEdge))
            {
                dist[i]=e[index][i]+dist[index];
                prev[i]=index;
            }
        }
    }
}


int main()
{
    Init();
    ShortestPath2(0);
    /*for(int i=0; i<n; i++)
        cout<<prev[i]<<" ";
    cout<<endl;
    for(int i=0; i<n; i++)
        cout<<dist[i]<<" ";
    cout<<endl;*/
}