数据结构-------图(最通俗易懂的文章)(一)图的数据结构及其遍历
簡(jiǎn)要
圖的結(jié)構(gòu)類似于樹(shù),都是一個(gè)又一個(gè)結(jié)點(diǎn)(頂點(diǎn))連接而成,如下圖:
因此樹(shù)其實(shí)也是一種特殊的圖結(jié)構(gòu),樹(shù)的每個(gè)節(jié)點(diǎn)只能有一個(gè)入度,而圖可以有多個(gè),圖的知識(shí)點(diǎn)在這里解不講解了,例如弧,入度,出度等可以查閱書(shū)籍或者網(wǎng)上查閱。這里主要講解圖的存儲(chǔ)結(jié)構(gòu)。
圖的存儲(chǔ)結(jié)構(gòu)
圖有多種存儲(chǔ)結(jié)構(gòu),例如鄰接矩陣,鄰接表或者十字鏈表等,那么它是如何用結(jié)構(gòu)體來(lái)實(shí)現(xiàn)上面圖這種抽象結(jié)構(gòu)的?
之前文章所說(shuō),數(shù)據(jù)結(jié)構(gòu)一般都有兩種表示方法,一種是數(shù)組,一種是鏈?zhǔn)?#xff0c;由于圖的結(jié)構(gòu)比較復(fù)雜,任意兩個(gè)點(diǎn)之間都有可能存在聯(lián)系,因此無(wú)法以數(shù)據(jù)元素放到數(shù)組這種來(lái)表示它的關(guān)系。但是換個(gè)思路,當(dāng)遇到簡(jiǎn)單的圖(有向和無(wú)向)或者網(wǎng)(有向和無(wú)向,帶有路徑,權(quán)就稱為網(wǎng))表示的時(shí)候,你可以用兩個(gè)數(shù)組來(lái)存儲(chǔ)圖的數(shù)據(jù)信息和頂點(diǎn)之間的關(guān)系。那么就引出了鄰接矩陣。
鄰接矩陣
上面說(shuō)用兩個(gè)數(shù)組來(lái)表示圖的關(guān)系,具體什么意思,首先拿最簡(jiǎn)單的有向圖來(lái)舉例:
以上關(guān)系簡(jiǎn)單理解,V1可以到V2,V2可以到V3等等這里就不細(xì)說(shuō)。這里所說(shuō)的用矩陣來(lái)表示該圖關(guān)系,其實(shí)是用一個(gè)二維數(shù)組來(lái)表示該矩陣。用下面圖來(lái)解釋什么意思:
二維數(shù)組都有序號(hào),有幾個(gè)頂點(diǎn)就有幾行幾列,比如V1可以到V2,那么在數(shù)組上的第一行第二列的值就應(yīng)該為 1 , V1無(wú)法到達(dá)V3
,那么數(shù)組中第一行的第三列的值就為0,那么自然就懂了, 圖中頂點(diǎn)的序號(hào)對(duì)應(yīng)數(shù)組中的位置 (當(dāng)然數(shù)組中也是從0開(kāi)始算起的,這里只是幫助理解),有路徑,可以到達(dá),那么對(duì)應(yīng)數(shù)組的值就為1,如果沒(méi)有路徑那么對(duì)應(yīng)的值就為0,
那么這里就用了一個(gè)二維數(shù)組來(lái)表示了圖中各頂點(diǎn)之間的關(guān)系。自然可以發(fā)現(xiàn),第一行為1 的個(gè)數(shù)就是頂點(diǎn)序號(hào)為1 的出度數(shù),列就是頂點(diǎn)總的入度數(shù)。
上面這個(gè)是有向圖的二維數(shù)組定義,那么無(wú)向圖的二維數(shù)組自然就懂了,無(wú)向圖的定義是兩個(gè)頂點(diǎn)可以互相到達(dá),那么二維數(shù)組就自然成了對(duì)稱矩陣了,可以自己畫(huà)畫(huà),這里就不說(shuō)了。
然后再定義一個(gè)數(shù)組來(lái)存放各個(gè)頂點(diǎn)的數(shù)據(jù)關(guān)系,那么就完成了圖結(jié)構(gòu)的創(chuàng)建,也可以抽象的想成,定義一個(gè)儲(chǔ)存圖頂點(diǎn)的數(shù)組,再定義一個(gè)二維數(shù)組來(lái)存儲(chǔ)頂點(diǎn)的關(guān)系。
上圖就已經(jīng)完全解釋了鄰接矩陣如何實(shí)現(xiàn)了存儲(chǔ),就是定義一個(gè)圖的結(jié)構(gòu)體,然后其中定義倆數(shù)組一個(gè)放頂點(diǎn)(包括頂點(diǎn)的信息),一個(gè)放頂點(diǎn)的位置關(guān)系。
那么定義的結(jié)構(gòu)體為:
這里假設(shè)有個(gè)例子
要來(lái)實(shí)現(xiàn)上面的圖,頂點(diǎn)信息就存它們頂點(diǎn)的名稱。
那么就輸出上面的例圖:
先輸入圖結(jié)構(gòu)的信息,幾個(gè)頂點(diǎn)幾條弧( 幾條邊 )然后輸入三個(gè)頂點(diǎn)的信息,執(zhí)行三次輸入操作,然后頂點(diǎn)信息輸入完成后,輸入弧的信息,你要連接的點(diǎn),按照頂點(diǎn)在數(shù)組中的位置(頂點(diǎn)在頂點(diǎn)數(shù)組的位置要和在鄰接矩陣對(duì)應(yīng)好),然后輸出鄰接矩陣,如圖,那么可以看出第一行第二個(gè)第三個(gè)為 1 說(shuō)明V0可以通到V1和V2 。可以慢慢理解
上面是有向圖,那么有向網(wǎng)(帶權(quán)值的圖,帶路徑的)自然就懂了。那么初始化定義就可以在原來(lái)的基礎(chǔ)上加上:
void init(Graph &G) //初始化函數(shù)和上面的一樣,就是在后面加上權(quán)值賦值的操作 {int i,j;printf("請(qǐng)輸入圖的節(jié)點(diǎn)數(shù)和弧數(shù)");scanf("%d",&i);scanf("%d",&j);G.vexnum=i;G.arcnum=j;for(int x=0;x<G.vexnum;x++){for(int y=0;y<G.vexnum;y++){G.arcs[x][y]=99; //先將鄰接矩陣每個(gè)值定義為99,因?yàn)闊o(wú)法到達(dá),那么它的權(quán)值就是無(wú)窮大,這里用99代替無(wú)窮大}}for(int x=0;x<i;x++){printf("請(qǐng)輸入頂點(diǎn)的數(shù)據(jù)信息");scanf("%s",&G.vex[x]);}for(int x=0;x<j;x++){printf("請(qǐng)輸入要連接的點(diǎn)"); int a,b,c;scanf("%d",&a);scanf("%d",&b);printf("請(qǐng)輸入它的權(quán)值");scanf("%d",&c); //主要在這里修改即可,將原來(lái)的 1改為輸入權(quán)值即可G.arcs[a][b]=c; } }那么實(shí)現(xiàn)下面這個(gè)例子:
上面標(biāo)有了權(quán)值那么如何實(shí)現(xiàn):
輸出結(jié)果可以從鄰接矩陣看出頂點(diǎn)的位置和權(quán)值信息。根據(jù)矩陣中的序號(hào)來(lái)推斷各頂點(diǎn)之間的信息。(99代表沒(méi)有路徑,無(wú)窮大)
鄰接矩陣比較簡(jiǎn)單。
鄰接表
鄰接表表示法有點(diǎn)類似前面數(shù)結(jié)構(gòu)中的孩子表示法,可以看之前的樹(shù)結(jié)構(gòu)文章。
總而言之,鄰接表的功能是找出與其連接的點(diǎn)以及權(quán)值,但是你要找它的入度之類卻比較麻煩,需要遍歷所有頂點(diǎn),不如鄰接矩陣的。
鄰接表是如何定義的?
將每個(gè)頂點(diǎn)用結(jié)構(gòu)體定義,但是還是要放在那個(gè)圖結(jié)構(gòu)體的數(shù)組中,但是每個(gè)頂點(diǎn)結(jié)構(gòu)體中有一個(gè)鏈?zhǔn)奖韥?lái)存放弧的信息,(其連接的下一個(gè)點(diǎn)和它的權(quán)值)。
上面是框架圖,下面為解釋圖:
每個(gè)頂點(diǎn)中有一個(gè)鏈?zhǔn)奖韥?lái)存放它指向的頂點(diǎn)的位置(在數(shù)組中的位置)或者也可以存弧的權(quán)值,這里的鏈?zhǔn)奖砝镄畔⒅淮娣潘赶蝽旤c(diǎn)的位置,并不存儲(chǔ)指向頂點(diǎn)的數(shù)據(jù)信息,相當(dāng)于一個(gè)索引。只需要遍歷頂點(diǎn)的線性表就可以獲得它所指向的有哪些頂點(diǎn)了。這里也可以將該鏈?zhǔn)奖砝斫鉃橐粋€(gè)存儲(chǔ)弧信息的表。
首先定義圖的結(jié)構(gòu)體框架:(這個(gè)例子是帶有權(quán)值的,應(yīng)該叫做網(wǎng),但是為了大家方便理解,就先叫做圖吧)
typedef struct ArcNode //定義圖中的表結(jié)構(gòu),鏈?zhǔn)奖?/span> {int adjvex; //要連接點(diǎn)在數(shù)組中的位置int arc; //定義頂點(diǎn)與該連接點(diǎn)之間的權(quán)值struct ArcNode *next; //定義下一個(gè)節(jié)點(diǎn),鏈?zhǔn)奖碇g的鏈,實(shí)現(xiàn)鏈?zhǔn)奖碇g的連接 }ArcNode;typedef struct VNode //定義圖頂點(diǎn)結(jié)構(gòu) {ArcNode *first; //每個(gè)頂點(diǎn)中的表頭的頭指針,可根據(jù)上面的圖像理解char vex[10]; //圖信息(數(shù)據(jù))的存儲(chǔ),這里也可以存儲(chǔ)頂點(diǎn)的多個(gè)數(shù)據(jù),都由自己定,這里就假設(shè)存儲(chǔ)的是頂點(diǎn)名稱 }VNode; //定義圖頂點(diǎn)結(jié)構(gòu) typedef struct {VNode NodeList[10]; //定義頂點(diǎn)數(shù)組,存放圖的頂點(diǎn),和上面的道理一樣int vexnum,arcnum; //定義圖的屬性,幾個(gè)頂點(diǎn)幾條弧}Graph; //定義圖總框架,c語(yǔ)言調(diào)用的時(shí)候要遵從先后定義順序,所以該圖結(jié)構(gòu)定義在最后面對(duì)鏈?zhǔn)奖聿惶私獾目梢钥聪旅嫖恼?br /> 線性表------最通俗易懂的文章
然后我們將該中圖結(jié)構(gòu)進(jìn)行一下實(shí)例化:
void initGraph(Graph &G) {int x,y;printf("輸入圖的節(jié)點(diǎn)個(gè)數(shù)");scanf("%d",&x);G.vexnum=x; //定義圖的頂點(diǎn)數(shù)目for(int i=0;i<G.vexnum;i++) //對(duì)頂點(diǎn)的信息進(jìn)行初始化,輸入,因?yàn)檫@里定義的是頂點(diǎn)數(shù)組,所以用循環(huán)來(lái)定義頂點(diǎn)的信息{ scanf("%s",&G.NodeList[i].vex); //輸入頂點(diǎn)的數(shù)據(jù)信息G.NodeList[i].first=(ArcNode*)malloc(sizeof(ArcNode)); //對(duì)每個(gè)頂點(diǎn)的鏈表頭指針進(jìn)行初始化,不懂可以看線性表文章G.NodeList[i].first->next=NULL; }for(int i=0;i<G.vexnum;i++) //按照頂點(diǎn)在數(shù)組中的位置進(jìn)行循環(huán)對(duì)頂點(diǎn)的相關(guān)弧來(lái)對(duì)點(diǎn)的弧進(jìn)行初始化信息{printf("這是%s節(jié)點(diǎn),輸入它的弧的個(gè)數(shù)為:",G.NodeList[i].vex);int n;scanf("%d",&n); //輸入當(dāng)前頂點(diǎn)的弧數(shù)ArcNode *p; //定義一個(gè)頭指針來(lái)綁定每個(gè)頂點(diǎn)中的鏈?zhǔn)奖?#xff0c;號(hào)方便進(jìn)行對(duì)其的操作p=G.NodeList[i].first; for(int j=0;j<n;j++) //對(duì)你定義的弧數(shù)進(jìn)行初始化{ArcNode *q;q=(ArcNode*)malloc(sizeof(ArcNode)); //來(lái)為鏈?zhǔn)奖淼拿恳粋€(gè)節(jié)點(diǎn)來(lái)開(kāi)辟空間(創(chuàng)建一個(gè)臨時(shí)節(jié)點(diǎn)來(lái)實(shí)現(xiàn)鏈?zhǔn)奖淼倪B接)printf("要連接的節(jié)點(diǎn):"); scanf("%d",&q->adjvex); //要連接頂點(diǎn)在數(shù)組中的位置printf("弧的長(zhǎng)度為:");scanf("%d",&q->arc); //要連接頂點(diǎn)與當(dāng)前頂點(diǎn)的權(quán)值q->next=NULL; //實(shí)現(xiàn)鏈?zhǔn)奖淼倪B接,這里是從尾部進(jìn)行連接的,上面文章中是從表頭后進(jìn)行連接的,連接原理可以看上面文章p->next=q;p=q; //將p指針綁定成下一個(gè)節(jié)點(diǎn),方便進(jìn)行下一次的連接}} }再定義一個(gè)輸出函數(shù),當(dāng)輸入頂點(diǎn)的位置后,就可以輸出它所連接的點(diǎn)了,這里是有向圖,可以輸出它所出度的點(diǎn)。
void printfG(Graph &G,int n) {ArcNode *p=G.NodeList[n].first->next; //綁定需要輸出的頂點(diǎn)的next指針printf("這是%s頂點(diǎn)",G.NodeList[n].vex); //while(p!=NULL) //對(duì)鏈?zhǔn)奖磉M(jìn)行循環(huán)遍歷,若為空,說(shuō)明以及到了鏈?zhǔn)奖淼慕Y(jié)尾{ printf("它連接的頂點(diǎn)有%d,",p->adjvex); //對(duì)其連接點(diǎn)信息的輸出printf("它倆之間的權(quán)值為%d\n",p->arc);p=p->next; //進(jìn)行綁定鏈?zhǔn)奖淼南乱粋€(gè)節(jié)點(diǎn)} }具體實(shí)現(xiàn)
那么就實(shí)現(xiàn)一下上面的圖,比較簡(jiǎn)單,方便理解。可見(jiàn)V0的出度弧有兩個(gè),那么該頂點(diǎn)的鏈?zhǔn)奖砭陀袃蓚€(gè)節(jié)點(diǎn),同理V1和V2。
簡(jiǎn)介明了,輸入節(jié)點(diǎn)數(shù)后,對(duì)這幾個(gè)節(jié)點(diǎn)進(jìn)行初始化:頂點(diǎn)數(shù)據(jù),所連接的有那些頂點(diǎn)和之間的權(quán)值等等,當(dāng)然唯一缺點(diǎn)就是不能看頂點(diǎn)的入度,需要遍歷所有的頂點(diǎn)。否則你可以設(shè)置一個(gè)逆鄰表,就是鏈?zhǔn)奖硭娴男畔⒍际侨攵鹊男畔?#xff0c;上面例子中鏈?zhǔn)奖硭娴男畔⒍际浅龆鹊男畔ⅰ?/p>
上面的鄰接表只是方便來(lái)看頂點(diǎn)所連接的有哪些點(diǎn)和之間的權(quán)值,但是想遍歷圖中頂點(diǎn)的所有信息和關(guān)系,比較麻煩,所以出現(xiàn)了十字鏈表。
十字鏈表
由于受篇幅長(zhǎng)度影響,這里就大概說(shuō)一下原理,大家可以研究。上面兩種圖結(jié)構(gòu)中的弧只是一個(gè)抽象的意義,你并沒(méi)有定義弧這個(gè)對(duì)象吧,所以說(shuō)的頂點(diǎn)的連接是用一種關(guān)系來(lái)代替弧的存在,那么十字鏈表是將弧也進(jìn)行結(jié)構(gòu)體封裝定義,每有一條弧就定義一個(gè)結(jié)構(gòu)體,里面存弧所連倆頂點(diǎn)在數(shù)組中的位置,然后兩個(gè)指針域,一個(gè)指向相同入度的點(diǎn),一個(gè)指向相同出度的點(diǎn),(指針?biāo)赶虻倪€是弧的結(jié)構(gòu)體)那么你隨便查詢一個(gè)頂點(diǎn)的出入信息,那么就可以使用它的弧節(jié)點(diǎn)的指針域,直到為NULL為止。
可以看下面文章:
十字鏈表法
圖的遍歷
圖的兩種常用遍歷方法:深度優(yōu)先搜索和廣度優(yōu)先搜索
深度遍歷
深度遍歷算法是使用了遞歸的思想,隨便找到一個(gè)頂點(diǎn),然后找到它連接的第一個(gè)頂點(diǎn),一直找連接的第一個(gè)頂點(diǎn),直到找到?jīng)]有連接的頂點(diǎn),然后返回到上一級(jí)遞歸找他的另一個(gè)子頂點(diǎn),然后再開(kāi)始深度搜索,就是一直往下突,直到?jīng)]有連接的頂點(diǎn)為止(遍歷過(guò)的節(jié)點(diǎn)不能再遍歷一次,也相當(dāng)于是不能連接的點(diǎn)),然后再返回去,重復(fù)遞歸操作就可以遍歷完圖中所有頂點(diǎn)。下面給出深度遍歷圖的流程:
紅色數(shù)字表示遍歷頂點(diǎn)的順序。
可以看上圖慢慢理解,十分簡(jiǎn)單。這里可以根據(jù)樹(shù)結(jié)構(gòu)的遍歷來(lái)理解,當(dāng)你遍歷到最后面的節(jié)點(diǎn)無(wú)處可走的時(shí)候,返回到它的上一個(gè)節(jié)點(diǎn),看看有沒(méi)有第二個(gè)子節(jié)點(diǎn),如果有就開(kāi)始遍歷它的第二個(gè)子節(jié)點(diǎn),如果沒(méi)有第二個(gè)子節(jié)點(diǎn)了了,再返回上一個(gè)節(jié)點(diǎn),重復(fù)此步驟。只不過(guò)在圖結(jié)構(gòu)中,只要有連接的可遍歷點(diǎn)就一口氣捅到最后面,然后回溯(一個(gè)一個(gè)的回溯,仔細(xì)檢查是否有第二個(gè)子頂點(diǎn)),有點(diǎn)貪心法的味道。
實(shí)現(xiàn)
那么它是如何實(shí)現(xiàn)這種遍歷方法?上面說(shuō)了主要使用遞歸的方法來(lái)實(shí)現(xiàn),首先遞歸函數(shù)的使用和原理得理解,就是定義一個(gè)函數(shù),在這個(gè)函數(shù)里面使用該函數(shù),具體使用這里就不講解了。
咱們就拿上圖做例子
這里創(chuàng)建過(guò)程就不寫(xiě)了,首先你遍歷的時(shí)候,是如何判定頂點(diǎn)是否已經(jīng)遍歷過(guò)了,使你不會(huì)再進(jìn)行第二次遍歷?就是設(shè)置一個(gè)布爾(bool)類型的數(shù)組,數(shù)組的大小就是你頂點(diǎn)的數(shù)目,來(lái)存放每個(gè)點(diǎn)是否遍歷過(guò),初始化為false表示未遍歷過(guò),當(dāng)遍歷過(guò)后就將相應(yīng)頂點(diǎn)的 bool 設(shè)置為true。
舉個(gè)例子,有三個(gè)點(diǎn)需要遍歷:
bool visited[3]; //因?yàn)槿齻€(gè)頂點(diǎn),所以布爾數(shù)組的容量設(shè)置為3for(int i=0;i<3;i++){visited[i]=false; //將每個(gè)頂點(diǎn)進(jìn)行初始化為false,表示每個(gè)點(diǎn)都沒(méi)有遍歷過(guò)}visited[1]=true; //表示第二個(gè)頂點(diǎn)遍歷過(guò)了,所以將其bool值設(shè)置為了true那么需要在程序中設(shè)置一個(gè)全局變量布爾數(shù)組,用來(lái)監(jiān)控圖遍歷的情況,那么為什么要定義全局變量,而不是直接在圖創(chuàng)建的時(shí)候,在圖結(jié)構(gòu)里面定義?因?yàn)樵摲N遍歷方法使用了遞歸的方法,會(huì)導(dǎo)致布爾數(shù)組結(jié)果回溯。導(dǎo)致遍歷過(guò)的頂點(diǎn)再遍歷一次,程序錯(cuò)誤。
bool visited[MAX_SIZE]; //全局變量定義需要在函數(shù)外然后定義兩個(gè)查找子頂點(diǎn)函數(shù)
int findFirst(Graph G,int w) //定義一個(gè)找第一個(gè)子頂點(diǎn)的函數(shù),w表示要找哪個(gè)頂點(diǎn)的子頂點(diǎn) {for(int n=0;n<G.vexnum;n++){if(G.arcs[w][n]==1&&visited[n]==false) return n; //根據(jù)矩陣的特點(diǎn)找第一個(gè)頂點(diǎn),第幾個(gè)頂點(diǎn)就是在矩陣的第幾行,從第一列開(kāi)始循環(huán),當(dāng)為1的時(shí)候說(shuō)明相應(yīng)的頂點(diǎn)相連,第一個(gè)1說(shuō)明就是其第一個(gè)頂點(diǎn)//并且返回第一個(gè)頂點(diǎn)是哪個(gè)頂點(diǎn)(在數(shù)組中的位置)}return 0; //若該頂點(diǎn)沒(méi)有任何相連的點(diǎn)后返回0 }int findNext(Graph G,int w,int t) //找下一個(gè)子頂點(diǎn)函數(shù),w表示要找哪個(gè)頂點(diǎn)的子頂點(diǎn),t表示查找的開(kāi)始點(diǎn) {for(t=t+1;t<G.vexnum;t++) //因?yàn)檫@是找next頂點(diǎn),所以要t=t+1,原因可以看下面的遍歷來(lái)理解為什么{if(G.arcs[w][t]==1&&visited[t]==false) return t; //和上面的一樣,當(dāng)為矩陣中的值為1 的時(shí)候說(shuō)明兩點(diǎn)相連,并且返回是哪個(gè)頂點(diǎn)}return 0; //當(dāng)沒(méi)有相鄰的頂點(diǎn)后函數(shù)就會(huì)返回 0 }Tip:根據(jù)矩陣的性質(zhì),給你一個(gè)點(diǎn),可以從矩陣找它的第一個(gè)子頂點(diǎn)和下一個(gè)子頂點(diǎn),自己慢慢理解,但是對(duì)于無(wú)向圖大家都知道,是對(duì)稱矩陣,那么在深度遍歷或者廣度遍歷無(wú)向圖的時(shí)候,找子頂點(diǎn)會(huì)有重復(fù)現(xiàn)象,已經(jīng)遍歷過(guò)了的頂點(diǎn),再用兩個(gè)找子頂點(diǎn)函數(shù)就會(huì)重復(fù),重復(fù)遍歷,那么就要在找子頂點(diǎn)函數(shù)的判斷中加上 visited[n]==false 條件,在找到子頂點(diǎn)的同時(shí)并且判斷該子頂點(diǎn)是否已經(jīng)遍歷過(guò)了,如果沒(méi)有再返回子頂點(diǎn)。
定義深度遍歷函數(shù):
void DeepTraverse(Graph G) //深度遍歷函數(shù) {for(int v=0;v<G.vexnum;v++) visited[v]=false; //初始化布爾數(shù)組,將每個(gè)頂點(diǎn)都設(shè)置為false,表示都沒(méi)有遍歷過(guò)for(int v=0;v<G.vexnum;v++) //這步是開(kāi)始進(jìn)行遍歷,使用下面DFS函數(shù),這里為什么還要對(duì)所有頂點(diǎn)進(jìn)行一次循環(huán)?//深度遍歷,只要是相連的頂點(diǎn),從其中一個(gè)點(diǎn)遍歷,就可以全部一連串都遍歷出來(lái),但是圖中可能有不連通的兩部分,導(dǎo)致其中不相連,那么有些點(diǎn)就遍歷不上了,所以為了防止這種情況,將所有頂點(diǎn)的visited數(shù)組遍歷一下{if(visited[v]==false) DFS(G,v); //根據(jù)布爾數(shù)組,若為false則開(kāi)始遍歷,調(diào)用下面的遞歸函數(shù),v就是上面開(kāi)始遍歷的點(diǎn) }void DFS(Graph G,int v) //遍歷遞歸函數(shù) {visited[v]=true; //遍歷的時(shí)候,將其對(duì)應(yīng)布爾數(shù)組設(shè)置為true,表示已經(jīng)遍歷過(guò)了printf("%s",G.vex[v]); //將遍歷的點(diǎn)數(shù)據(jù)處理,這里就簡(jiǎn)單的輸出作為數(shù)據(jù)處理for(int w=findFirst(G,v);w>0;w=findNext(G,v,w)) //開(kāi)始遞歸操作,進(jìn)行一個(gè)循環(huán)先找當(dāng)前遍歷點(diǎn)的第一個(gè)子頂點(diǎn)作為 w 的初始值//若有第一個(gè)子頂點(diǎn),那么findFirst函數(shù)返回的就不是0,開(kāi)始進(jìn)行DFS函數(shù)遞歸,若沒(méi)有子頂點(diǎn),那么返回0,直接退出該循環(huán),表示該點(diǎn)后面的子頂點(diǎn)遍歷完了。{if(visited[w]==false) DFS(G,w); //若當(dāng)前點(diǎn)未遍歷過(guò),那么進(jìn)入DFS函數(shù)進(jìn)行遞歸} }上面就是深度遍歷的過(guò)程,主要是由遞歸函數(shù)來(lái)實(shí)現(xiàn)的,所以比較麻煩,可以進(jìn)行debug操作來(lái)一步一步看其過(guò)程,下面舉個(gè)簡(jiǎn)單的例子來(lái)解釋一下代碼流程
如何創(chuàng)建圖結(jié)構(gòu)上篇文章已經(jīng)講了,這里就不多說(shuō)了,下面是創(chuàng)建的結(jié)果:
那么代碼執(zhí)行流程 :
橙色文字表示DFS遞歸級(jí)別,就是DFS1是在DFS 0中調(diào)用的,DFS 2實(shí)在DFS 1 中調(diào)用的,這里想要理解其使用,必須先理解遞歸函數(shù)怎么用和它的原理。
那么會(huì)得出其結(jié)果為:
因?yàn)樵蹅兇a為邊遍歷邊輸出它的信息,所以就會(huì)得出其遍歷順序?yàn)?#xff1a; V0 V1 V3 V2,符合深度遍歷的特點(diǎn)。
那么再看之前的例子:
輸出的順序在片頭已經(jīng)給出,那么用程序試一下:
那么輸出結(jié)果符合,深度遍歷實(shí)現(xiàn)。
總結(jié)
深度遍歷你先理解其含義和運(yùn)行流程,就是隨便找到一個(gè)頂點(diǎn)一個(gè)勁的往下突,突到頭后,開(kāi)始回溯,但是還要將所有頂點(diǎn)再進(jìn)行一次審查,防止不連通的圖,導(dǎo)致頂點(diǎn)遺漏,因?yàn)橹灰_(kāi)始深度遍歷,那么只要其是相連的頂點(diǎn),那么都可以一次性遍歷完,那么這個(gè)實(shí)現(xiàn)的步驟就是使用遞歸函數(shù)來(lái)實(shí)現(xiàn),有點(diǎn)類似8皇后問(wèn)題。
廣度遍歷
廣度遍歷顧名思義,遍歷方式是一層一層的,就是只要與當(dāng)前遍歷點(diǎn)距離為1的點(diǎn)先開(kāi)始遍歷,,因此也可以稱為廣度優(yōu)先遍歷,和深度優(yōu)先遍歷不同的是,深度遍歷是一個(gè)勁的往下突,而廣度像是一種橫掃的方式。其實(shí)用一張圖來(lái)演示一下流程就懂了。
紅色數(shù)字表示遍歷點(diǎn)的順序,有點(diǎn)像擴(kuò)散,就是尋找當(dāng)前點(diǎn)距離為1的點(diǎn),但是它是一層一層的,然后再找與上一次所找的頂點(diǎn)距離為1的點(diǎn),但它并不是同時(shí)遍歷的,還是有順序的遍歷。
上面的數(shù)組其實(shí)是一個(gè)輔助理解作用,但是它可以方便理解代碼是如何實(shí)現(xiàn)的 。
實(shí)現(xiàn)
廣度優(yōu)先遍歷代碼實(shí)現(xiàn)需要用隊(duì)列做基礎(chǔ),從上面圖看出,可以假設(shè)這里有一個(gè)數(shù)組(當(dāng)然不是具體的數(shù)組,只是一個(gè)抽象的集合),存放需要找距離為1的頂點(diǎn)。當(dāng)你開(kāi)始的時(shí)候,數(shù)組中先存放V0,起始點(diǎn)。然后從數(shù)組中取出V0(這時(shí)候數(shù)組為空),尋找與V0距離為1的點(diǎn),就是緊挨的點(diǎn),找到了V1和V2,再放到數(shù)組中,然后再按存放的順序,拿出V1,找與它距離為1的頂點(diǎn),找到V3,放到數(shù)組中,按照順序,是不是該找V2了,然后找與V2距離為1的點(diǎn),找到V4和V5,那么這回?cái)?shù)組中就為 { V3,V4,V5 },同理,再開(kāi)始找V3,顯然沒(méi)有頂點(diǎn)了,然后按順序找V4,V5.這就是大概流程,就是先取出一個(gè),找到相關(guān)頂點(diǎn)再放到數(shù)組中,按順序找下一個(gè),再取,再放。其實(shí)這個(gè)思想是代碼實(shí)現(xiàn)的思想,那么上面說(shuō)的好像是一層同時(shí)遍歷是算法的思想。
其實(shí)總結(jié)一下就是你找到一個(gè)點(diǎn),找與它相連的幾個(gè)點(diǎn),放到數(shù)組里,然后依次找這幾個(gè)點(diǎn)相關(guān)的點(diǎn),再次放到數(shù)組里,然后在去找上一次找出的點(diǎn)的相關(guān)點(diǎn)放進(jìn)去,好像是一層一層的。
那么這里就用了隊(duì)列做輔助工具,如果不理解隊(duì)列結(jié)構(gòu)創(chuàng)建和使用的看下面文章
數(shù)據(jù)結(jié)構(gòu)-----------隊(duì)列(最通俗易懂的文章)
那么我們用一張圖來(lái)表示隊(duì)列存儲(chǔ)的流程:
那么要用隊(duì)列數(shù)據(jù)結(jié)構(gòu)做工具,肯定有隊(duì)列數(shù)據(jù)結(jié)構(gòu)的創(chuàng)建和使用:
那么隊(duì)列數(shù)據(jù)結(jié)構(gòu)創(chuàng)建好后,并且也理解了廣度遍歷算法那么代碼是如何實(shí)現(xiàn)的:
void BFSTraverse(Graph &G) {Queue Q; //創(chuàng)建隊(duì)列,因?yàn)閺V度遍歷要用initQueue(Q); //初始化隊(duì)列for(int v=0;v<G.vexnum;v++) visited[v]=false; //這個(gè)和深度遍歷一樣,你要遍歷圖,首先對(duì)圖中所有頂點(diǎn)用bool數(shù)組進(jìn)行初始化,表示都還未遍歷,當(dāng)然該數(shù)組要用全局變量定義原因上面有for(int v=0;v<G.vexnum;v++) //和深度遍歷一樣作用,可能圖中有些點(diǎn)之間不連通,可能會(huì)遺忘遍歷//所以要對(duì)所有頂點(diǎn)最后進(jìn)行一次審查,但是如果聯(lián)通,只要從一個(gè)頂點(diǎn)進(jìn)去了就一連串可以遍歷出來(lái){if(visited[v]==false) //開(kāi)始遍歷{visited[v]=true; //表示該點(diǎn)已經(jīng)遍歷printf("%s",G.vex[v]); //對(duì)該點(diǎn)數(shù)據(jù)處理,這里就用輸出語(yǔ)句代替復(fù)雜的處理語(yǔ)句EnQueue(Q,v); //將該點(diǎn)入隊(duì)列,每個(gè)點(diǎn)是先遍歷再入隊(duì)列while(Q.head!=Q.tail) //開(kāi)始廣度遍歷精髓,當(dāng)隊(duì)列不為空時(shí)候表示還沒(méi)有遍歷完,可以看上圖理解,當(dāng)隊(duì)列為空時(shí)候,說(shuō)明已經(jīng)遍歷完{int u=GetQueue(Q); //彈出隊(duì)列中的第一個(gè)點(diǎn),就是要找子頂點(diǎn)的點(diǎn),看上圖,隊(duì)列中存儲(chǔ)的是頂點(diǎn)在數(shù)組中的位置for(int w=findFirst(G,u);w>0;w=findNext(G,u,w)) //開(kāi)始找其子頂點(diǎn),和深度一樣(調(diào)用的倆函數(shù)和深度一樣,不重復(fù)寫(xiě)定義了){visited[w]=true; //遍歷子頂點(diǎn)printf("%s",G.vex[w]); //對(duì)子頂點(diǎn)數(shù)據(jù)處理EnQueue(Q,w); //將子頂點(diǎn)入隊(duì)列,看上圖演示}}}} }那么對(duì)一開(kāi)始的圖進(jìn)行實(shí)例:
下面就為輸出的結(jié)果,符合廣度遍歷,但是可能大家覺(jué)得有點(diǎn)偶然性,那么再舉個(gè)例子:
這個(gè)是從網(wǎng)上隨便找的例子,根據(jù)廣度遍歷,起始點(diǎn)為V0,根據(jù)廣度遍歷點(diǎn)的順序大家可以先想一下,很簡(jiǎn)單,首先遍歷的點(diǎn)肯定為V1 V2 V5,然后就是 V3 V4
那么就符合了廣度遍歷。
最近發(fā)現(xiàn)了一個(gè)國(guó)外挺牛的網(wǎng)站,將所有數(shù)據(jù)結(jié)構(gòu)都實(shí)現(xiàn)了可視化。你可以調(diào)節(jié)演示速度,有各種算法演示,比如什么查找之類,紅黑樹(shù),什么都有。可能就是全英文,看不懂的可以用瀏覽器的翻譯軟件。地址如下:
數(shù)據(jù)結(jié)構(gòu)可視化
總結(jié)
以上是生活随笔為你收集整理的数据结构-------图(最通俗易懂的文章)(一)图的数据结构及其遍历的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。