圖的遍歷
一,簡介
圖的遍歷主要就是深度和廣度優先遍歷。下面引入一個圖:
其實不難發現這個圖是有兩個部分組成,分別是每一個節點以及節點之間的連接。現在要遍歷這個圖其實就是按照編號來進行遍歷,把這個圖的每一個頂點遍歷一遍。每一個頂點是第幾個被訪問到的叫做時間戳。下面是這個遍歷的過程:
首先從1出發,發現2號頂點還沒有走過1於是現在到了2號頂點,從2號頂點在開始,發現四號頂點沒有走過,所以現在就走到四號頂點,在四號頂點發現沒有沒有走過的頂點了,所以現在要返回到2,在2發現還是老樣子,沒有可以去的地方,所以現在就再返回1,在1這個位置發現3沒有走過,所以現在到了3這個位置,再從3到5.現在所有的頂點全部都走完了!!
二,圖的儲存
關於圖的儲存這里介紹一種簡單的方法:鄰接矩陣存儲法,這個方法說白了就是用二維數組來儲存這個圖。看下圖:
下面就來解釋一下這個圖吧:第一行也就是代表着第一個節點:對於第一個節點所相連的節點標記為1,看第一行,也就是1號節點和2,3,5有相連,主對角線是0是因為一個節點不能和它本身相連,沒有相連的就是無窮,但是這個無窮不好表示所以就暫時用99999999來表示吧。這里還有一個細節,圖分為2個類,有向圖和無向圖。其實從這個圖里可以發現無向圖矩陣是對稱陣。但是如果是有向圖的話,就只有在主對角線一邊有路。
三,代碼實現
#include <stdio.h> int book[101] = { 0 }; int e[101][101]; int sum = 0, n; void dfs(int cur) {//cur就是當前的編號 int i; printf("%d", cur); if (sum == n) {//所有頂點已經訪問了,所以就退出這個遞歸,在深度優先算法中這個退出條件非常重要 return;//這個退出條件真的要認真思考,但是注意一下,其實這個退出條件是可以不止一個的 } for (int i = 1; i <= n; i++) {//從一號到n號依次嘗試,看每一個頂點是否有邊相連。 if (e[cur][i] == 1 && book[101] == 0) {//cur就是現在遍歷到的節點,首先判斷這個點是不是有路,接着再看這里有沒有走過 book[i] = 1;//book用來記錄圖是否已經遍歷完了 dfs(i);//從頂點i再開始出發
//這里其實還有一點,這種只有遍歷,不是找相應的最短路徑,所以沒必要在函數返回的時候取消標記。 } } return; } int main() { int i, j, a, m, b; scanf_s("%d %d", &n, &m); for ( i = 1; i <= n; i++) { for ( j = 1; i <= m; j++) { if (i == j) { //這里的意思就是對角線上的元素是0 e[i][j] = 0; } else { e[i][j] = 9999; } } } //繼續讀入頂點的邊 for ( i = 1; i <= m; i++) { scanf_s("%d %d", &a, &b); e[a][b] = 1; e[b][a] = 1;//這個圖是無向圖,所以這個是雙向的,如果是有向圖就只要定義一個位置就好了 } book[1] = 1;//記住第一個位置要先標記上 dfs(1);//這里其實就是搜索的其實位置 return 0;
這里就是有深度優先搜索來進行搜索(這里不說太多),這里補充解釋一下深度優先搜索的核心代碼吧:
//后面其實就是深度優先搜索的核心代碼 void dfs(int step) { //判斷邊界,這里非常重要,在后續我覺得可以出一個博客專門舉例子寫這種邊界 //嘗試每一種可能 for (int i = 1; i <= n; i++) { //繼續下一步 dfs(step + 1); //后續操作其實在我的理解上就是遞歸樹的返回,例如把標記取消,這個在最短路徑這還是很重要的。 } //返回 }
后面是廣度優先搜索:
//廣度優先搜索要用到隊列的知識這里對隊列知識還是做一個比較詳細的介紹吧
#include <stdio.h> int main() { int i, j, n, m, a, b, cur, book[101] = { 0 }, e[101][101]; int que[10001], head, tail; scanf_s("%d %d", &m, &n); for (i = 1; i <= n; i++) { for (j = 1; j <= n; j++) { if (i == j) { e[i][j] = 0; } else { e[i][j] = 999999999; } } } //讀入頂點之間的邊 for (i = 1; i <= m; i++) { scanf_s("%d %d", &a, &b); e[a][b] = 1; e[b][a] = 1; } //后面開始廣度搜索,現在其實也是做一個復習把 head = 1;//這里是隊列初始化 tail = 1; //從一號頂點出發,說明一號頂點已經訪問了 que[tail] = 1;//這里就是把第一個節點入隊。 tail++; book[1] = 1; //后面還是老樣子當隊列不為空的時候來進行循環 while (head < tail && tail <= n) {//head<tail在我的理解下好像就是隊列的一個循環調節但兩個相等的時候循環結束。tail<n是因為tail不能超過一行的限制 cur = que[head];//從當前的編號開始向外拓展,首先要把這個當前編號放在頭的位置 for (i = 1; i <= n; i++) { //判斷從頂點cur到i是否有邊,並判斷頂點i是否已經訪問過了 if (e[cur][i] == 1 && book[i] == 0) { book[i] = 1; //現在把上一個圖相連的這個點入隊 que[tail] = i; tail++; } if (tail > n) { break;//第一行結束這個循環就break掉 } } head++;//注意現在這個head就輪到下一個圖了,比如2,也就是說現在已經到了第二行了,這個head非常重要甚至是隊列遍歷進行的條件。 } for (i = 1; i < tail; i++) printf("%d", que[i]); return 0; }
(這些代碼中的注釋大多是自己的理解,可能有些錯誤)
這里后面再補充幾個圖來幫助大家理解吧。
其實這里隊列還是簡單的,所以也沒有用到結構來進行定義結構數組,有的時候就要用到一個甚至多個結構數組了,后面有機會的話我會在博客里寫一些有趣的搜索問題!
圖的遍歷其實只是一個基礎,本來想先想寫深度優先搜索和廣度優先搜索的,但是想寫的太多了,正好最近在看最短路徑的問題,所以就先寫了比較基礎的圖的遍歷,一步步來吧,加油。
參考書籍 《啊哈!算法》