0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

圖論算法有圖有代碼

算法與數(shù)據(jù)結(jié)構(gòu) ? 來(lái)源:CSDN博客 ? 作者:NoMasp柯于旺 ? 2022-11-22 11:28 ? 次閱讀

圖的定義

背景知識(shí)

看到這篇博客相信一開始映入讀者眼簾的就是下面這幅圖了,這就是傳說(shuō)中的七橋問(wèn)題(哥尼斯堡橋問(wèn)題)。在哥尼斯堡,普雷格爾河環(huán)繞著奈佛夫島(圖中的A島)。這條河將陸地分成了下面4個(gè)區(qū)域,該處還有著7座連接這些陸地的橋梁。

e40dbe14-6a14-11ed-8abf-dac502259ad0.jpg

問(wèn)題是如何從某地出發(fā),依次沿著各個(gè)橋,必須經(jīng)過(guò)每座橋且每座橋只能經(jīng)過(guò)1次,最終回到原地。

不知道這個(gè)問(wèn)題且好奇的童鞋現(xiàn)在肯定在忙活著找出來(lái)這道題的結(jié)果了。

是偉大的數(shù)學(xué)家歐拉(Leonhard Euler)在1736年首次使用圖的方法解決了該問(wèn)題。

歐拉將上面的模型轉(zhuǎn)換成了下面這種”圖“的形式。

e41fb470-6a14-11ed-8abf-dac502259ad0.jpg

歐拉把頂點(diǎn)的度定義為與該頂點(diǎn)相關(guān)聯(lián)的邊的條數(shù),并且他證明了存在從任意點(diǎn)出發(fā),經(jīng)過(guò)所有邊恰好一次,并最終回到出發(fā)頂點(diǎn)的走法的充分必要條件是:每個(gè)頂點(diǎn)的度均為偶數(shù)。人們稱之為歐拉閉跡(Eulerian walk)。

簡(jiǎn)要定義

圖(graph)G=(V,E)由頂點(diǎn)(vertex)的集V和邊(Edge)的集E組成。頂點(diǎn)代表了對(duì)象,在示意圖中我們使用點(diǎn)或圓來(lái)表示它;邊代表了兩個(gè)對(duì)象的連接關(guān)系,在示意圖中我們使用連接兩頂點(diǎn)的線段來(lái)表示。

有時(shí)也把邊稱作弧(arc),如果點(diǎn)對(duì)(v,w)是有序的,那么圖就叫做有向的圖(有向圖)。如果點(diǎn)對(duì)(v,w)是無(wú)序的,那么圖就叫做無(wú)向的圖(無(wú)向圖)。簡(jiǎn)單的講,邊沒(méi)有指向性的圖叫做無(wú)向圖,邊具有指向性的圖叫做有向圖。

頂點(diǎn)v和w鄰接(adjacent)當(dāng)且僅當(dāng)(v,w)屬于E。

我們可以給邊賦予各式的屬性,比如權(quán)值(cost)。權(quán)值可以表示從一個(gè)頂點(diǎn)到另一個(gè)頂點(diǎn)的距離,也可以表示一個(gè)頂點(diǎn)到另一個(gè)頂點(diǎn)說(shuō)話費(fèi)的代價(jià)(比如時(shí)間、金錢等)。一個(gè)邊上帶權(quán)值的圖稱為網(wǎng)絡(luò)(network)。

如果無(wú)向圖中從每一個(gè)頂點(diǎn)到其他每個(gè)頂點(diǎn)都存在一條路徑,則稱該無(wú)向圖是連通的(connected)。具有這樣性質(zhì)的有向圖稱為是強(qiáng)連通的的(strongly connected)。如果有向圖不是強(qiáng)連通的,但它的基礎(chǔ)圖(underlying graph)(也就是其弧上去掉方向說(shuō)形成的圖)是連通的,那么稱該有向圖是弱連通的(weakly connected)。完全圖(complete graph)是其每一對(duì)頂點(diǎn)間都存在一條邊的圖。

e42eb07e-6a14-11ed-8abf-dac502259ad0.jpg

所謂入度(indegree)是指的頂點(diǎn)v的邊(u,v)的條數(shù)。

e43b21d8-6a14-11ed-8abf-dac502259ad0.jpg

如下表示了一個(gè)有著7個(gè)頂點(diǎn)和12條邊的有向圖。

e4514efe-6a14-11ed-8abf-dac502259ad0.jpg

如果具有n個(gè)頂點(diǎn),e條邊的圖G的頂點(diǎn)i的度為di,則G的邊數(shù)為:

e=∑n?10di2

以上這個(gè)數(shù)學(xué)公式的markdown“源碼”:
$ e =frac { sum_{0}^{n-1} d_i} {2} $

現(xiàn)在將圖看作抽象數(shù)據(jù)類型,下面給出ADT圖的結(jié)構(gòu):

functions 對(duì)于所有的graph∈Graph,v,v1,v2∈Vertices
Graph Create() return一個(gè)空?qǐng)D
Graph InsertVertex (graph, v) 向圖graph中插入沒(méi)有關(guān)聯(lián)邊的新頂點(diǎn)v,return改變后的圖
Graph InsertEdge (graph,v1,v2) 在圖graph的頂點(diǎn)v1和v2之間插入一條邊,return改變后的圖
Graph DeleteVertex (graph, v) 刪除圖graph的頂點(diǎn)v及與其關(guān)聯(lián)的所有邊,return改變后的圖
Graph DeleteEdge (graph,v1,v2) 刪除圖graph的邊(v1,v2),頂點(diǎn)v1,v2不刪除,return改變后的圖
Boolean IsEmpty (graph) if(graph==空?qǐng)D) return TRUE,else return FALSE
List Adjacent (graph, v) return頂點(diǎn)v的所有鄰接結(jié)點(diǎn)

圖的存儲(chǔ)表示方式

圖主要有3種常用的存儲(chǔ)表示方式:鄰接矩陣(adjacency matrices),鄰接表(adjacency lists),鄰接多重表(adjacency multilists)。

鄰接矩陣

鄰接矩陣使用|V|?|V|的二維數(shù)組來(lái)表示圖。g[i][j]表示的是頂點(diǎn)i和頂點(diǎn)j的關(guān)系。

1)因?yàn)樵跓o(wú)向圖中,我們只需要知道頂點(diǎn)i和頂點(diǎn)j是否是相連的,因此我們只需要將g[i][j]和g[j][j]設(shè)置為1或是0表示相連或不相連即可。如下圖所示。

e45dac76-6a14-11ed-8abf-dac502259ad0.jpg

2)而在有向圖中,我們只需要知道是否有從頂點(diǎn)i到頂點(diǎn)j的邊,因此如果頂點(diǎn)i有一條指向頂點(diǎn)j的邊,那么g[i][j]就設(shè)為1,否則設(shè)為0。有向圖與無(wú)向圖不同,并不需要滿足g[i][j]=g[j][i]。

e46e8046-6a14-11ed-8abf-dac502259ad0.jpg

3)在帶權(quán)值的圖中,g[i][j]表示的是頂點(diǎn)i到頂點(diǎn)j的邊的權(quán)值。由于在邊不存在的情況下,如果將g[i][j]設(shè)為0,就無(wú)法和權(quán)值為0的情況區(qū)分開來(lái),因此選取適當(dāng)?shù)妮^大的常數(shù)INF(只要能和普通的權(quán)值區(qū)別開來(lái)就可以了),然后令g[i][j]=INF就好了。當(dāng)然,在無(wú)向圖中還是要保持g[i][j]=g[j][i]。在一條邊上有多種不帶權(quán)值的情況下,定義多個(gè)同樣的|V|?|V|數(shù)組,或者是使用結(jié)構(gòu)體或類作為數(shù)組的元素,就可以和原來(lái)一樣對(duì)圖進(jìn)行處理了。

e47d2f2e-6a14-11ed-8abf-dac502259ad0.jpg

使用這種存儲(chǔ)方式,可以很方便地判斷任意兩個(gè)頂點(diǎn)之間是否有邊以及確定頂點(diǎn)的度,這也是這種表示法最大的優(yōu)勢(shì)。任意一個(gè)頂點(diǎn)i的度等于其鄰接矩陣中頂點(diǎn)i所對(duì)應(yīng)的行中的數(shù)字之和:

∑n?1j=0adjmat[i][j]

以上這個(gè)數(shù)學(xué)公式的markdown“源碼”:

$ sum_{j=0}^{n-1} g[i][j] $

在這種表示法中掃描所有邊至少需要O(n2)時(shí)間,因?yàn)楸仨殭z查矩陣中的n2?n個(gè)元素才能確定圖中邊的條數(shù)(鄰接矩陣對(duì)角線上的n個(gè)元素都是0,因此不用檢查。又因?yàn)闊o(wú)向圖的鄰接矩陣是對(duì)稱的,實(shí)際只需檢查鄰接矩陣的一半元素)。通常把邊很少的圖成為稀疏圖(sparse graphs)。

鄰接表

如果用鄰接矩陣表示稀疏圖就會(huì)浪費(fèi)大量?jī)?nèi)存空間,而用鏈接表,則是通過(guò)把頂點(diǎn)所能到的頂點(diǎn)的邊保存在鏈表中來(lái)表示圖,這樣就只需要O(|V|+|E|)的內(nèi)存空間。

e48ba234-6a14-11ed-8abf-dac502259ad0.jpg

而所謂的鄰接表,就是用n個(gè)鏈表代替鄰接矩陣中的n行。鏈表中的結(jié)點(diǎn)結(jié)構(gòu)至少要包含一個(gè)頂點(diǎn)域和一個(gè)鏈域。對(duì)于任意給定的鏈表i,鏈表中的結(jié)點(diǎn)就是與頂點(diǎn)i相鄰的所有頂點(diǎn)。鄰接表存儲(chǔ)聲明的C語(yǔ)言聲明如下:

#define MAX_VERTICES 50

typedef struct node *node-pointer;

typedef struct node

{

int vertex;

struct node *link;

};

node_pointer graph[MAX_VERTICES];

int n=0;

鄰接多重表

在無(wú)向圖的鄰接表存儲(chǔ)表示中,每一條邊(vi,vj)都表示為兩項(xiàng):一項(xiàng)在頂點(diǎn)vi的鄰接表中,而另一項(xiàng)在頂點(diǎn)vj的鄰接表中。在多重表中,各鏈表中的結(jié)點(diǎn)可以被幾個(gè)鏈表共享,此時(shí)圖中的每一條邊只對(duì)應(yīng)于一個(gè)結(jié)點(diǎn),而這個(gè)結(jié)點(diǎn)出現(xiàn)在該邊所關(guān)聯(lián)的兩個(gè)頂點(diǎn)的每個(gè)鄰接鏈表中。如下圖所示:

鄰接多重表結(jié)點(diǎn)結(jié)構(gòu)的C語(yǔ)言聲明為:

typedef struct edge *edge-pointer

typedef struct edge

{

short int marked;

int vertex1;

int vertex2;

edge_pointer path1;

edge_pointer path2;

};

圖的基本操作和算法

廣度優(yōu)先搜索

請(qǐng)先忽視下圖中所有的下標(biāo),讓我們從頭開始。隨意選擇一個(gè)點(diǎn),此處選擇v3,作為切入點(diǎn)。因此到v3的距離為0。從v3出發(fā),距離為1的結(jié)點(diǎn)是v1和v6;繼續(xù)下一步,v6已經(jīng)無(wú)路可走,而與v1距離為1的是v2和v4,因此對(duì)它們標(biāo)記上2;繼續(xù)下去,v2和v4走一步都可以到v5,v4走一步可以到v7,因此v5和v7被標(biāo)記為3。至此搜索便結(jié)束了。

這就是廣度優(yōu)先搜索(breadth-first search),該方法按層處理頂點(diǎn)。距起始點(diǎn)最近的那些頂點(diǎn)首先被求值,最遠(yuǎn)點(diǎn)則最后被求值,這很像對(duì)樹的層序遍歷(level-order traversal)。

e49a3fb0-6a14-11ed-8abf-dac502259ad0.jpg

為了實(shí)現(xiàn)廣度優(yōu)先搜索,可以使用動(dòng)態(tài)鏈接隊(duì)列。在隊(duì)列中的每個(gè)頂點(diǎn)都包含兩個(gè)域:頂點(diǎn)的序號(hào)和鏈接指針。

函數(shù)bfs所使用的隊(duì)列的定義和函數(shù)原型聲明為:

typedef struct queue *queue_pointer;

typedef struct queue

{

int vertex;

queue_pointer link;

};

void addq(queue_pointer *, queue_pointer *,int);

int deleteq(queue_pointer *);

圖的廣度優(yōu)先搜索算法:

void bfs(int v)

{

node_pointer w;

queue_pointer front,rear;

front=rear=NULL;

printf("%5d",v);

visited[v]=TRUE;

addq(&front,&rear,v);

while(front)

{

v=deleteq(&front);

for(w=graph[v];w;w=w->link)

{

if(!visited[w->vertex])

{

printf("%5d",w->vertex);

addq(&front,&rear,w->vertex);

visited[w->vertex]=TRUE;

}

}

}

}

圖中每個(gè)頂點(diǎn)都被存入隊(duì)列一次,所以該算法中的while循環(huán)至多重復(fù)n次。如果采用鄰接表存儲(chǔ)表示,那么該算法所需要的時(shí)間為:

d0+d1+…+dn?1=O(e)

其中di為頂點(diǎn)vi的度。

而如果采用鄰接矩陣來(lái)實(shí)現(xiàn),那么對(duì)于每個(gè)頂點(diǎn)的訪問(wèn),while循環(huán)的時(shí)間為O(n),所以算法的總耗時(shí)為O(n2)。和接下來(lái)的深度優(yōu)先搜索一樣,一次廣度優(yōu)先搜索訪問(wèn)到的頂點(diǎn)以及與這些頂點(diǎn)相關(guān)聯(lián)的邊形成的圖G的一個(gè)連通分支。

深度優(yōu)先搜索

深度優(yōu)先搜索內(nèi)容較多,已經(jīng)在下文中單獨(dú)列出。

連通圖

使用以上的兩種搜索算法也可以用來(lái)判斷一個(gè)無(wú)向圖是否是連通的。具體步驟如下:

1.調(diào)用bfs(0)或dfs(0)

2.檢查是否存在未被訪問(wèn)過(guò)的頂點(diǎn)

具體代碼如下:

void connected(void)

{

int i;

for(i=0;i

{

if(!visited[i])

{

dfs(i);

printf(" ");

}

}

}

算法分析:如果采用鄰接表存儲(chǔ),那么函數(shù)dfs時(shí)間開銷為O(e)。這里for循環(huán)的時(shí)間開銷為O(n),所以整個(gè)算法的時(shí)間復(fù)雜性為O(n+e)。

雙連通圖

雙聯(lián)通圖(biconnected graph)是沒(méi)有關(guān)節(jié)點(diǎn)的連通圖。對(duì)此有一個(gè)比較重要的公式如下:

low(u) = min{dfn(u), min{low(w)|w是u的兒子}, min{dfn(w)|(u,w)是一條回退邊} }

回退邊也叫back edge,大家顧名思義就好,下面有更多應(yīng)用。

下面來(lái)段求解圖的雙連通分支的算法:

void bicon(int u, int v)

{

node_pointer ptr;

int w,x,y;

dfn[u]=low[u]=num++;

for(ptr=graph[u];ptr;ptr=ptr->link)

{

w=ptr->vertex;

if(v!=w && dfn[w]

add(&top,u,w);

if(dfn[w]<0)

{

bicon(w,u);

low[u]=MIN2(low[u],low[w]);

if(low[w]>=dfn[u])

{

printf("New biconnected component: ");

do

{

delete(&top,&x,&y);

printf(" <%d,%d>",x,y);

}while(!((x==u)&&(y==w)));

printf(" ");

}

}

else if(w!=v)

low[u]=MIN2(low[u],dfn[w]);

}

}

拓?fù)渑判?/strong>

拓?fù)渑判颍╰opological sort)是對(duì)有向無(wú)環(huán)圖的頂點(diǎn)的一種排序,它使得如果存在一條從vi到vj的路徑,那么在排序中vj出現(xiàn)在vi的后面。正是由于這個(gè)特性,如果圖含有回路,那么拓?fù)渑判蚴遣豢赡艿摹?/p>

e49a3fb0-6a14-11ed-8abf-dac502259ad0.jpg

拓?fù)渑判蚝?jiǎn)單的說(shuō),就是將上圖變成下圖。

e4c3bc8c-6a14-11ed-8abf-dac502259ad0.jpg

求拓?fù)渑判蛩惴ǖ囊环N簡(jiǎn)單方式:選中一個(gè)沒(méi)有入邊的頂點(diǎn),顯示出該點(diǎn),并將它和它的邊一起從圖中刪除,然后對(duì)圖的其余部分應(yīng)用同樣的方法處理。

假設(shè)每一個(gè)頂點(diǎn)的入度被存儲(chǔ)且圖被讀入一個(gè)鄰接表中,下面的代碼則可以生成一個(gè)拓?fù)渑判颉?/p>

e4d0a10e-6a14-11ed-8abf-dac502259ad0.jpg

對(duì)上圖應(yīng)用拓?fù)渑判虻慕Y(jié)果如下:

e4e3c450-6a14-11ed-8abf-dac502259ad0.jpg

最短路徑算法

單源最短路徑問(wèn)題:給定一個(gè)加權(quán)圖G=(V,E)和一個(gè)特定頂點(diǎn)s作為輸入,找出從s到G中每一個(gè)其他點(diǎn)的最短加權(quán)路徑。

如下圖所示,從v1到v6的最短加權(quán)路徑的值為6(v1?v4?v7?v6)

從v2到v5的最短加權(quán)路徑的值為5(v2?v4?v5)。

e4fef0cc-6a14-11ed-8abf-dac502259ad0.jpg

下面這個(gè)圖從v5到v4的最短加權(quán)路徑可就有意思了,它是1么?不是。按照v5?v4?v2?v5?v4的路徑走則是一條更短的路徑了,因?yàn)檫@是帶負(fù)值回路的圖。而由于帶負(fù)值而引入的循環(huán),這個(gè)循環(huán)叫做負(fù)值回路(negative-cost cycle),當(dāng)它出現(xiàn)在圖中時(shí),最短路徑問(wèn)題就是不確定的了。有負(fù)值的邊未必不好,但它們明顯使問(wèn)題更加難了。

e51f20ae-6a14-11ed-8abf-dac502259ad0.jpg

當(dāng)未指明所討論的是加權(quán)路徑還是無(wú)權(quán)路徑時(shí),如果圖是加權(quán)的,那么路徑就是加權(quán)的。

下面列出單源最短路徑算法:

void shortestpath(int v,int cost[][MAX_VERTICES],int distance[],int n,short int found[])

{

int i,u,w;

for(i=0;i

{

found[i]=FALSE;

distance[i]=cost[v][i];

}

found[v]=TRUE;

distance[v]=0;

for(i=0;i

{

u=choose(distance,n,found);

found[u]=TRUE;

for(w=0;w

if(!found[w])

if(distance[u]+cost[u][w]

distance[w]=cost[u][w]+distance[u];

}

}

int choose(int distance[],int n,short int found[])

{

int i,min,minpos;

min=INT_MAX;

minpos=-1;

for(i=0;i

if(distance[i]

{

min=distance[i];

minpos=i;

}

return minpos;

}

思考:找出A到所有其他頂點(diǎn)的最短路徑以及B到所有其他頂點(diǎn)的最短無(wú)權(quán)路徑。

e5317736-6a14-11ed-8abf-dac502259ad0.jpg

如果要求所有頂點(diǎn)對(duì)之間的最短路徑,可以用下面這個(gè)算法:

void allcosts(int cost[][MAX_VERTICES],int distance[][MAX_VERTICES],int n)

{

int i,j,k;

for(i=0;i

for(j=0;j

distance[i][j]=cost[i][j];

for(k=0;k

for(i=0;i

for(j=0;j

if(distance[i][k]+distance[k][j]

distance[i][j]=distance[i][k]+distance[k][j];

}

傳遞閉包

我們由一個(gè)問(wèn)題引入傳遞閉包的概念。有一個(gè)邊不帶權(quán)值的有向圖G,要判斷任意兩個(gè)頂點(diǎn)i 和j 之間是否存在一條路徑,此處有兩種情況,一種是路徑長(zhǎng)度為正數(shù),一種是路徑長(zhǎng)度為非負(fù)。以上兩種情況分別被稱為圖的傳遞閉包(transitive closure),和自反傳遞閉包(reflexive transitive closure)。

傳遞閉包矩陣(transitive closure matrix)是一個(gè)矩陣,記作A+,如果從頂點(diǎn)i到j(luò)存在一條長(zhǎng)度大于0的路徑,則A+[i][j]=1,否則A+[i][j]=0。

自反傳遞閉包矩陣是一個(gè)矩陣,記作A?,如果從頂點(diǎn)i到j(luò)存在一條長(zhǎng)度大于0的路徑,則A?[i][j]=1,否則A?[i][j]=0。

Dijkstra算法

前面的廣度優(yōu)先搜索中的圖是無(wú)權(quán)圖,而如果一旦變成了加權(quán)圖,那么問(wèn)題就變得困難起來(lái)了。

對(duì)于每個(gè)頂點(diǎn),我們標(biāo)記為known以及unknown,和上面一樣,還得有一個(gè)距離的dv。與無(wú)權(quán)最短路徑一樣,Dijkstra算法也是按階段進(jìn)行,在每個(gè)階段選擇一個(gè)頂點(diǎn)v,它在所有unknown頂點(diǎn)中具有最小的dv,同時(shí)算法聲明從s到v的最短路徑是known的。然后緊接著,不斷的進(jìn)行下去即可。

那么這個(gè)算法到底是怎么回事了?請(qǐng)看下圖。

e542671c-6a14-11ed-8abf-dac502259ad0.jpg

圖中已經(jīng)對(duì)權(quán)重做好了標(biāo)記,以v1作為切入點(diǎn),因此初始情況如下左圖。

v1此時(shí)已經(jīng)是known的了,而其有2個(gè)鄰接點(diǎn)v2和v4,因此可以調(diào)整為如下右圖。正無(wú)窮圖標(biāo)標(biāo)識(shí)沒(méi)有連通。pv表示前一個(gè)鄰接點(diǎn)。

e55ded8e-6a14-11ed-8abf-dac502259ad0.jpg

毫無(wú)疑問(wèn)這里會(huì)接下來(lái)走到v4去,因?yàn)関4的權(quán)重為1比v2的權(quán)重為2要小。調(diào)整為如下左圖。

e573e51c-6a14-11ed-8abf-dac502259ad0.jpg

可能你已經(jīng)看到了上圖中的右圖而好奇為什么下一步是v2,但是v4根本不能走到v2。因?yàn)関4能夠走到的,比如v3,權(quán)重從v1開始一共是3,這比從v1到v2還要大。于是就跳轉(zhuǎn)回到了v2。

下一步便走到了v5,因?yàn)橹挥兄禐?的權(quán)重,同樣的v3也是,于是它們倆被雙雙標(biāo)記為known。如下左圖所示。

緊接著走到了v7,同時(shí)v6下調(diào)到了5+1=6得到了如下右圖。至于為什么要做這個(gè)調(diào)整,是因?yàn)榇藭r(shí)v1到v7的加權(quán)為1+4=5,而v7到v6的加權(quán)為1,所以就有了這個(gè)調(diào)整。

e58aaad6-6a14-11ed-8abf-dac502259ad0.jpg

最后便順勢(shì)走到了v6完成了整個(gè)Dijkstra算法,它們都已被標(biāo)記為known。

e5d8538a-6a14-11ed-8abf-dac502259ad0.jpg

在后面還將會(huì)有一種斐波那契堆,針對(duì)Dijkstra算法做了優(yōu)化,歡迎大家的繼續(xù)關(guān)注。

具有負(fù)邊值得圖

而如果一個(gè)圖具有負(fù)的邊值,那么Dijkstra算法就行不通了。這是因?yàn)橐粋€(gè)頂點(diǎn)u被聲明為known后,那就可能從某個(gè)另外的unknown頂點(diǎn)v有一條回到u的負(fù)的路徑。而“回到”就意味著循環(huán),前面的例子中我們已經(jīng)知道了循環(huán)是多么的……

問(wèn)題并非沒(méi)有解決的辦法,如果我們有一個(gè)常數(shù)X,將其加到每一條邊的值上,這樣除去負(fù)的邊,再計(jì)算新圖的最短路徑,最后把結(jié)果應(yīng)用到原圖上。然后這個(gè)解決方案也是布滿了荊棘,因?yàn)榫佣嘣S多條邊的路徑變得比那些具有很少邊的路徑權(quán)重更重了。如果我們將s放到隊(duì)列中,然后再每一個(gè)階段讓一個(gè)頂點(diǎn)v出隊(duì),找出所有與v鄰接的頂點(diǎn)w,使得dw>dv+cv,w,然后更新到dw和pw,并在w不在隊(duì)列中時(shí)將它放到隊(duì)列中,可以為每一個(gè)頂點(diǎn)設(shè)置一個(gè)位(bit)以指示它在隊(duì)列中出現(xiàn)的情況。

無(wú)環(huán)圖

如果圖是無(wú)環(huán)的,則可以通過(guò)改變聲明頂點(diǎn)為known的順序,或者叫做頂點(diǎn)選取法則來(lái)改進(jìn)Dijkstra算法。這種方法通過(guò)拓?fù)渑判騺?lái)選擇頂點(diǎn),由于選擇和更新可以在拓?fù)渑判驁?zhí)行的過(guò)程中執(zhí)行,因此新的算法只需要一趟就可以完成。

通過(guò)下面這個(gè)動(dòng)作結(jié)點(diǎn)圖(activity-node graph)來(lái)解釋什么是關(guān)鍵路徑分析(critical path analysis)再合適不過(guò)了。一條邊(v,w)表示動(dòng)作v必須在動(dòng)作w開始前完成,如前面說(shuō)描述的那樣,這就意味著圖必須是無(wú)環(huán)的。

e5ee496a-6a14-11ed-8abf-dac502259ad0.jpg

為了進(jìn)行這些運(yùn)算,我們把動(dòng)作結(jié)點(diǎn)圖轉(zhuǎn)化成事件結(jié)點(diǎn)圖(event-node graph),每個(gè)事件對(duì)應(yīng)于一個(gè)動(dòng)作和所有與它相關(guān)的動(dòng)作完成。

e6077a2a-6a14-11ed-8abf-dac502259ad0.jpg

所以現(xiàn)在我們需要找出事件的最早完成時(shí)間,只要找出從第一個(gè)事件到最后一關(guān)事件的最長(zhǎng)路徑的長(zhǎng)。因?yàn)橛姓祷芈罚╬ositive-cost cycle)的存在最長(zhǎng)路徑問(wèn)題常常是沒(méi)有意義的。而由于事件結(jié)點(diǎn)圖是無(wú)環(huán)圖,那就不需要擔(dān)心回路的問(wèn)題了,這樣一來(lái)就不用有所顧忌了。

以下是最早完成時(shí)間。

e61ab9a0-6a14-11ed-8abf-dac502259ad0.jpg

以下是最晚完成時(shí)間。

e6354eaa-6a14-11ed-8abf-dac502259ad0.jpg

借助頂點(diǎn)的拓?fù)渑判蛴?jì)算最早完成時(shí)間,而最晚完成時(shí)間則通過(guò)倒轉(zhuǎn)拓?fù)渑判騺?lái)計(jì)算。

而事件結(jié)點(diǎn)圖中每條邊的松弛時(shí)間(slack time)代表對(duì)應(yīng)動(dòng)作可以被延遲而不推遲整體完成的時(shí)間量,最早完成時(shí)間、最晚完成時(shí)間和松弛時(shí)間如下所示。

e64daa7c-6a14-11ed-8abf-dac502259ad0.jpg

某些動(dòng)作的松弛時(shí)間為0,這些動(dòng)作是關(guān)鍵性的動(dòng)作,它們必須按計(jì)劃結(jié)束。至少存在一條完成零-松弛邊組成的路徑,這樣的路徑是關(guān)鍵路徑(critical path)。

網(wǎng)絡(luò)流問(wèn)題

如下左圖所示,有一個(gè)頂點(diǎn)s,稱為源點(diǎn)(source);還有一個(gè)頂點(diǎn)t,稱為匯點(diǎn)(sink)。對(duì)于頂點(diǎn)c,它最大流出2,因此它的最大流入為2,如下右圖所示。而t的最大流也就是5。

e65feafc-6a14-11ed-8abf-dac502259ad0.jpg

要想計(jì)算最大流,同樣可是使用前面的思想——分階段進(jìn)行。令開始時(shí)所有邊都沒(méi)有流,如下中間圖所示。我們可以用殘余圖(residual graph)來(lái)表示對(duì)于每條邊還能再添加上多少流。對(duì)于每一條邊,可以從容量中減去當(dāng)前的流而計(jì)算出殘留的流。

e676c1be-6a14-11ed-8abf-dac502259ad0.jpg

第一步:假設(shè)我們選擇s?b?d?t路徑,此時(shí)會(huì)發(fā)出2個(gè)單位的流通過(guò)這條路徑的每一邊,如下中間圖所示。對(duì)比左圖,我們做如下約定:一旦注滿(使飽和)一條邊,例如a到b和b到d,就將這條邊從殘余圖(也就是中間圖)去掉,如下右圖所示。

e68d364c-6a14-11ed-8abf-dac502259ad0.jpg

第二步:接下來(lái)選擇s?a?c?t路徑,此時(shí)也會(huì)發(fā)出2個(gè)單位的流通過(guò)這條路徑的每一邊,如下中間圖所示(只看s?a?c?t即可,s?b?d?t為上一步說(shuō)走過(guò)的路徑)。同樣將殘余圖更新如下右圖所示。

e6c4169e-6a14-11ed-8abf-dac502259ad0.jpg

第三步:從上圖的殘余圖中我們已經(jīng)可以看出來(lái)最后一步的唯一一種走法了,也就是從s?a?d?t。做如下圖所示更新。

e6d9cc00-6a14-11ed-8abf-dac502259ad0.jpg

很顯然從t無(wú)法走到s,因此算法至此便終止了。因此正好5個(gè)單位的流是最大值。前面的三步我們走的如此順利,那么問(wèn)題真的如此簡(jiǎn)單么?

如果一開始我們選擇了s?a?d?t,那么算法就會(huì)失敗了,因?yàn)槁芬呀?jīng)被堵死了。

e6f83e9c-6a14-11ed-8abf-dac502259ad0.jpg

為了使算法得以成功運(yùn)作,那么就要讓流圖中具有以相反方向發(fā)送流的路徑,如下所示。那么對(duì)于如下右圖中的殘余圖而言,從d返回到a的便成了3而非4,這是因?yàn)閺膖流到d的流量是3個(gè)單位。現(xiàn)在在殘余圖中就有a和d之間有2個(gè)方向,或者還有1個(gè)單位的流可以從a導(dǎo)向d,或者是3個(gè)單位的流導(dǎo)向相反的反向,當(dāng)然,我們也可以撤銷流。

e7105072-6a14-11ed-8abf-dac502259ad0.jpg

緊接著如果通過(guò)d到a導(dǎo)入2個(gè)單位的流,算法就會(huì)從邊(a,d)取走2個(gè)單位的流,更新流圖如下。

e72e1f26-6a14-11ed-8abf-dac502259ad0.jpg

思考:找出下面網(wǎng)絡(luò)中的一個(gè)拓?fù)渑判蛞约白畲罅鳌?/p>

e742719c-6a14-11ed-8abf-dac502259ad0.jpg

活動(dòng)網(wǎng)絡(luò)

AOV網(wǎng)絡(luò)

除了一些不能再簡(jiǎn)單的工程外,所有的工程都可以劃分為若干個(gè)成為活動(dòng)(activities)的子工程。比如說(shuō)大學(xué)的課程,得修完了大學(xué)英語(yǔ)1才能修大學(xué)英語(yǔ)2,也得修完了高等數(shù)學(xué)才能修線性代數(shù)、概率論、離散數(shù)學(xué)等。將這些作為頂點(diǎn)標(biāo)記為圖時(shí),它便是一個(gè)有向圖。

頂點(diǎn)表示活動(dòng)的網(wǎng)(activity on vertex network)或AOV網(wǎng),是用頂點(diǎn)表示活動(dòng)或任務(wù),邊表示活動(dòng)或任務(wù)之間的優(yōu)先關(guān)系的有向圖G。在AOV網(wǎng)絡(luò)G中,當(dāng)且僅當(dāng)從頂點(diǎn)i到j(luò)存在一條有向路徑,則頂點(diǎn)i稱為頂點(diǎn)j的前驅(qū)(predecessor);當(dāng)且僅當(dāng)是G中的一條邊,則稱頂點(diǎn)i為頂點(diǎn)j的直接前驅(qū)(immediate predecessor)。如果頂點(diǎn)i是頂點(diǎn)j的前驅(qū),則稱頂點(diǎn)j為頂點(diǎn)i的后繼(successor);如果頂點(diǎn)i是頂點(diǎn)j的直接前驅(qū),則稱頂點(diǎn)j為頂點(diǎn)i的直接后繼。

拓?fù)渑帕惺怯捎邢驁D中所有頂點(diǎn)形成一個(gè)線性序列,對(duì)圖中任意兩個(gè)頂點(diǎn)i和j,如果頂點(diǎn)i是頂點(diǎn)j的前驅(qū),則頂點(diǎn)i在拓?fù)湫蛄兄信旁陧旤c(diǎn)j的前面。

我們?cè)谇懊嬉呀?jīng)介紹了拓?fù)渑判颍@里給出它的偽代碼。

for(i=0;i

{

if every vertex has a predecessor

{

fprintf(stderr,"Network has a cycle. ");

exit(1);

}

pick a vertex v that has no predecessors;

output v;

delete v and all edges leading out of v from the netwok;

}

對(duì)于拓?fù)渑判騿?wèn)題,所需的操作主要有:

1)判斷頂點(diǎn)是否有前驅(qū);
2)刪除頂點(diǎn)和關(guān)聯(lián)于該頂點(diǎn)的所有邊。

在操作1中,我們可以在每個(gè)頂點(diǎn)中都保存其直接前驅(qū)個(gè)數(shù)的計(jì)數(shù)。對(duì)于操作2,可以使用前面介紹過(guò)的鄰接表來(lái)表示AOV網(wǎng)絡(luò)。于是可以將其實(shí)現(xiàn)為以下C代碼:

// 聲明

typedef struct node *node_pointer;

typedef struct node

{

int vertex;

node_pointer link;

};

typedef struct

{

int count;

node_pointer link;

}hdnodes;

hdnodes graph[MAX_VERTICES];

// 函數(shù)

void topsort(hdnodes graph[],int n)

{

int i,j,k,top;

node_pointer ptr;

top=-1;

for(i=0;i

{

if(!graph[i].count)

{

graph[i].count=top;

top=i;

}

}

for(i=0;i

{

if(top==-1)

{

fprintf(stderr," Network has a cycle. Sort terminated. ");

exit(1);

}

else

{

j=top;

top=graph[top].count;

printf("v%d, ",j);

for(ptr=graph[j].link;ptr;ptr=ptr->link)

{

k=ptr->vertex;

graph[k].count--;

if(!graph[k].count)

{

graph[k].count=top;

top=k;

}

}

}

}

}

在topsort的聲明中,count域用來(lái)保存頂點(diǎn)的入度,而link域則是指向鄰接表首結(jié)點(diǎn)的指針。鄰接表中的每個(gè)結(jié)點(diǎn)又包含了兩個(gè)域:vertex和link。在輸入時(shí),可以方便地設(shè)置count域的值。當(dāng)輸入一條邊時(shí),頂點(diǎn)j的count就會(huì)加1。用一個(gè)棧來(lái)保存count值為0的頂點(diǎn)序列。當(dāng)然也可以使用隊(duì)列,但棧更容易實(shí)現(xiàn)。由于在count域減至0以后,count域就沒(méi)有用了,所以通過(guò)頭結(jié)點(diǎn)的count域把棧中的各個(gè)結(jié)點(diǎn)鏈接起來(lái)。

對(duì)于topsort的分析:對(duì)于具有n個(gè)頂點(diǎn)和e條邊的AOV網(wǎng)絡(luò),第一個(gè)for循環(huán)的時(shí)間開銷為O(n)。而第二個(gè)for循環(huán)執(zhí)行n次。if子句在常數(shù)時(shí)間內(nèi)完成;else子句中的for循環(huán)時(shí)間開銷為O(di),其中di是頂點(diǎn)i的出度。由于這個(gè)循環(huán)會(huì)在每個(gè)頂點(diǎn)輸出時(shí)執(zhí)行一次,所以總時(shí)間為:

O((∑n?1i=odi)+n)=O(e+n)

因此這個(gè)算法的漸進(jìn)時(shí)間為O(e+n),與問(wèn)題的規(guī)模呈線性關(guān)系。

AOE網(wǎng)絡(luò)

AOE網(wǎng)絡(luò)就是邊表示活動(dòng)的網(wǎng)絡(luò)(activity on edge network),它的有向邊表示在一個(gè)工程中所需完成的任務(wù)或活動(dòng),而頂點(diǎn)表示事件,用來(lái)標(biāo)識(shí)某些活動(dòng)的完成。在AOV網(wǎng)絡(luò)中,事件高數(shù)2完成意味著要先完成高數(shù)1;AOE網(wǎng)絡(luò)中,事件高數(shù)2完成意味著已經(jīng)完成了高數(shù)1。也就是說(shuō)在AOE中,當(dāng)一個(gè)事件發(fā)生時(shí),就表明觸發(fā)該事件的所有活動(dòng)都已經(jīng)完成。

在AOE網(wǎng)絡(luò)中,有些活動(dòng)可以并行地進(jìn)行,所以完成整個(gè)工程所需的最短時(shí)間是從開始頂點(diǎn)到終止頂點(diǎn)的最長(zhǎng)路徑的長(zhǎng)度。關(guān)鍵路徑(critical path)就是一條具有最長(zhǎng)路徑長(zhǎng)度的路徑。

一個(gè)事件vi可以發(fā)生的最早發(fā)生時(shí)間(earliest time),是從開始頂點(diǎn)vo到頂點(diǎn)vi的最長(zhǎng)路徑的長(zhǎng)度?;顒?dòng)vi的最遲開始時(shí)間(latest time),記作late(i),是指在不增加工程工期的前提下,活動(dòng)ai能夠最遲的開始時(shí)間。

關(guān)鍵網(wǎng)絡(luò)(critical activity)是指滿足early(i)=late(i)的活動(dòng),一個(gè)活動(dòng)的最遲開始時(shí)間late(i)與最早開始時(shí)間early(i)之間的差說(shuō)明了該活動(dòng)的關(guān)鍵程度。

下面列出兩個(gè)比較常用的公式:

1)事件最早發(fā)生時(shí)間的計(jì)算

earliest[j]=maxi∈P(j){earliest[i]+的持續(xù)時(shí)間}

以上這個(gè)數(shù)學(xué)公式的markdown“源碼”:

$ earliest[j] = displaystyle max_{x in {P(j)}} { earliest[i] + 的持續(xù)時(shí)間 } $

2)事件最晚發(fā)生時(shí)間的計(jì)算

latest[j]=mini∈S(j){latest[i]?的持續(xù)時(shí)間}

最小生成樹

一個(gè)無(wú)向圖G的最小生成樹(minimum spanning tree)就是由該圖的那些連接G的所有頂點(diǎn)的邊構(gòu)成的總值最低的樹。最小生成樹存在當(dāng)且僅當(dāng)G是連通的。

下面第二個(gè)圖是第一個(gè)圖的最小生成樹(碰巧是唯一的,但并不能代表一般情況)。最小生成樹是一棵樹;因?yàn)樗鼰o(wú)環(huán);因?yàn)樽钚∩蓸浒恳粋€(gè)頂點(diǎn),所以它叫生成樹;此外,它顯然是包含所有頂點(diǎn)的最小的樹。

e7587262-6a14-11ed-8abf-dac502259ad0.jpg

Prim算法

計(jì)算最小生成樹的一種方法是使其連續(xù)地一步一步成長(zhǎng),在每一步中,都要把一個(gè)結(jié)點(diǎn)當(dāng)作根并且往上累加邊,于是就將關(guān)聯(lián)的頂點(diǎn)加到了增長(zhǎng)中的樹上。

Prim算法和前面求最短路徑的Dijkstra算法思想類似,因此和前面一樣我們對(duì)每一個(gè)頂點(diǎn)保留值dv和pv以及一個(gè)標(biāo)記頂點(diǎn)的known或unknown。

e76e038e-6a14-11ed-8abf-dac502259ad0.jpg

還是老辦法,在Prim算法中也設(shè)定一個(gè)表的初始狀態(tài)如下。

e78cb572-6a14-11ed-8abf-dac502259ad0.jpg

將v1設(shè)置為known的,根據(jù)Prim算法上一張圖所示,v1連接了v2、v3、v4,其dv分別為2、4、1,因此更新如下。

e79d987e-6a14-11ed-8abf-dac502259ad0.jpg

將v4聲明為known,更新如下。

e7b64478-6a14-11ed-8abf-dac502259ad0.jpg

將v2和v3先后聲明為known,更新如下。

e7c966b6-6a14-11ed-8abf-dac502259ad0.jpg

將v7聲明為known后更新如下左圖,最后將v6和v5也更新為known后更新如下右圖。

e7e19966-6a14-11ed-8abf-dac502259ad0.jpg

下面是Prim算法的偽代碼實(shí)現(xiàn),其中T為生成樹的邊集,TV是當(dāng)前生成樹T的頂點(diǎn)集合

Kruskal算法

第二種貪心策略是連續(xù)地按照最小的權(quán)選擇邊,并且當(dāng)所選的邊不產(chǎn)生回路時(shí)就把它作為取定的邊。同樣是前面的示例,用Kruskal算法執(zhí)行如下。

e7f93698-6a14-11ed-8abf-dac502259ad0.jpg

形式上,Kruskal算法是在處理一個(gè)森林——樹的集合。下圖展示了邊被添加到森林中的順序。

e8141fc6-6a14-11ed-8abf-dac502259ad0.jpg

當(dāng)添加到森林中的邊足夠多時(shí),算法終止,這里算法的作用在于決定邊(u,v)是應(yīng)該添加還是舍棄。

在該算法執(zhí)行的過(guò)程中,兩個(gè)頂點(diǎn)屬于同一個(gè)集合當(dāng)且僅當(dāng)它們?cè)诋?dāng)前的生成森林(spanning forest)中連通。如果u和v在同一個(gè)集合中,那么連接它們的邊就要放棄,因?yàn)楫?dāng)它們已經(jīng)連接的情況下,再添加邊(u,v)就會(huì)形成一個(gè)回路。

如果這兩個(gè)頂點(diǎn)不在同一個(gè)集合中,就應(yīng)該將這條邊加入,并對(duì)包含頂點(diǎn)u和v的兩個(gè)集合執(zhí)行一次union操作。這樣將保持集合的不變性,因?yàn)橐坏┻叄╱,v)添加到生成森林中,若w連通到u而x連通到v,這x和w必然是連通的,因此屬于相同的集合。雖然將邊排序便于選取,但用線性時(shí)間建立一個(gè)堆則是更好的想法,此時(shí)deleteMin使得邊依次得到測(cè)試。

Sollin算法

Sollin算法在每一步都為生成樹遞歸地選取若干條邊,在每一步處理開始時(shí),說(shuō)選取的邊與圖中的所有n個(gè)頂點(diǎn)形成一個(gè)生成森林。在執(zhí)行過(guò)程中,為森林中的每棵樹都選取一條邊,與選取的邊都是恰有一個(gè)頂點(diǎn)在樹上且代價(jià)最小。由于森林中的兩棵樹可能選取同一條邊,所以需要去掉同一條邊被多次選取的情況。在開始時(shí),說(shuō)選取的邊集為空,當(dāng)最后結(jié)果只有一棵樹或者再?zèng)]有邊可供選取時(shí),算法就此結(jié)束。

深度優(yōu)先搜索

深度優(yōu)先搜索(depth-first search)是對(duì)前序遍歷的推廣,對(duì)每一個(gè)頂點(diǎn),字段visited被初始化成false,通過(guò)哪些尚未被訪問(wèn)的結(jié)點(diǎn)遞歸調(diào)用該過(guò)程,我們保證不會(huì)陷入無(wú)限循環(huán)。如果圖是無(wú)向且連通的,或是有向但非強(qiáng)連通的,這種方法可能會(huì)訪問(wèn)不到某些結(jié)點(diǎn)。此時(shí)我們搜索一個(gè)未被標(biāo)記的結(jié)點(diǎn),然后應(yīng)用深度優(yōu)先遍歷,并繼續(xù)這個(gè)過(guò)程直到不存在未標(biāo)記的結(jié)點(diǎn)為止。因?yàn)樵摲椒ūWC每一條邊只被訪問(wèn)一次,所以只要使用鄰接表,執(zhí)行遍歷的總時(shí)間就是O(|E|+|V|)。

深度優(yōu)先搜索的算法實(shí)現(xiàn):

#define FALSE 0

#define TRUE 1

short int visited[MAX_VERTICES]

void dfs(int v)

{

node_pointer w;

visited[v]=TRUE;

printf("%5d",v);

for(w=graph[v];w;w=w->link);

if(!visited[w->vertex])

dfs(w->vertex);

}

和上文中的廣搜一樣,我們也來(lái)對(duì)dfs進(jìn)行分析。

如果采用鄰接表來(lái)存儲(chǔ),就可以沿著頂點(diǎn)的鏈表來(lái)確定其所有鄰接頂點(diǎn)。因此在鄰接表中的每一個(gè)頂點(diǎn)都至多掃描一次,所以完成搜索時(shí)間復(fù)雜性為O(e)。

如果采用鄰接矩陣來(lái)存儲(chǔ),訪問(wèn)頂點(diǎn)的所有鄰接頂點(diǎn)的時(shí)間為O(n),而整個(gè)過(guò)程至多訪問(wèn)n個(gè)頂點(diǎn),因此完成搜索時(shí)間復(fù)雜性為O(n2)。

無(wú)向圖

如下左圖中是一個(gè)無(wú)向圖,我們以此為例,假設(shè)從A開始,標(biāo)記A為known并遞歸地調(diào)用dfs(B)。dfs(B)標(biāo)記B為known并遞歸調(diào)用dfs(C)。dfs(C)標(biāo)記C為known并遞歸調(diào)用dsf(D)。而后D的鄰接結(jié)點(diǎn)只有C,但C已經(jīng)是knwon的,這里便無(wú)法再繼續(xù)遞歸下去,因此返回到dfs(C)。dfs(C)看到已經(jīng)標(biāo)記為known的B鄰接點(diǎn)和D鄰接點(diǎn),因此調(diào)用dfs(E)。dfs(E)標(biāo)記E為known,同樣的它只能返回到dfs(C),再返回到dfs(B),最后返回到dfs(A)。實(shí)際上這里接觸每條邊2次,一次是作為邊(v,w),另一次是作為邊(w,v)。

如下右圖展示了深度優(yōu)先搜索樹(depth-first spanning tree)的步驟。虛線表示向后邊(back edge),表示這條“邊”并不是樹的一部分。

e8325e64-6a14-11ed-8abf-dac502259ad0.jpg

樹將模擬我們執(zhí)行的遍歷,使用樹的邊對(duì)該樹的前序編號(hào)(preorder numbering)表示頂點(diǎn)被標(biāo)記的順序;如果圖不是連通的,那么處理所有的結(jié)點(diǎn)(以及邊)自然需要多次反復(fù)調(diào)用dfs,每次都生成一棵樹,整個(gè)集合就是深度優(yōu)先生成森林(depth-first spanning forest)。

雙連通性

前面我們已經(jīng)介紹過(guò)了雙連通圖,如果刪除一個(gè)無(wú)向圖的仁一頂點(diǎn)后,剩下的圖仍然連通,那么這樣的無(wú)向連通圖就稱為是雙連通的(biconnected)。

如果圖不是雙聯(lián)通的,那么將其刪除后不再連通的那些頂點(diǎn)叫做割點(diǎn)(articulation point)。

深度優(yōu)先搜索提供了一種找出連通圖中所有割點(diǎn)的線性時(shí)間算法。從圖中任一頂點(diǎn)開始,執(zhí)行深度優(yōu)先搜索并在頂點(diǎn)被訪問(wèn)時(shí)給它們編號(hào)。對(duì)于每一個(gè)頂點(diǎn)v,我們稱其前序編號(hào)為Num(V)。然后,對(duì)于深度優(yōu)先搜索生成樹中的每一個(gè)頂點(diǎn)v,計(jì)算編號(hào)最低的頂點(diǎn),我們稱之為L(zhǎng)ow(V),該點(diǎn)可從v開始通過(guò)樹的零條或多條邊,且可能還有一條后向邊而以該序達(dá)到。

e8480bce-6a14-11ed-8abf-dac502259ad0.jpg

有向圖

如前所述,如果圖不是強(qiáng)連通的,那么從某個(gè)結(jié)點(diǎn)開始的深度優(yōu)先搜索可能訪問(wèn)不了所有的結(jié)點(diǎn),這種情況下我們從某個(gè)未做標(biāo)記的結(jié)點(diǎn)處開始,反復(fù)執(zhí)行深度優(yōu)先搜索,直到所有的結(jié)點(diǎn)都被訪問(wèn)到為止。

e85fbf9e-6a14-11ed-8abf-dac502259ad0.jpg

對(duì)此我們從頂點(diǎn)B開始深度優(yōu)先搜索,依次訪問(wèn)B、C、A、D、E、F。而后從某個(gè)未被標(biāo)記的頂點(diǎn)重新開始,比如H,然后訪問(wèn)J和I。最后從G開始,也從此結(jié)束。

e87729ea-6a14-11ed-8abf-dac502259ad0.jpg

深度優(yōu)先生成森林中的虛線是一些(v,w)邊,其中的w在考察時(shí)已經(jīng)做了標(biāo)記。在無(wú)向圖中,它們總是一些向后邊,但是可以看到,存在三種類型的邊并不通向新的頂點(diǎn)。這里有一些向后邊(back edge),如(A,B);還有一些向前邊(forward edge),如(C,D);最后還有一些交叉邊(cross edge),如(F,C),它們把不直接相關(guān)的兩個(gè)樹結(jié)點(diǎn)連接起來(lái)。深度優(yōu)先搜索森林一般通過(guò)把一些子結(jié)點(diǎn)和一些新的樹叢左到右添加到森林中形成。

深度優(yōu)先搜索還可以用來(lái)檢測(cè)一個(gè)有向圖是否是無(wú)環(huán)圖,因?yàn)橐粋€(gè)有向圖是無(wú)環(huán)圖當(dāng)且僅當(dāng)它沒(méi)有向后邊。上面的例子明顯不是無(wú)環(huán)圖,因?yàn)樗邢蚝筮?。而拓?fù)渑判蛞部梢杂脕?lái)檢測(cè)一個(gè)圖是否是無(wú)環(huán)圖,進(jìn)行拓?fù)渑判虻牧硪环N方法是通過(guò)深度優(yōu)先搜索生成森林的后序遍歷給頂點(diǎn)指定拓?fù)渚幪?hào)N,N?1,……,1。只要圖是無(wú)環(huán)的,這種排序就是一致的。

審核編輯 :李倩


聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 算法
    +關(guān)注

    關(guān)注

    23

    文章

    4592

    瀏覽量

    92538
  • 歐拉
    +關(guān)注

    關(guān)注

    1

    文章

    13

    瀏覽量

    1815

原文標(biāo)題:圖論算法 有圖有代碼 萬(wàn)字總結(jié) 向前輩致敬

文章出處:【微信號(hào):TheAlgorithm,微信公眾號(hào):算法與數(shù)據(jù)結(jié)構(gòu)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    談?wù)?b class='flag-5'>有哪些電路

    在電子工程領(lǐng)域,電路是很多電子工程師學(xué)習(xí)電子設(shè)計(jì)的第一步內(nèi)容,它們以圖形化的方式展示了電路的結(jié)構(gòu)、元件及它們之間的連接關(guān)系,然而很多工程師只知道原理、方框圖等,但對(duì)很多電路不太清楚,所以下面將談?wù)?/div>
    的頭像 發(fā)表于 10-15 14:08 ?534次閱讀

    常用的ADC濾波算法哪些

    ADC(模數(shù)轉(zhuǎn)換器)濾波算法在信號(hào)處理中起著至關(guān)重要的作用,它們能夠幫助我們提取出有用的信號(hào),同時(shí)濾除噪聲和干擾。以下是常用的ADC濾波算法詳解,這些算法各具特色,適用于不同的應(yīng)用場(chǎng)景。
    的頭像 發(fā)表于 10-08 14:35 ?229次閱讀

    圖像識(shí)別算法的測(cè)試方法哪些

    圖像識(shí)別算法的測(cè)試方法是一個(gè)廣泛而深入的話題,涉及到多個(gè)方面。 數(shù)據(jù)集的選擇 : 標(biāo)準(zhǔn)數(shù)據(jù)集 :使用廣泛認(rèn)可的數(shù)據(jù)集,如MNIST、CIFAR-10、ImageNet等,這些數(shù)據(jù)集明確的類別劃分
    的頭像 發(fā)表于 07-16 11:06 ?437次閱讀

    神經(jīng)網(wǎng)絡(luò)算法的結(jié)構(gòu)哪些類型

    神經(jīng)網(wǎng)絡(luò)算法是深度學(xué)習(xí)的基礎(chǔ),它們?cè)谠S多領(lǐng)域都有廣泛的應(yīng)用,如圖像識(shí)別、自然語(yǔ)言處理、語(yǔ)音識(shí)別等。神經(jīng)網(wǎng)絡(luò)的結(jié)構(gòu)很多種類型,每種類型都有其獨(dú)特的特點(diǎn)和應(yīng)用場(chǎng)景。以下是對(duì)神經(jīng)網(wǎng)絡(luò)算法結(jié)構(gòu)的介紹
    的頭像 發(fā)表于 07-03 09:50 ?372次閱讀

    運(yùn)動(dòng)控制算法哪些

    運(yùn)動(dòng)控制算法是機(jī)器人學(xué)和自動(dòng)化領(lǐng)域中的核心技術(shù)之一,它們負(fù)責(zé)規(guī)劃和執(zhí)行機(jī)器人或自動(dòng)化設(shè)備的精確運(yùn)動(dòng)。以下是一些常見的運(yùn)動(dòng)控制算法,以及它們的基本原理和應(yīng)用場(chǎng)景。 PID控制算法
    的頭像 發(fā)表于 06-13 09:17 ?1997次閱讀

    常用的電機(jī)控制算法哪些

    在電機(jī)控制領(lǐng)域,選擇合適的控制算法對(duì)于實(shí)現(xiàn)高效、精確且穩(wěn)定的電機(jī)運(yùn)行至關(guān)重要。以下將詳細(xì)介紹幾種常用的電機(jī)控制算法,并通過(guò)具體的分析和實(shí)例,探討它們的特點(diǎn)、應(yīng)用以及優(yōu)勢(shì)。
    的頭像 發(fā)表于 06-05 16:31 ?2006次閱讀

    扎心靈魂小拷問(wèn):了AI編寫代碼之后,軟件工程師會(huì)被AI取代嗎?

    軟件開發(fā)者們很多讓他們焦慮的事情。他們最擔(dān)心的不再是如何用他們最喜歡的編程語(yǔ)言(C、C++、Erlang、Java等)表達(dá)最新的算法。相反,這種擔(dān)憂正逐漸被人工智能(AI)所取代。 在這里,我們將探討AI編寫代碼的過(guò)程,并回
    的頭像 發(fā)表于 05-24 19:17 ?593次閱讀
    扎心靈魂小拷問(wèn):<b class='flag-5'>有</b>了AI編寫<b class='flag-5'>代碼</b>之后,軟件工程師會(huì)被AI取代嗎?

    FPGA壓縮算法哪些

    在圖像壓縮算法中可以采用哈夫曼編碼的方式對(duì)編碼冗余的信息進(jìn)行壓縮,可以采用預(yù)測(cè)的方式來(lái)減少像素間冗余,可以采用量化的方式完成心理視覺(jué)冗余信息的去除
    的頭像 發(fā)表于 04-15 11:48 ?546次閱讀
    FPGA壓縮<b class='flag-5'>算法</b><b class='flag-5'>有</b>哪些

    代碼檢查的方式三種

    【摘要】?代碼檢查中,提到的編程規(guī)范,規(guī)則集,規(guī)則,規(guī)則用例(場(chǎng)景、誤報(bào)、檢出)分別代表什么意思呢? 在 SAST 靜態(tài)檢查領(lǐng)域,代碼檢查服務(wù)可以幫助開發(fā)者發(fā)現(xiàn)和修復(fù)代碼中的風(fēng)格、質(zhì)量和安全
    的頭像 發(fā)表于 02-25 10:08 ?799次閱讀
    <b class='flag-5'>代碼</b>檢查的方式<b class='flag-5'>有</b>三種

    請(qǐng)問(wèn)sigmastudio算法集成對(duì)什么資源要求,以及什么方法可以查看系統(tǒng)資源占用情況?

    您好, 目前基于ADSP-21565開發(fā)了一些基礎(chǔ)音頻功能,想知道目前系統(tǒng)占用了多少資源,還剩下多少資源,以此來(lái)評(píng)估后續(xù)的sigmastudio算法集成可行性。 請(qǐng)問(wèn)sigmastudio算法集成對(duì)什么資源要求,以及
    發(fā)表于 01-10 08:28

    如何寫出好的代碼?高質(zhì)量代碼的三要素

    膾炙人口的詩(shī)"春百花秋月,夏涼風(fēng)冬雪",意境唯美,簡(jiǎn)明易懂。好的代碼也是讓人陶醉的,那么如何寫出好的
    的頭像 發(fā)表于 01-05 11:29 ?1166次閱讀
    如何寫出好的<b class='flag-5'>代碼</b>?高質(zhì)量<b class='flag-5'>代碼</b>的三要素

    目前的室內(nèi)定位算法什么優(yōu)勢(shì)

    隨著智能手機(jī)、物聯(lián)網(wǎng)和無(wú)人駕駛等技術(shù)的迅猛發(fā)展,室內(nèi)定位技術(shù)成為了人們關(guān)注的熱點(diǎn)。由于GPS在室內(nèi)定位中受限,研究者們不斷在室內(nèi)定位算法上進(jìn)行探索和創(chuàng)新。本文詳盡、詳實(shí)、細(xì)致地回顧了目前的室內(nèi)
    的頭像 發(fā)表于 12-25 17:00 ?655次閱讀

    php加密方式哪些

    PHP加密方式許多種,以下是一些常用的加密方式: 對(duì)稱加密 對(duì)稱加密算法使用相同的密鑰進(jìn)行加密和解密。常見的對(duì)稱加密算法DES、3DES、AES。對(duì)稱加密
    的頭像 發(fā)表于 12-04 15:32 ?616次閱讀

    java中的注釋三類分別是

    在Java編程語(yǔ)言中,注釋是非常重要的一部分,它們提供了對(duì)代碼的解釋和說(shuō)明。注釋可以幫助開發(fā)人員更好地理解代碼,使代碼更易于維護(hù)和理解。在Java中,三種主要類型的注釋:?jiǎn)涡凶⑨尅⒍?/div>
    的頭像 發(fā)表于 11-28 16:47 ?1149次閱讀

    178個(gè)經(jīng)典c語(yǔ)言源代碼+算法大全

    電子發(fā)燒友網(wǎng)站提供《178個(gè)經(jīng)典c語(yǔ)言源代碼+算法大全.rar》資料免費(fèi)下載
    發(fā)表于 11-21 10:19 ?6次下載
    178個(gè)經(jīng)典c語(yǔ)言源<b class='flag-5'>代碼</b>+<b class='flag-5'>算法</b>大全