图的应用
1. 圖的應(yīng)用總覽
?? ?在數(shù)據(jù)結(jié)構(gòu)中圖的應(yīng)用很廣泛,本文主要從以下四個(gè)方面介紹:
?? ?①最小生成樹:給定一個(gè)無(wú)向網(wǎng)絡(luò),在該網(wǎng)的所有生成樹中,使得各邊權(quán)數(shù)之和最小的那棵生成樹稱為該網(wǎng)的最小生成樹,也叫最小代價(jià)生成樹。
?? ?②拓?fù)渑判?#xff1a;由某個(gè)集合上的一個(gè)偏序得到該集合上的一個(gè)全序,這個(gè)操作稱之為拓?fù)渑判颉?br /> ?? ?③關(guān)鍵路徑:在AOE-網(wǎng)中有些活動(dòng)可以并行地進(jìn)行,所以完成工程的最短時(shí)間是從開始點(diǎn)到完成點(diǎn)的最長(zhǎng)路徑的長(zhǎng)度,路徑長(zhǎng)度最長(zhǎng)的路徑叫做關(guān)鍵路徑(Critical Path)。
?? ?④最短路徑:最短路徑問(wèn)題是圖研究中的一個(gè)經(jīng)典算法問(wèn)題,旨在尋找圖(由結(jié)點(diǎn)和路徑組成的)中兩結(jié)點(diǎn)之間的最短路徑。
2. 最小生成樹
問(wèn)題提出:
?? ?要在n個(gè)城市間建立通信聯(lián)絡(luò)網(wǎng)。頂點(diǎn):表示城市,權(quán):城市間通信線路的花費(fèi)代價(jià)。希望此通信網(wǎng)花費(fèi)代價(jià)最小。
問(wèn)題分析:
?? ?答案只能從生成樹中找,因?yàn)橐龅饺魏蝺蓚€(gè)城市之間有線路可達(dá),通信網(wǎng)必須是連通的;但對(duì)長(zhǎng)度最小的要求可以知道網(wǎng)中顯然不能有圈,如果有圈,去掉一條邊后,并不破壞連通性,但總代價(jià)顯然減少了,這與總代價(jià)最小的假設(shè)是矛盾的。
結(jié)論:
?? ?希望找到一棵生成樹,它的每條邊上的權(quán)值之和(即建立該通信網(wǎng)所需花費(fèi)的總代價(jià))最小 —— 最小代價(jià)生成樹。
?? ?構(gòu)造最小生成樹的算法很多,其中多數(shù)算法都利用了一種稱之為 MST 的性質(zhì)。
?? ?MST 性質(zhì):設(shè) N = (V, E) ?是一個(gè)連通網(wǎng),U是頂點(diǎn)集 V的一個(gè)非空子集。若邊 (u, v) 是一條具有最小權(quán)值的邊,其中u∈U,v∈V-U,則必存在一棵包含邊 (u, v) 的最小生成樹。
(1)普里姆 (Prim) 算法
算法思想:?
?? ?①設(shè) N=(V, E)是連通網(wǎng),TE是N上最小生成樹中邊的集合。
?? ?②初始令 U={u_0}, (u_0∈V), TE={ }。
?? ?③在所有u∈U,u∈U-V的邊(u,v)∈E中,找一條代價(jià)最小的邊(u_0,v_0 )。
?? ?④將(u_0,v_0 )并入集合TE,同時(shí)v_0并入U(xiǎn)。
?? ?⑤重復(fù)上述操作直至U = V為止,則 T=(V,TE)為N的最小生成樹。
?
代碼實(shí)現(xiàn):
(2)克魯斯卡爾 (Kruskal) 算法
算法思想:?
?? ?①設(shè)連通網(wǎng) ?N = (V, E ),令最小生成樹初始狀態(tài)為只有n個(gè)頂點(diǎn)而無(wú)邊的非連通圖,T=(V, { }),每個(gè)頂點(diǎn)自成一個(gè)連通分量。
?? ?②在 E 中選取代價(jià)最小的邊,若該邊依附的頂點(diǎn)落在T中不同的連通分量上(即:不能形成環(huán)),則將此邊加入到T中;否則,舍去此邊,選取下一條代價(jià)最小的邊。
③依此類推,直至 T 中所有頂點(diǎn)都在同一連通分量上為止。
? ? ??
?? ?最小生成樹可能不惟一!
3. 拓?fù)渑判?/h1>
(1)有向無(wú)環(huán)圖
(1)有向無(wú)環(huán)圖
?? ?無(wú)環(huán)的有向圖,簡(jiǎn)稱 DAG (Directed Acycline Graph) 圖。
?
有向無(wú)環(huán)圖在工程計(jì)劃和管理方面的應(yīng)用:除最簡(jiǎn)單的情況之外,幾乎所有的工程都可分為若干個(gè)稱作“活動(dòng)”的子工程,并且這些子工程之間通常受著一定條件的約束,例如:其中某些子工程必須在另一些子工程完成之后才能開始。
對(duì)整個(gè)工程和系統(tǒng),人們關(guān)心的是兩方面的問(wèn)題:?
①工程能否順利進(jìn)行;?
②完成整個(gè)工程所必須的最短時(shí)間。
對(duì)應(yīng)到有向圖即為進(jìn)行拓?fù)渑判蚝颓箨P(guān)鍵路徑。?
AOV網(wǎng):?
?? ?用一個(gè)有向圖表示一個(gè)工程的各子工程及其相互制約的關(guān)系,其中以頂點(diǎn)表示活動(dòng),弧表示活動(dòng)之間的優(yōu)先制約關(guān)系,稱這種有向圖為頂點(diǎn)表示活動(dòng)的網(wǎng),簡(jiǎn)稱AOV網(wǎng)(Activity On Vertex network)。
例如:排課表
? ? ??
AOV網(wǎng)的特點(diǎn):
①若從i到j(luò)有一條有向路徑,則i是j的前驅(qū);j是i的后繼。
②若< i , j >是網(wǎng)中有向邊,則i是j的直接前驅(qū);j是i的直接后繼。
③AOV網(wǎng)中不允許有回路,因?yàn)槿绻谢芈反嬖?#xff0c;則表明某項(xiàng)活動(dòng)以自己為先決條件,顯然這是荒謬的。
問(wèn)題: ? ?
?? ?問(wèn)題:如何判別 AOV 網(wǎng)中是否存在回路?
?? ?檢測(cè) AOV 網(wǎng)中是否存在環(huán)方法:對(duì)有向圖構(gòu)造其頂點(diǎn)的拓?fù)溆行蛐蛄?#xff0c;若網(wǎng)中所有頂點(diǎn)都在它的拓?fù)溆行蛐蛄兄?#xff0c;則該AOV網(wǎng)必定不存在環(huán)。
拓?fù)渑判虻姆椒?#xff1a;
?? ?①在有向圖中選一個(gè)沒(méi)有前驅(qū)的頂點(diǎn)且輸出之。
?? ?②從圖中刪除該頂點(diǎn)和所有以它為尾的弧。
?? ?③重復(fù)上述兩步,直至全部頂點(diǎn)均已輸出;或者當(dāng)圖中不存在無(wú)前驅(qū)的頂點(diǎn)為止。
? ? ? ??
?? ?一個(gè)AOV網(wǎng)的拓?fù)湫蛄胁皇俏ㄒ坏?#xff01;
代碼實(shí)現(xiàn):
?
關(guān)鍵路徑
?? ?把工程計(jì)劃表示為有向圖,用頂點(diǎn)表示事件,弧表示活動(dòng),弧的權(quán)表示活動(dòng)持續(xù)時(shí)間。每個(gè)事件表示在它之前的活動(dòng)已經(jīng)完成,在它之后的活動(dòng)可以開始。稱這種有向圖為邊表示活動(dòng)的網(wǎng),簡(jiǎn)稱為 AOE網(wǎng) (Activity On Edge)。
例如:
設(shè)一個(gè)工程有11項(xiàng)活動(dòng),9個(gè)事件。
事件v_1——表示整個(gè)工程開始(源點(diǎn))?
事件v_9——表示整個(gè)工程結(jié)束(匯點(diǎn))
?
對(duì)AOE網(wǎng),我們關(guān)心兩個(gè)問(wèn)題: ?
①完成整項(xiàng)工程至少需要多少時(shí)間??
②哪些活動(dòng)是影響工程進(jìn)度的關(guān)鍵?
關(guān)鍵路徑——路徑長(zhǎng)度最長(zhǎng)的路徑。
路徑長(zhǎng)度——路徑上各活動(dòng)持續(xù)時(shí)間之和。
v_i——表示事件v_i的最早發(fā)生時(shí)間。假設(shè)開始點(diǎn)是v_1,從v_1到〖v?i〗的最長(zhǎng)路徑長(zhǎng)度。?(?)——表示活動(dòng)a_i的最早發(fā)生時(shí)間。
l(?)——表示活動(dòng)a_i最遲發(fā)生時(shí)間。在不推遲整個(gè)工程完成的前提下,活動(dòng)a_i最遲必須開始進(jìn)行的時(shí)間。
l(?)-?(?)意味著完成活動(dòng)a_i的時(shí)間余量。
我們把l(?)=?(?)的活動(dòng)叫做關(guān)鍵活動(dòng)。顯然,關(guān)鍵路徑上的所有活動(dòng)都是關(guān)鍵活動(dòng),因此提前完成非關(guān)鍵活動(dòng)并不能加快工程進(jìn)度。
?? ?例如上圖中網(wǎng),從從v_1到v_9的最長(zhǎng)路徑是(v_1,v_2,v_5,v_8,ν_9 ),路徑長(zhǎng)度是18,即ν_9的最遲發(fā)生時(shí)間是18。而活動(dòng)a_6的最早開始時(shí)間是5,最遲開始時(shí)間是8,這意味著:如果a_6推遲3天或者延遲3天完成,都不會(huì)影響整個(gè)工程的完成。因此,分析關(guān)鍵路徑的目的是辨別哪些是關(guān)鍵活動(dòng),以便爭(zhēng)取提高關(guān)鍵活動(dòng)的工效,縮短整個(gè)工期。
?? ?由上面介紹可知:辨別關(guān)鍵活動(dòng)是要找l(?)=?(?)的活動(dòng)。為了求?(?)和l(?),首先應(yīng)求得事件的最早發(fā)生時(shí)間v?(j)和最遲發(fā)生時(shí)間vl(j)。如果活動(dòng)a_i由弧?j,k?表示,其持續(xù)時(shí)間記為dut(?j,k?),則有如下關(guān)系:
?(?)= v?(j)
l(?)=vl(k)-dut(?j,k?)
?? ?求v?(j)和vl(j)需分兩步進(jìn)行:
第一步:從v?(0)=0開始向前遞推
v?(j)=Max{v?(i)+dut(?j,k?)} ? ?i,j?∈T,j=1,2,…,n-1
其中,T是所有以第j個(gè)頂點(diǎn)為頭的弧的集合。
第二步:從vl(n-1)=v?(n-1)起向后遞推
vl(i)=Min{vl(j)-dut(?i,j?)} ??i,j?∈S,i=n-2,…,0
其中,S是所有以第i個(gè)頂點(diǎn)為尾的弧的集合。
下面我們以上圖AOE網(wǎng)為例,先求每個(gè)事件v_i的最早發(fā)生時(shí)間,再逆向求每個(gè)事件對(duì)應(yīng)的最晚發(fā)生時(shí)間。再求每個(gè)活動(dòng)的最早發(fā)生時(shí)間和最晚發(fā)生時(shí)間,如下面表格:
? ? ? ? ??
在活動(dòng)的統(tǒng)計(jì)表中,活動(dòng)的最早發(fā)生時(shí)間和最晚發(fā)生時(shí)間相等的,就是關(guān)鍵活動(dòng)
關(guān)鍵路徑的討論:
①若網(wǎng)中有幾條關(guān)鍵路徑,則需加快同時(shí)在幾條關(guān)鍵路徑上的關(guān)鍵活動(dòng)。 ? ? ?如:a11、a10、a8、a7。?
②如果一個(gè)活動(dòng)處于所有的關(guān)鍵路徑上,則提高這個(gè)活動(dòng)的速度,就能縮短整個(gè)工程的完成時(shí)間。如:a1、a4。
③處于所有關(guān)鍵路徑上的活動(dòng)完成時(shí)間不能縮短太多,否則會(huì)使原關(guān)鍵路徑變成非關(guān)鍵路徑。這時(shí)必須重新尋找關(guān)鍵路徑。如:a1由6天變成3天,就會(huì)改變關(guān)鍵路徑。
關(guān)鍵路徑算法實(shí)現(xiàn):
int CriticalPath(ALGraph G) { //因?yàn)镚是有向網(wǎng),輸出G的各項(xiàng)關(guān)鍵活動(dòng)SqStack T;int i, j; ArcNode* p;int k , dut;if(!TopologicalOrder(G,T))return 0;int vl[VexNum];for (i = 0; i < VexNum; i++)vl[i] = ve[VexNum - 1]; //初始化頂點(diǎn)事件的最遲發(fā)生時(shí)間while (T.base != T.top) //按拓?fù)淠嫘蚯蟾黜旤c(diǎn)的vl值{for(pop(&T, &j), p = G.vertices[j].firstarc; p; p = p->nextarc){k = p->adjvex; dut = *(p->info); //dut<j, k>if(vl[k] - dut < vl[j])vl[j] = vl[k] - dut;}//for}//whilefor(j = 0; j < G.vexnum; ++j) //求ee,el和關(guān)鍵活動(dòng){for (p = G.vertices[j].firstarc; p; p = p->nextarc){int ee, el; char tag;k = p->adjvex; dut = *(p->info);ee = ve[j]; el = vl[k] - dut;tag = (ee == el) ? '*' : ' ';PrintCriticalActivity(G,j,k,dut,ee,el,tag);}}return 1; }?
?
4.最短路
?? ?典型用途:交通網(wǎng)絡(luò)的問(wèn)題——從甲地到乙地之間是否有公路連通?在有多條通路的情況下,哪一條路最短?
?
?? ?交通網(wǎng)絡(luò)用有向網(wǎng)來(lái)表示:頂點(diǎn)——表示城市,弧——表示兩個(gè)城市有路連通,弧上的權(quán)值——表示兩城市之間的距離、交通費(fèi)或途中所花費(fèi)的時(shí)間等。
?? ?如何能夠使一個(gè)城市到另一個(gè)城市的運(yùn)輸時(shí)間最短或運(yùn)費(fèi)最省?這就是一個(gè)求兩座城市間的最短路徑問(wèn)題。
?? ?問(wèn)題抽象:在有向網(wǎng)中A點(diǎn)(源點(diǎn))到達(dá)B點(diǎn)(終點(diǎn))的多條路徑中,尋找一條各邊權(quán)值之和最小的路徑,即最短路徑。最短路徑與最小生成樹不同,路徑上不一定包含n個(gè)頂點(diǎn),也不一定包含n - 1條邊。
? ?常見最短路徑問(wèn)題:單源點(diǎn)最短路徑、所有頂點(diǎn)間的最短路徑
(1)如何求得單源點(diǎn)最短路徑?
?? ?窮舉法:將源點(diǎn)到終點(diǎn)的所有路徑都列出來(lái),然后在其中選最短的一條。但是,當(dāng)路徑特別多時(shí),特別麻煩;沒(méi)有規(guī)律可循。
?? ?迪杰斯特拉(Dijkstra)算法:按路徑長(zhǎng)度遞增次序產(chǎn)生各頂點(diǎn)的最短路徑。
路徑長(zhǎng)度最短的最短路徑的特點(diǎn):
?? ?在此路徑上,必定只含一條弧 <v_0, v_1>,且其權(quán)值最小。由此,只要在所有從源點(diǎn)出發(fā)的弧中查找權(quán)值最小者。
下一條路徑長(zhǎng)度次短的最短路徑的特點(diǎn):
①、直接從源點(diǎn)到v_2<v_0, v_2>(只含一條弧);
②、從源點(diǎn)經(jīng)過(guò)頂點(diǎn)v_1,再到達(dá)v_2<v_0, v_1>,<v_1, v_2>(由兩條弧組成)
再下一條路徑長(zhǎng)度次短的最短路徑的特點(diǎn):
?? ?有以下四種情況:
?? ?①、直接從源點(diǎn)到v_3<v_0, v_3>(由一條弧組成);
?? ?②、從源點(diǎn)經(jīng)過(guò)頂點(diǎn)v_1,再到達(dá)v_3<v_0, v_1>,<v_1, v_3>(由兩條弧組成);
?? ?③、從源點(diǎn)經(jīng)過(guò)頂點(diǎn)v_2,再到達(dá)v_3<v_0, v_2>,<v_2, v_3>(由兩條弧組成);
?? ?④、從源點(diǎn)經(jīng)過(guò)頂點(diǎn)v_1 ?,v_2,再到達(dá)v_3<v_0, v_1>,<v_1, v_2>,<v_2, v_3>(由三條弧組成);
其余最短路徑的特點(diǎn):?? ?
?? ?①、直接從源點(diǎn)到v_i<v_0, v_i>(只含一條弧);
?? ?②、從源點(diǎn)經(jīng)過(guò)已求得的最短路徑上的頂點(diǎn),再到達(dá)v_i(含有多條弧)。
Dijkstra算法步驟:
?? ?初始時(shí)令S={v_0}, ?T={其余頂點(diǎn)}。T中頂點(diǎn)對(duì)應(yīng)的距離值用輔助數(shù)組D存放。
?? ?D[i]初值:若<v_0, v_i>存在,則為其權(quán)值;否則為∞。?
?? ?從T中選取一個(gè)其距離值最小的頂點(diǎn)v_j,加入S。對(duì)T中頂點(diǎn)的距離值進(jìn)行修改:若加進(jìn)v_j作中間頂點(diǎn),從v_0到v_i的距離值比不加 vj 的路徑要短,則修改此距離值。
?? ?重復(fù)上述步驟,直到 S = V 為止。
算法實(shí)現(xiàn):
void ShortestPath_DIJ(MGraph G,int v0,PathMatrix &P,ShortPathTable &D) { // 用Dijkstra算法求有向網(wǎng) G 的 v0 頂點(diǎn)到其余頂點(diǎn)v的最短路徑P[v]及帶權(quán)長(zhǎng)度D[v]。// 若P[v][w]為TRUE,則 w 是從 v0 到 v 當(dāng)前求得最短路徑上的頂點(diǎn)。 P是存放最短路徑的矩陣,經(jīng)過(guò)頂點(diǎn)變成TRUE// final[v]為TRUE當(dāng)且僅當(dāng) v∈S,即已經(jīng)求得從v0到v的最短路徑。int v,w,i,j,min;Status final[MAX_VERTEX_NUM];for(v = 0 ;v < G.vexnum ;++v){final[v] = FALSE;D[v] = G.arcs[v0][v].adj; //將頂點(diǎn)數(shù)組中下標(biāo)對(duì)應(yīng)是 v0 和 v的距離給了D[v]for(w = 0;w < G.vexnum; ++w)P[v][w] = FALSE; //設(shè)空路徑if(D[v] < INFINITY){P[v][v0] = TRUE;P[v][v] = TRUE;}}D[v0]=0;final[v0]= TRUE; /* 初始化,v0頂點(diǎn)屬于S集 */for(i = 1;i < G.vexnum; ++i) /* 其余G.vexnum-1個(gè)頂點(diǎn) */{ /* 開始主循環(huán),每次求得v0到某個(gè)v頂點(diǎn)的最短路徑,并加v到S集 */min = INFINITY; /* 當(dāng)前所知離v0頂點(diǎn)的最近距離 */for(w = 0;w < G.vexnum; ++w)if(!final[w]) /* w頂點(diǎn)在V-S中 */if(D[w] < min){v = w;min = D[w];} /* w頂點(diǎn)離v0頂點(diǎn)更近 */final[v] = TRUE; /* 離v0頂點(diǎn)最近的v加入S集 */for(w = 0;w < G.vexnum; ++w) /* 更新當(dāng)前最短路徑及距離 */{if(!final[w] && min < INFINITY && G.arcs[v][w].adj < INFINITY && (min + G.arcs[v][w].adj < D[w])){ /* 修改D[w]和P[w],w∈V-S */D[w] = min + G.arcs[v][w].adj;for(j = 0;j < G.vexnum;++j)P[w][j] = P[v][j];P[w][w] = TRUE;}}} }總結(jié)
- 上一篇: 树状数组实现
- 下一篇: leetcode360. 有序转化数组