數據結構與算法系列研究七——圖、prim算法、dijkstra算法

圖、prim算法、dijkstra算法

1、圖的定義

 圖(Graph)能夠簡單表示爲G=<V, E>,其中V稱爲頂點(vertex)集合,E稱爲邊(edge)集合。圖論中的圖(graph)表示的是頂點之間的鄰接關係。
node

(1) 無向圖(undirect graph)
      E中的每條邊不帶方向,稱爲無向圖。
(2) 有向圖(direct graph)
      E中的每條邊具備方向,稱爲有向圖。
(3) 混合圖
       E中的一些邊不帶方向, 另外一些邊帶有方向。
(4) 圖的階
      指圖的頂點數目,即頂點集V中的元素個數。
(5) 多重圖
      擁有平行邊或自環的圖。
(6) 簡單圖
      不含平行邊和自環的圖.
ios

    (7) 邊的表示方法與有關術語
      a. 無向圖的邊稱爲無向邊(edge),它用無序偶表示

       稱頂點vi與vj相互鄰接或互爲鄰接點(adjacent);邊(vi, vj)依附於(incident)頂點vi和vj或與頂點vi和vj相關聯。
     b. 有向圖的邊稱爲有向邊或弧(arc),它用有序偶表示

     稱頂點vi爲弧尾(tail)或始點(initial node),頂點vj爲弧頭(head)或終端點(terminal node) ;vi鄰接至vj,而vj鄰接自vi;弧<vi, vj>依附於或關聯頂點vi和vj。
  (8) 頂點的度(degree)
     a. 無向圖頂點的度定義爲與該頂點相關聯的邊的數目;
       性質1:無向圖中,各頂點的度數和等於邊數的2倍。
    b. 有向圖頂點的度定義爲與該頂度相關聯的弧的數目。   
       即,有向圖頂點的度=入度(indegree)+出度(outdegree),其中入度定義爲鏈接該頂點的弧頭的數目;出度定義爲鏈接該頂點的弧尾的數目。
      性質2:有向圖中,頂點的入度和=出度和=弧的數目。
  (9) 徹底圖
     a. n階無向簡單圖中,若每一個頂點的度均爲n-1,稱該圖爲無向徹底圖。算法

       性質3:n階無向徹底圖邊的數目爲
     b. n階有向簡單圖中,若每一個頂點的入度=出度=n-1,稱該圖爲n階有向徹底圖。
       性質4:n階有向徹底圖弧的數目爲n(n-1)。
  (10) 網(Network)
      若圖中的邊帶有權重(weight),稱爲網。邊上的權重通常表明某種代價或耗費。好比:頂點表示城市,邊表示城市間的公路,則權重能夠用公路里程來表示。若邊上的權重爲無窮大,通常表示代價無窮大等含義。
  (11) 稀疏圖(sparse graph)與稠密圖(dense graph)
        若無向圖或有向圖有e條邊或弧,若e很小(如e<nlog2n),稱爲稀疏圖,不然稱爲稠密圖。
  (12) 子圖
        對於圖G=<V, E>,如有另外一圖G'=<V', E'>知足,稱圖G'爲G的子圖。數組

2、 圖的路徑

  (1)路徑(path)
        圖G=<V, E>中,從任一頂點開始,由邊或弧的鄰接相當系構成的有限長頂點序列稱爲路徑。注意:
          有向圖的路徑必須沿弧的方向構成頂點序列;
          構成路徑的頂點可能重複出現(即容許反覆繞圈)。
   (2) 路徑長度
       路徑中邊或弧的數目。
   (3) 簡單路徑
        除第一個和最後一個頂點外,路徑中無其它重複出現的頂點,稱爲簡單路徑。
   (4) 迴路或環(cycle)
        路徑中的第一個頂點和最後一個頂點相同時,稱爲迴路或環。網絡

3、圖的連通性

   (1) 無向連通圖:在無向圖中,若從頂點vi到vj有路徑,則稱vi和vj是連通的。若該圖中任意兩個頂點都是連通的,則稱它是連通圖。
   (2) 連通份量:無向圖中的極大連通子圖(包括子圖中的全部頂點和全部邊)稱爲連通份量或連通分支。連通圖也能夠定義爲連通分支數等於1的圖。
   數據結構

 

   (3) 有向連通圖
        在有向圖中,任一對頂點vi和vj(vi不等於vj),若從vi到vj以及從vj到vi均連通(即存在路徑),稱它是強連通的。
   (4) 強連通份量
        有向圖中的極大強連通子圖稱爲強連通份量。
   性質1:有向強連通圖的充要條件是該圖存在一個迴路通過每一個頂點至少1次。
   性質2:n階無向連通圖中至少有n-1條邊; n階有向連通圖中至少有n條邊。
   例如,3個頂點組成的最小無向和有向連通圖
ide

  (5) 生成樹
        一個n階連通圖的生成樹是一個極小連通子圖,它包含圖中所有n個頂點以及保證該子圖是連通圖的最少的n-1條邊。
        性質3:在生成樹上增長任何一條邊,必造成迴路。
  (6) 有向樹與生成森林
       若是一個有向圖恰有一個頂點入度爲0,其他頂點的入度均爲1,則是一棵有向樹。一個有向圖的生成森林由若干棵有向樹組成,含有圖中所有頂點,但只有中以構成若干棵不相交的有向樹的弧。測試

4、圖的存儲結構

 1.鄰接矩陣:若n階圖表示爲G=<V, E>,其中V={v0, v1, …, vn-1},則定義
spa

若圖G爲n階網,則定義:3d

其中,wij爲邊(vi, vj)或弧<vi, vj>上的權重。
無向簡單圖鄰接矩陣的性質:
    關於主對角線對稱,即A=AT;
    主對角線元素全爲0;
    矩陣中1的數目=邊數的2倍;
    第i行1的數目=第i列1的數目=頂點vi的度。
2. 鄰接表與逆鄰接表:若n階圖表示爲G=<V, E>,其中V={v0, v1, …, vn-1},則可用鏈表實現圖的存儲結構。
(1)、鄰接表:
    a. 無向圖:關聯頂點vi的全部邊組成的集合用單鏈表實現存儲,頭結點存儲頂點vi的編號和信息,其他結點存儲鄰接於頂點vi的其它頂點的編號、邊的權重和信息。這樣共造成n個單鏈表,稱爲鄰接表。
    b. 有向圖:以頂點vi爲弧尾的全部弧組成的集合用單鏈表實現存儲,頭結點存儲弧尾vi的編號和信息,其他結點存儲弧頭頂點編號、弧的權重和信息。
頭結點(存儲頂點vi):

1 typedef struct
2 { //頂點數據(可選)
3  ElemTp data; 4    //頂點信息(可選)
5  InfoTp info; 6    int i;  //頂點下標
7    ArcNode *firstarc; 8 } HNode;
View Code

表結點(存儲邊或弧):

1 typedef struct node 2 { //邊或弧的權重(可選)
3    double w; 4    //邊或弧的信息(可選)
5  InfoTp info; 6    int j;  //鄰接點下標
7    struct node *nextarc; 8 } ArcNode;
View Code

總體數據結構:

#define MAX_N  最大頂點數 typedef enum { DG, UDG, DN, UDN } GraphKind; // DG:有向圖, UDG:無向圖, DN:有向網, UDN:無向網
typedef   struct { HNode h[MAX_N]; //頭結點造成數組
    int n, e;   //n:實際頂點數; e:邊或弧的數目
    Graphkind   kind;    //圖的類型(可選) 
} ALGraph;
View Code

(2)、 逆鄰接表
        有向圖中,表結點存儲鄰接至頂點vi的全部弧,即頭結點是弧頭,表結點是弧尾。
無向圖鄰接表存儲結構示意圖:

特色:表結點數爲邊數的2倍;頂點vi的度爲第i個單鏈表的表結點數。
有向圖鄰接表存儲結構示意圖:

特色:表結點數爲弧的數目;頂點vi的出度爲第i個單鏈表的表結點數。 (求入度不方便)
有向圖逆鄰接表存儲結構示意圖:

 

特色:表結點數爲弧的數目;頂點vi的入度爲第i個單鏈表的表結點數。(求出度不方便)

3. 有向圖的十字鏈表
        每一個表結點(弧<vi, vj>)在水平方向構成單鏈表,造成以vi爲弧尾的全部弧組成的集合;
        每一個表結點(弧<vi, vj>)在垂直方向構成單鏈表,造成以vj爲弧頭的全部弧組成的集合。

typedef struct { //頂點數據(可選)
 ElemTp data; //頂點信息(可選)
 InfoTp info; int i;  //頂點下標
   OLANode *firstin; OLANode *firstout; } OLHNode; typedef struct node { //弧的權重(可選)
   double w; //弧的信息(可選)
 InfoTp info; int i, j;  //弧的端點下標
   struct node *hlink; struct node *vlink; } OLANode; typedef struct { OLHNode h[MAX_N]; //頭結點造成數組
    int n, e;   //n:實際頂點數; e:邊或弧的數目
    Graphkind   kind;    //圖類型(可選) 
} OLGraph;
View Code

十字鏈表特色:表結點數等於弧的數目;求入度和出度都很方便。
有向圖十字鏈表存儲結構示意圖:

4. 無向圖的鄰接多重表
      採用相似十字鏈表的思想實現無向圖存儲。任意邊(vi, vj)只存儲一個表結點,每一個表結點有inext和jnext兩個指針域,inext指向關聯於頂點vi的下一條邊,而jnext指向關聯於頂點vj的下一邊條。頂點vi的頭結點僅含一個指針域,指向關聯於vi的第1條邊。

鄰接多重表存儲結構示意圖:

5、圖的遍歷和相關算法

1. 遍歷的定義
      從圖中某頂點出發,沿路徑方向訪問每一個頂點一次且僅一次。
2. 圖遍歷算法的輔助數據結構
      爲避免頂點重複訪問,需定義一個頂點標誌數組visited[0..n-1],標記每一個頂點是否已訪問。
3. 圖的深度優先搜索(Depth First Search)算法
     搜索原則:沿出發頂點的第1條路徑儘可能深刻,遍歷路徑上的全部頂點;而後退回到該頂點,搜索第2條, 第3條, …, 路徑,直到以該頂點爲始點的全部路徑上的頂點都已訪問過(這是遞歸算法)。對於非連通圖,需從每一個頂點出發,嘗試深度優先遍歷。

void DFStravel(Graph  &G)  //Graph爲鄰接矩陣
{  bool *visited=new bool[G.n]; for(i=0; i<G.n; i++) visted[i]=false; for(i=0; i<G.n; i++) //保證非連通圖的遍歷
        if (!visited[i]) DFS(G, i); delete []visited; } void DFS(Graph  &G, int i) //從vi出發深度優先搜索
{  visit(i); visited[i]=true; for (j=First_Adj(G, i); j!=-1; j=Next_Adj(G, i, j)) if (!visited[j]) DFS(G, j); }
View Code

4. 圖的寬度優先搜索(Breadth First Search)算法
搜索原則:

  1.  訪問遍歷出發頂點,該頂點入隊;
  2.  隊列不空,則隊頭頂點出隊;
  3.  訪問出隊頂點全部的未訪問鄰接點並將訪問的頂點入隊;
  4.  重複(2), (3), 直到隊列爲空。

 以上爲非遞歸算法,需設隊列實現算法。對於非連通圖,需從每一個頂點出發,嘗試寬度優先搜索。

 1 void BFStravel(Graph  &G)  //Graph爲鄰接矩陣
 2 {  bool *visited=new bool[G.n];  3     for(i=0; i<G.n; i++) visted[i]=false;  4  InitQuene(Q);  5     for(i=0; i<G.n; i++)  6        if (!visited[i])  7        { visit(i); visited[i]=true; enQueue(Q, i);  8           while(!Empty(Q))  9           { u=delQueue(Q); 10              for(v=First_Adj(G,u);v!=-1;v=Next_Adj(G,u,v)) 11                 if(!visited[v]) 12                { visit(v); visted[v]=true; enQueue(Q, v); 13                 } // end of if !visited[v]
14            }   // end of while 
15          }      // end of if !visited[i]
16   delete []visited; 17 }
View Code

5. 求第1鄰接點和下一個鄰接點算法

 1 //鄰接矩陣
 2 int First_Adj(Graph &G, int u)  3 { for(v=0; v<G.n; v++) if(G.arcs[u][v]!=0) break;  4    if(v<G.n) return v;  5    return -1;  6 }  7 int  Next_Adj(Graph &G, int u, int v)  8 { for(++v; v<G.n; v++) if(G.arcs[u][v]) break;  9    if(v<G.n) return v; 10    return -1; 11 } 12 //鄰接表和十字鏈表
13 for(v=First_Adj(G, u); v!=-1; v=Next_Adj(G, u, v)) 14 //用如下循環語句代替
15 for(p=G.h[u].firstarc, v=p?p->j:-1; v!=-1; \ 16 p=p->nextarc, v=p?p->j:-1) 17 //若爲十字鏈表,則用如下循環語句代替
18 for(p=G.h[u].firstout, v=p?p->j:-1; v!=-1; \ 19 p=p->hlink, v=p?p->j:-1)
View Code

6. 圖的遍歷算法的複雜度

深度優先遍歷頂點訪問次序(從頂點v0出發):

求鄰接點次序不一樣,可獲得不一樣的訪問序列,如:v0, v2, v5, v6, v1, v3, v7, v4等
寬度優先遍歷頂點訪問次序(從頂點v0出發):

給定存儲結構示意圖,則遍歷次序惟一肯定:

從0出發深度優先次序:
0, 1, 4, 2, 3
從0出發寬度優先次序:
0, 1, 3, 4, 2
七、連通性與最小生成樹
  1. 連通性的判斷方法
   無向圖從任一頂點出發,若DFS或BFS可訪問全部頂點,則該圖是連通圖;
   有向圖從每一個頂點出發,若DFS或BFS都可訪問全部頂點,則該圖是強連通圖。
  2. 求連通分支  無向圖DFSTravel或BFSTravel過程當中,從頂點出發進行DFS或BFS的次數爲連通分支數。
  3. 求生成樹  DFSTravel或BFSTravel經歷的路徑和頂點構成連通分支的生成樹森林。若圖是連通的,則獲得生成樹。
  4.最小生成樹的概念:對於帶權無向圖(無向網),其全部生成樹中,邊上權值之和最小的稱爲最小生成樹。注意:最小生成樹的構形不必定惟一。
  5.最小生成樹生成算法的基本原理-MST性質
     MST性質:假設G=(V, E)是一個連通網,U是頂點V的一個非空子集。若(u, v)是知足條件u∈U且v∈V-U的全部邊中一條具備最小權值的邊,則必存在一棵包含邊(u, v)的最小生成樹。
  6.普里姆(Prim)算法
      算法思想:直接運用MST性質。
        假設G=(V, E)是連通網,TE是G上最小生成樹中邊的集合。算法從U={u0} (u0∈V)且TE={}開始,重複執行下列操做:
        在全部u∈U且v∈V-U的邊(u, v) 中找一條權值最小的邊(u', v')併入集合TE中,同時v'併入U,直到V=U爲止。
        最後,TE中必有n-1條邊。T=(V, TE)就是G的一棵最小生成樹。
       用Prim算法手工構造最小生成樹:記爲T1

    Prim算法的實現:
        設置輔助數組closedge[0..n-1],其中n表示無向連通網的頂點總數。
        設n個頂點組成的集合V={v0, v1, …, vn-1}且各頂點編號與closedge數組下標對應。若初始時U={v0},在Prim算法執行過程當中,對任意頂點vi屬於V-U,closedge[i]包含兩個域,即

   若頂點vi已併入集合U,則令closedge[i].lowcost=0;
   若頂點vi在V-U中,且與U中每一個頂點無邊相邊,可令closedge[i].lowcost=無窮。
   每趟從全部vi屬於V-U中(closedge[i].lowcost>0表示vi屬於V-U)選擇lowcost最小的vi,將vi併入集合U。
   假設每趟併入U集合的頂點爲vi,則
         a. 令closedge[i].lowcost=0;
         b. 調整其它lowcost>0的全部closedge元素,即
          對任意vj屬於V-U,若cost(vi, vj)<closedge[j].lowcost,則更新 closedge[j].lowcost=cost(vi, vj);closedge[j].vex=i不然,closedge[j]不更新。

  T1的closedge數組動態變化過程:(vex, lowcost )

  七、克魯斯卡爾(Kruskl)算法
      給定連通網N=(V, E),令最小生成樹的初始狀態爲只有n個頂點而無邊的非連通圖T,圖中每一個頂點自成一個連通份量。在E中選擇最小權重的邊,若該邊依附的頂點落在T中不一樣的連通份量中,則將該邊加入到T中,不然捨去此邊而選擇下一條權重最小的邊。依次類推,直到T中全部頂點都在同一連通份量上爲止。
核心:每次選擇一條具備最小權值、且跨越兩個不一樣連通份量的邊,使兩個不一樣連通份量變成一個連通份量。

Kruskl算法:需使用堆和求等價類算法,不用掌握。
Prim和Kruskl算法的時間複雜度
   Prim:  T(n)=O(n2), 適合邊多的稠密度
   Kruskl:  T(n)=O(elog2e),   適合邊少的稀疏圖
  八、最短路徑的概念
    a.給定n階有向或無向網N=(V,  E),其中,V={v0, v1, … , vn-1}。設P表示頂點vi到vj的一條路徑中所有邊(弧)組成的集合,則該條路徑的帶權路徑長度定義爲P中的全部邊(弧)的權值之和。頂點vi到vj的最短路徑是指vi到vj的全部路徑中帶權路徑長度最小的路徑。
   3點說明:
         頂點vi到vj的最短路徑不必定惟一;
        若vi到vj不連通,則vi到vj的最短路徑長度爲無窮大;
        對於n階無向網,頂點對的組合數爲n(n-1)/2,即共有n(n-1)/2個最短路徑;對於n階有向網,則總共有n(n-1)個最短路徑。
   b. 求最短路徑的迪傑斯特拉算法(Dijkstra)
      算法說明:
        對於n階網N=(V, E),Dijkstra算法按最短路徑長度遞增的次序求任意給定的某頂點(做爲始點)到其它的n-1個頂點的最短路徑。若須要求出所有頂點對間的最短路徑,必須以每一個頂點爲源點應用Dijkstra算法n次。
       首先,引入輔助向量dist[0..n-1],該向量用於存儲n-1條最短路徑的長度。設始點爲vk, 則算法結束後,dist[i](i不等於k)的值爲始點vk至頂點vi的最短路徑長度。
        初始化:dist[i]=wk,i   i=0, 1, 2, …, n-1
        其中,若vi鄰接自vk,則wk,i爲邊上權值,不然w(k,i)=無窮大。
            第1步:求n-1個最短路徑長度中的最小值以及對應路徑終點
                顯然,始點vk到其它n-1個頂點的最短路徑的最小值應爲依附於始點vk的全部邊(弧)中權值的最小值,對應路徑終點爲該最小權值邊(弧)依附的另外一鄰接點。
             故,最短的最短路徑的終點下標可用下式計算。
                (1)式中,arg表示求下標i,使得i知足條件:dist[i]是全部dist[]中的最小值。
              總之,若下標j知足(1)式,則vk至vj的最短路徑長度爲dist[j],且dist[j]是n-1個最短路徑中長度最短的。
            第2步:循環n-2趟(m=1, 2, … , n-2),
              按長度遞增次序生成其它最短路徑
              若視算法第1步爲第0趟,記第m(m=0, 1, … , n-2)趟生成的最短路徑終點下標爲jm,則必須使
          

6、實驗實現Prim算法

6.1.實驗內容
   用prim算法實現最小生成樹。
6.2.輸入與輸出
  輸入:採用文件形式輸入圖的節點數,弧的數目,用三元組的形式輸入弧的兩個節點以及權重。
  輸出:經過輸出連接生成樹的節點的次序以及對應邊的權重獲得最小生成樹。
6.3.關鍵數據結構與算法描述
  關鍵數據結構:無向圖的數據結構,closedge數組的數據結構。具體以下:

/***********************************************/ typedef struct network { int n;                //實際節點數
    int arcnum;           //弧的數目
    double w[MAXSIZE][MAXSIZE];//權重
}Network;//構建帶有權重的網絡圖結構
typedef  struct { double lowcost; //節點的最小權重域
    int      vex;   //節點的對應頂點位置
} CD_TP; /***********************************************/
View Code

算法描述:
    Prim算法的原理爲構建closedge數組,每一個節點有兩個域,分別爲對應於生成樹的最小權重的節點域以及該節點和最小生成樹對應的最小權重數lowcost。經過n-1次遍歷,每次遍歷都要加入一個與已有節點相鄰的最小頂點,而後更新剩餘節點的與最小生成樹對應的最小權重,以便進行下次遍歷,通過n-1次遍歷以後獲得n-1個與初始頂點相關的節點,同時也就是獲得了n-1條弧,構成n個節點的最小生成樹。具體算法以下:

/****************************************************/
for(i=0; i<G.n; i++)   //從k號頂點出發
 { closedge[i].vex=k; closedge[i].lowcost=G.w[k][i]; //定第k行,按行遍歷
 } cout<<"生成樹按照從第"<<k+1<<"節點依次鏈接的節點爲"<<endl; closedge[k].lowcost=0; //使第k行的權重由無窮大變爲0,加入生成樹
   cout<<k+1;              //輸出k號頂點,因從0開始
   for(m=0; m<G.n-1; m++)  //n-1趟循環
 { for(i=0; i<G.n; i++) if(closedge[i].lowcost>0) break; for(j=i+1; j<G.n; j++) if(closedge[j].lowcost>0&& closedge[j].lowcost<closedge[i].lowcost) i=j; //找到生成樹外的最小權重做爲添加對象
 cout<<","<<i+1; //輸出i號頂點,因從0開始
         closedge[i].lowcost=0;//添加進入生成樹
         for(j=0; j<G.n; j++) if(closedge[j].lowcost>0&& closedge[j].lowcost>G.w[i][j]) { closedge[j].lowcost=G.w[i][j]; //更新符合條件closedge的最小權重域
               closedge[j].vex=i;       //同時更新對應的節點關聯到目前最小權重關聯點i
 } } cout<<endl<<"該生成樹有"<<G.n<<"個節點,"<<G.arcnum<<"條弧"<<endl; cout<<"生成樹的"<<G.n-1<<"條邊及其權重爲:"<<endl; for(i=0; i<G.n; i++) if(i!=k) //k爲起始節點,與自身相隔無窮大
 { cout<<"("<<i+1<<","<<closedge[i].vex+1<<")"; cout<<"-"<<G.w[i][closedge[i].vex]<<endl;//與上面兩點對應
 } delete []closedge; /****************************************************/
View Code

6.4.理論與測試
  對下圖,通過5次遍歷便可獲得最小生成樹:

測試:在文件中輸入以下信息:

從v1開始遍歷運行程序獲得:

而後再構建一個無向圖以下:

最小生成樹以下:

 

在文件中輸入以下:

運行後輸出以下:

6.五、附錄(源代碼)

 1 #include "iostream"
 2 #include "stdio.h"
 3 #include"stdlib.h"
 4 using namespace std;  5 #define infinity 1000000//定義爲無窮大
 6 #define MAXSIZE  100    //節點最大數
 7 typedef struct network  8 {  9     int n;                //實際節點數
10     int arcnum;           //弧的數目
11     double w[MAXSIZE][MAXSIZE];//權重
12 }Network;//構建帶有權重的網絡圖結構
13 typedef  struct
14 { 15     double lowcost; //節點的最小權重域
16     int      vex;   //節點的對應頂點位置
17 } CD_TP; 18 //初始化圖 
19 void InitGraph(Network  &G) 20 { 21     FILE *fp; 22     int i,j; 23     int n; 24     int arcnum; 25     double weight; 26     if((fp=fopen("F:Network.txt","r"))==NULL) 27  { 28         printf("can't open the text!/n"); 29         exit(0); 30  } 31     fscanf(fp,"%d%d",&n,&arcnum); 32      G.n=n; 33      G.arcnum=arcnum; 34     for(i=0; i<G.n; i++) 35         for(j=0; j<G.n; j++) 36          G.w[i][j] = infinity;//初始化爲無窮大
37     
38     while(fscanf(fp,"%d%d%lf", &i, &j, &weight)!=EOF) 39  { 40      G.w[i-1][j-1] = weight;//依次讀入權重
41      G.w[j-1][i-1] = weight;//構建無向圖
42  } 43     fclose(fp); //關閉文件
44 } 45 void  prim(Network &G, int k)   //從頂點vk出發
46 { 47     CD_TP *closedge=new CD_TP[G.n]; 48    //初始化closedge
49     int i,j,m; 50    for(i=0; i<G.n; i++)   //從k號頂點出發
51  { 52        closedge[i].vex=k; 53        closedge[i].lowcost=G.w[k][i]; //定第k行,按行遍歷
54  } 55    cout<<"生成樹按照從第"<<k+1<<"節點依次鏈接的節點爲"<<endl; 56    closedge[k].lowcost=0; //使第k行的權重由無窮大變爲0,加入生成樹
57    cout<<k+1;              //輸出k號頂點,因從0開始
58    for(m=0; m<G.n-1; m++)  //n-1趟循環
59  { 60        for(i=0; i<G.n; i++) 61            if(closedge[i].lowcost>0) 62                break; 63         for(j=i+1; j<G.n; j++) 64            if(closedge[j].lowcost>0&&
65                closedge[j].lowcost<closedge[i].lowcost) 66                i=j; //找到生成樹外的最小權重做爲添加對象
67 
68          cout<<","<<i+1; //輸出i號頂點,因從0開始
69          closedge[i].lowcost=0;//添加進入生成樹
70          for(j=0; j<G.n; j++) 71            if(closedge[j].lowcost>0&&
72               closedge[j].lowcost>G.w[i][j]) 73  { 74                closedge[j].lowcost=G.w[i][j]; //更新符合條件closedge的最小權重域
75                closedge[j].vex=i;         //同時更新對應的節點關聯到目前最小權重關聯點i
76  } 77  } 78      cout<<endl<<"該生成樹有"<<G.n<<"個節點,"<<G.arcnum<<"條弧"<<endl; 79      cout<<"生成樹的"<<G.n-1<<"條邊及其權重爲:"<<endl; 80      for(i=0; i<G.n; i++) 81      if(i!=k) //k爲起始節點,與自身相隔無窮大
82  { 83          cout<<"("<<i+1<<","<<closedge[i].vex+1<<")"; 84          cout<<"-"<<G.w[i][closedge[i].vex]<<endl;//與上面兩點對應
85  } 86      delete []closedge; 87 } 88 void MainMenu() 89 { 90  Network G; 91  InitGraph(G); 92     prim(G, 2); 93 } 94 int main() 95 { 96  MainMenu(); 97     return 0; 98 }
View Code

7、dijkstra算法實驗

7.1.實驗內容
   用dijkstra算法求有向圖或無向圖最短路徑。
7.2.輸入與輸出
  輸入:用字符文件輸入圖的頂點數,弧數,以及三元組的包含下標和權重的鄰接矩陣。
  輸出:從某個源點出發所獲得的到其餘節點的最短路徑。
7.3.關鍵數據結構與算法描述
  關鍵數據結構:圖的鄰接矩陣結構,具體以下:

/************************************************/ typedef struct network { int n;           //實際頂點數
    int arcnum;      //實際弧的數目
    NetType G_Type;  //圖的類型,有向圖/無向圖
    int arcs[MX][MX];//鄰接矩陣
}Network;//構建圖的鄰接矩陣用來存儲權重 /***********************************************/
View Code

  算法描述:
   Dijkstra算法是計算源點到其餘節點的最短路徑的算法。要明白算法的核心,就要深入理解DIST數組的做用,path二維數組的含義和final數組的標誌。Dist數組存儲的是每一次遍歷後從源點到DIST下標各點的最短路徑,若無路徑則是無窮大。Path數組中path[i][j]爲真表示從j到i是連通的固然能夠間接連通。final[i]爲1的時侯表示源點到頂點i已經找到最短路徑。    
  算法的核心就是通過G.n-1次循環刷新dist,path和final數組從而獲得源點到各點的最短路徑長度和路徑走法。
  1.首先dist數組承接源點到各點的路徑長度,path數組初始化爲false。final初始化爲0;
  2.而後開始進行G.n-1次遍歷找到源點到其餘各點的最短路徑:從源點開始找到DIST數組之中對應final不爲1的全部元素中的最小值,將該最小值對應的頂點做爲「相對源點」(從該頂點開始搜索),其final值標記爲1.每次當final[i]爲假的時候若是「相對源點」對應的dist數值加上「相對頂點」到新頂點權重值(相對頂點和新頂點是鄰接關係)小於新頂點原來的dist值,則更新該dist[新頂點]的值。同時記錄從源點到該點的路徑,即在path數組中創建相應鏈接關係。之後的遍歷都是找到「相對源點」而後重複上步作法。直至遍歷結束。
  3.最後按照path數組和源點的對應關係就可打印出全部路徑以及各路徑的最短距離。
如下是dijkstra算法的核心部分:

 1 /**********************************************************/
 2 void ShorttestPath_DIJ(Network &G,int v,path &p,Dist &dist)  3 {  4     int w,nv;  5     for(nv=0;nv<G.n;nv++)  6  {  7       final[nv]=0;   //初始化爲0表示沒有找到最短路徑
 8       dist[nv]=G.arcs[v][nv];//將頂點v與其餘節點的權重值賦給dist數組 
 9       ps[nv]=dist[nv];   //同時ps數組中備份一份
10       for(w=0;w<G.n;w++) 11           p[nv][w]=false;  //初始化全部路徑都不通
12       if(dist[nv]<MX) 13  { 14           p[nv][v]=true;     //爲dist數組中有意義的權重加上路徑相通(v->nv) 
15           p[nv][nv]=true;    //同時自身也相通,爲之後的延續路徑作準備
16  } 17     }  //end for
18 
19     dist[v]=0;         //該節點確定不需遍歷且路徑長度爲0
20     final[v]=1;        //標記該節點
21     int min;        //最小值判斷未找到最短路徑中的最短路徑 22     //開始主循環 
23     for(int i=1;i<G.n;++i)   //G.n-1次循環,找到對應的最短路徑
24  { 25         min=MX; 26         for(w=0;w<G.n;++w) 27  { 28             if(final[w]==0)   //若待進行操做
29  { 30               if(dist[w]<min) 31  { 32                  v=w;       //則找到其中的最短路徑,且改變開始節點
33                  min=dist[w];  //最小值爲待計算路徑最小值
34  } 35  } 36                 
37  } 38             final[v]=1; //變換以後的v也已完成,需標記 
39             for(w=0;w<G.n;w++) 40             {   //若該節點未找到最短路徑而且知足以下條件則更新dist數組
41                 if(final[w]==0&&(min+G.arcs[v][w])<dist[w]) 42  { 43                     dist[w]=min+G.arcs[v][w];   //更新dist
44                     ps[w]=ps[v]+G.arcs[v][w]; //更新附帶最短路徑記錄
45                      for(int pos=0;pos<G.n;pos++) 46  { 47                         //注意此處是最重要的構建頂點鏈接和傳遞的語句
48                        p[w][pos]=p[v][pos];//v能到達的,新的w一定能到達
49  } 50                      p[w][w]=true;//自身也要標記,爲了p[w][pos]=p[v][pos]能傳遞下去 
51  } 52                 
53             }  //end for 
54     }  //end for 
55 } 56 /***************************************************/
57 如下是打印最短路徑的算法: 58 /***************************************************/
59 void DIJ_Print(Network &G,int start,path &P) 60 { 61    for(int i=1;i<G.n;i++) //最多打印G.n-1條 
62  { 63         if(final[i]==1&&ps[i]!=INFINITY)   //已找到最短路徑,則打印
64  { 65           cout<<"距離爲:"<<ps[i]<<"\t"; 66           cout<<start; 67           int m=start; //從起始頂點開始連接 
68           for(int j=1;j<G.n;j++) 69  { 70               if( P[i][j]==true)  //如有從j到i點的路徑 
71  { 72                   if(G.arcs[m][j]>0 && G.arcs[m][j]<INFINITY) 73  { 74                       cout<<"->"<<j; 75                       m=j; //更新起始節點爲當前輸出節點 
76                       j=1; //從新開始遍歷 
77  } 78  } 79           } //end for 
80           cout<<endl; 81         }  //end if
82    }// end for 
83 } 84 /***************************************************/
View Code

7.4.測試與理論
 理論:給定一個有向圖就能夠獲得源點到各點的最短路徑(固然除了從源點到不能連通的點)。
1.在文件中輸入以下數據構建鄰接矩陣

2.以下爲一有向圖

對其將v0做爲源點開始查找最短路徑可得:
V0~v2 最短路徑爲:v0-v2,長度爲:10
V0~v3 最短路徑爲:v0-v4-v3,長度爲:50
V0~v4 最短路徑爲:v0-v4,長度爲:30
V0~v5 最短路徑爲:v0-v4-v3-v5,長度爲:60
其餘的同理暫不贅述。
測試:
運行程序後爲:

注意從v5出發不能到達任何一點,故無最短路徑。

可見該算法是正確可行的。

7.五、附錄(源代碼)

 1 #include "iostream"   
 2 using namespace std;  3 #define MX     100          //數組長度最大值
 4 #define INFINITY  100000    //無窮大
 5 typedef bool path[MX][MX];  6 typedef int Dist[MX];//v0到vi的的距離 
 7 int ps[MX]={0}; //最短路徑值
 8 int final[MX];//final[i]=1表明已經求出v0到vi的最短路徑
 9 typedef enum
 10 {  11  DG,UDG  12 }NetType;  13 typedef struct network  14 {  15     int n;           //實際頂點數
 16     int arcnum;      //實際弧的數目
 17     NetType G_Type;  //圖的類型,有向圖/無向圖
 18     int arcs[MX][MX];//鄰接矩陣
 19 }Network;//構建圖的鄰接矩陣用來存儲權重
 20 /***************圖的初始化****************/
 21 void InitGraph(Network  &G)  22 {  23     FILE *fp;  24     int i,j;  25     int n;  26     int arcnum;  27     int weight;  28     if((fp=fopen("F:dist.txt","r"))==NULL)  29  {  30         printf("can't open the text!/n");  31         exit(0);  32  }  33     fscanf(fp,"%d%d",&n,&arcnum);  34      G.n=n;  35      G.arcnum=arcnum;  36     
 37     for(i=0; i<G.n; i++)  38         for(j=0; j<G.n; j++)  39          G.arcs[i][j] = INFINITY;//初始化爲無窮大
 40     while(fscanf(fp,"%d%d%d", &i, &j, &weight)!=EOF)  41  {  42      G.arcs[i][j] = weight;//依次讀入權重
 43  }  44     fclose(fp); //關閉文件
 45 }  46 /*=====================================================*/  
 47 void ShorttestPath_DIJ(Network &G,int v,path &p,Dist &dist)  48 {  49     int w,nv;  50     for(nv=0;nv<G.n;nv++)  51  {  52       final[nv]=0;   //初始化爲0表示沒有找到最短路徑
 53       dist[nv]=G.arcs[v][nv];//將頂點v與其餘節點的權重值賦給dist數組 
 54       ps[nv]=dist[nv];   //同時ps數組中備份一份
 55       for(w=0;w<G.n;w++)  56           p[nv][w]=false;  //初始化全部路徑都不通
 57       if(dist[nv]<MX)  58  {  59           p[nv][v]=true;     //爲dist數組中有意義的權重加上路徑相通(v->nv) 
 60           p[nv][nv]=true;    //同時自身也相通,爲之後的延續路徑作準備
 61  }  62     }  //end for
 63 
 64     dist[v]=0;         //該節點確定不需遍歷且路徑長度爲0
 65     final[v]=1;        //標記該節點
 66     int min;        //最小值判斷未找到最短路徑中的最短路徑  67     //開始主循環 
 68     for(int i=1;i<G.n;++i)   //G.n-1次循環,找到對應的最短路徑
 69  {  70         min=MX;  71         for(w=0;w<G.n;++w)  72  {  73             if(final[w]==0)   //若待進行操做
 74  {  75               if(dist[w]<min)  76  {  77                  v=w;       //則找到其中的最短路徑,且改變開始節點
 78                  min=dist[w];  //最小值爲待計算路徑最小值
 79  }  80  }  81                 
 82  }  83             final[v]=1; //變換以後的v也已完成,需標記 
 84             for(w=0;w<G.n;w++)  85             {   //若該節點未找到最短路徑而且知足以下條件則更新dist數組
 86                 if(final[w]==0&&(min+G.arcs[v][w])<dist[w])  87  {  88                     dist[w]=min+G.arcs[v][w];   //更新dist
 89                     ps[w]=ps[v]+G.arcs[v][w]; //更新附帶最短路徑記錄
 90                      for(int pos=0;pos<G.n;pos++)  91  {  92                         //注意此處是最重要的構建頂點鏈接和傳遞的語句
 93                        p[w][pos]=p[v][pos];//v能到達的,新的w一定能到達
 94  }  95                      p[w][w]=true;//自身也要標記,爲了p[w][pos]=p[v][pos]能傳遞下去 
 96  }  97                 
 98             }  //end for 
 99     }  //end for 
100 } 101 /*********************************打印路徑****************************/
102 void DIJ_Print(Network &G,int start,path &P) 103 { 104    for(int i=1;i<G.n;i++) //最多打印G.n-1條 
105  { 106         if(final[i]==1&&ps[i]!=INFINITY)   //已找到最短路徑,則打印
107  { 108           cout<<"距離爲:"<<ps[i]<<"\t"; 109           cout<<start; 110           int m=start; //從起始頂點開始連接 
111           for(int j=1;j<G.n;j++) 112  { 113               if( P[i][j]==true)  //如有從j到i點的路徑 
114  { 115                   if(G.arcs[m][j]>0 && G.arcs[m][j]<INFINITY) 116  { 117                       cout<<"->"<<j; 118                       m=j; //更新起始節點爲當前輸出節點 
119                       j=1; //從新開始遍歷 
120  } 121  } 122           } //end for 
123           cout<<endl; 124         }  //end if
125    }// end for 
126 } 127 /*******************算法控制中心**********************/
128 void ShortestPath(Network &G) 129 { 130     int start; 131     Dist D;       //D[i]表示從start到i的最短距離; 
132     path P;       //P[i,j]表示從start到i的最短路徑上會通過j 
133    
134     cout << "輸入出發點(0~"<<G.n-1<<")" << endl; 135     cin >> start; 136     if(start>=0 && start<G.n) 137  { 138        //調用迪傑斯特拉算法 
139  ShorttestPath_DIJ(G,start,P,D); 140       cout <<""<< start; 141       cout << "到其餘各點的最短路徑長度 :"<<endl ; 142       //調用迪傑斯特拉打印算法 
143  DIJ_Print(G,start,P); 144     }//endif 
145     else  
146         cout << "沒有這個地方!" << endl; 147 } 148 void MainMenu() 149 { 150  Network G; 151  InitGraph(G); 152     char choose; 153     cout << "************************" << endl; 154     cout << " a.計算最短路徑 " << endl; 155     cout << " b.退 出 " << endl; 156     cout << "************************" << endl; 157     cin >> choose; 158     while(1) 159  { 160         if( choose=='a' ) 161  { 162  ShortestPath(G); 163            cout << "************************" << endl; 164            cout << " a.計算最短路徑 " << endl; 165            cout << " b.退 出 " << endl; 166            cout << "************************" << endl; 167  } 168         else  if(choose=='b') 169  { 170             exit(0); 171  } 172         else  cout << "輸入錯誤,請從新輸入:"<<endl; 173         cin >> choose; 174  } 175 } 176 int main() 177 { 178  MainMenu(); 179     return 0; 180 }  
View Code

8、總結

  在這一塊中,講了不少東西,這些東西都是和圖這個數據結構相關的,圖是一種很是重要的數據結構,對圖的掌握可讓咱們更好的認識和理解網絡、城市等大型的拓撲結構,對於圖的一些算法也是很是的精妙和有趣的,有着很強的實用價值。

相關文章
相關標籤/搜索