圖、prim算法、dijkstra算法
一、圖的定義
圖(Graph)可以簡單表示為G=<V, E>,其中V稱為頂點(vertex)集合,E稱為邊(edge)集合。圖論中的圖(graph)表示的是頂點之間的鄰接關系。
(1) 無向圖(undirect graph)
E中的每條邊不帶方向,稱為無向圖。
(2) 有向圖(direct graph)
E中的每條邊具有方向,稱為有向圖。
(3) 混合圖
E中的一些邊不帶方向, 另一些邊帶有方向。
(4) 圖的階
指圖的頂點數目,即頂點集V中的元素個數。
(5) 多重圖
擁有平行邊或自環的圖。
(6) 簡單圖
不含平行邊和自環的圖.
(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的子圖。
二、 圖的路徑
(1)路徑(path)
圖G=<V, E>中,從任一頂點開始,由邊或弧的鄰接至關系構成的有限長頂點序列稱為路徑。注意:
有向圖的路徑必須沿弧的方向構成頂點序列;
構成路徑的頂點可能重復出現(即允許反復繞圈)。
(2) 路徑長度
路徑中邊或弧的數目。
(3) 簡單路徑
除第一個和最后一個頂點外,路徑中無其它重復出現的頂點,稱為簡單路徑。
(4) 回路或環(cycle)
路徑中的第一個頂點和最后一個頂點相同時,稱為回路或環。
三、圖的連通性
(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個頂點組成的最小無向和有向連通圖
(5) 生成樹
一個n階連通圖的生成樹是一個極小連通子圖,它包含圖中全部n個頂點以及保證該子圖是連通圖的最少的n-1條邊。
性質3:在生成樹上增加任何一條邊,必形成回路。
(6) 有向樹與生成森林
如果一個有向圖恰有一個頂點入度為0,其余頂點的入度均為1,則是一棵有向樹。一個有向圖的生成森林由若干棵有向樹組成,含有圖中全部頂點,但只有中以構成若干棵不相交的有向樹的弧。
四、圖的存儲結構
1.鄰接矩陣:若n階圖表示為G=<V, E>,其中V={v0, v1, …, vn-1},則定義
若圖G為n階網,則定義:
其中,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;
表結點(存儲邊或弧):

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

#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;
(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;
十字鏈表特點:表結點數等於弧的數目;求入度和出度都很方便。
有向圖十字鏈表存儲結構示意圖:
4. 無向圖的鄰接多重表
采用類似十字鏈表的思想實現無向圖存儲。任意邊(vi, vj)只存儲一個表結點,每個表結點有inext和jnext兩個指針域,inext指向關聯於頂點vi的下一條邊,而jnext指向關聯於頂點vj的下一邊條。頂點vi的頭結點僅含一個指針域,指向關聯於vi的第1條邊。
鄰接多重表存儲結構示意圖:
五、圖的遍歷和相關算法
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); }
4. 圖的寬度優先搜索(Breadth First Search)算法
搜索原則:
- 訪問遍歷出發頂點,該頂點入隊;
- 隊列不空,則隊頭頂點出隊;
- 訪問出隊頂點所有的未訪問鄰接點並將訪問的頂點入隊;
- 重復(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 }
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)
6. 圖的遍歷算法的復雜度
深度優先遍歷頂點訪問次序(從頂點v0出發):
求鄰接點次序不同,可得到不同的訪問序列,如:v0, v2, v5, v6, v1, v3, v7, v4等
寬度優先遍歷頂點訪問次序(從頂點v0出發):
給定存儲結構示意圖,則遍歷次序唯一確定:
從0出發深度優先次序:
0, 1, 4, 2, 3
從0出發寬度優先次序:
0, 1, 3, 4, 2
7、連通性與最小生成樹
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 )
7、克魯斯卡爾(Kruskl)算法
給定連通網N=(V, E),令最小生成樹的初始狀態為只有n個頂點而無邊的非連通圖T,圖中每個頂點自成一個連通分量。在E中選擇最小權重的邊,若該邊依附的頂點落在T中不同的連通分量中,則將該邊加入到T中,否則舍去此邊而選擇下一條權重最小的邊。依次類推,直到T中所有頂點都在同一連通分量上為止。
核心:每次選擇一條具有最小權值、且跨越兩個不同連通分量的邊,使兩個不同連通分量變成一個連通分量。
Kruskl算法:需使用堆和求等價類算法,不用掌握。
Prim和Kruskl算法的時間復雜度
Prim: T(n)=O(n2), 適合邊多的稠密度
Kruskl: T(n)=O(elog2e), 適合邊少的稀疏圖
8、最短路徑的概念
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,則必須使
六、實驗實現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; /***********************************************/
算法描述:
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; /****************************************************/
6.4.理論與測試
對下圖,經過5次遍歷即可得到最小生成樹:
測試:在文件中輸入如下信息:
從v1開始遍歷運行程序得到:
然后再構建一個無向圖如下:
最小生成樹如下:
在文件中輸入如下:
運行后輸出如下:
6.5、附錄(源代碼)

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

/************************************************/ typedef struct network { int n; //實際頂點數
int arcnum; //實際弧的數目
NetType G_Type; //圖的類型,有向圖/無向圖
int arcs[MX][MX];//鄰接矩陣
}Network;//構建圖的鄰接矩陣用來存儲權重 /***********************************************/
算法描述:
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 /***************************************************/
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.5、附錄(源代碼)

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 }
八、總結
在這一塊中,講了很多東西,這些東西都是和圖這個數據結構相關的,圖是一種非常重要的數據結構,對圖的掌握可以讓我們更好的認識和理解網絡、城市等大型的拓撲結構,對於圖的一些算法也是非常的精妙和有趣的,有着很強的實用價值。