圖的遍歷
圖的遍歷目的是訪問圖的每一個頂點恰好一次,,同時訪問圖中每條邊恰好一 次。
對于無向圖,常見的遍歷方式有深度優先遍歷(Depth-First Search, DFS) 和廣度優先遍歷(Breadth-First Search, BFS)。
1 深度優先遍歷(DFS)
深度優先遍歷是一種用于遍歷或搜索樹或圖的算法。
深度優先搜索是一個遞歸的過程。
- 首先,選定一個出發點后進行遍歷,如果有鄰接的未被訪問過的節點則繼續前進。
- 若不能繼續前進,則回退一步再前進
- 若回退一步仍然不能前進,則連續回退至可以前進的位置為止。
- 重復此過程,直到所有與選定點相通的所有頂點都被遍歷。
示例
先選擇一個起始的頂點
、
選擇其中一個鄰接點
3沒有鄰接點,回退到2
2有兩個鄰接點,但是3已經訪問過了,選擇0
0有兩個鄰接點,但是2已經訪問過了,選擇1
1有1個鄰接點,但是2已經訪問過了,于是整個深度優先遍歷完成,輸出結果為2->3->0->1
代碼如下:
#include <iostream>
using namespace std;// 定義圖的邊(頂點值默認從0開始)用兩個頂點之間的連接表示邊
typedef struct Node
{int vertex; // 下一個頂點的值struct Node *next; // 下一條頂點(如果當前頂點沒有更多的鄰接點,則此指針為NULL)
} Node;// 定義圖
typedef struct Graph
{int numVertices; // 頂點數Node **adjLists; // 鄰接表,指向指針數組的指針,每個指針都指向一個Node鏈表的頭部。數組// 的大小numVertices 數組元素則是指向從該頂點出發的第一條邊的指針(如果頂點沒有鄰接點,則為// NULL)。bool *isVisited; // 標記看看是不是訪問過 數組的大小numVertices (頂點數)
}Graph;// 創建一個圖,包含vertices個頂點
Graph *createGraph(int vertices)
{Graph *graph = new Graph();if (!graph){perror("創建失敗");return nullptr;}// /初始化頂點數graph->numVertices = 0;graph->adjLists = new Node *[vertices]; // 表示一個包含 vertices 個 Node* 元素的數組。在C++中,數組名在大多數情況下會退化成指向數組首元素的指針// new Node*[vertices] 返回的是一個指向包含 vertices 個 Node* 元素的數組的指針,其類型是 Node**,這與 adjLists 的類型匹配// /為鄰接表申請內存for (int i = 0; i < vertices; ++i){graph->adjLists[i] = nullptr;}// 標注位申請內存graph->isVisited = new bool [vertices] { 0 };return graph;
}// 添加邊 src起點 dest終點
void addEdge(Graph *graph, int src, int dest)
{// 創建邊Node *newNode = new Node();// //設置鄰接點newNode->vertex = dest;// 將新邊添加到起點的鏈表中newNode->next = graph->adjLists[src];// 更新第一天條邊graph->adjLists[src] = newNode;graph->adjLists[src] = newNode;如果是無向圖,也需要添加反向邊Node* newNode2 = (Node*)malloc(sizeof(Node));// Node* newNode2 = new Node;// newNode2->vertex = src;// newNode2->link = graph->adjLists[dest];// graph->adjLists[dest] = newNode2;
}// 深度優先遍歷
void DFS(Graph *graph, int v)
{// 輸出遍歷到的節點cout << v << " ";// 遞歸訪問所有未訪問的鄰接頂點Node *adjList = graph->adjLists[v];while (adjList){// 標記當前節點為已訪問graph->isVisited[v] = true;// 鄰接點未被訪問到if (!graph->isVisited[adjList->vertex]){ // 遞歸調用 訪問他的所有鄰接點DFS(graph, adjList->vertex);}// 往后遍歷每一條邊adjList = adjList->next;}
}// 清理Graph對象
void freeGraph(Graph *graph)
{if (!graph){return;}// 釋放鄰接表中每個鏈表占用的內存for (int i = 0; i < graph->numVertices; ++i){Node *current = graph->adjLists[i];while (current){Node *next = current->next; // 保存下一條邊的指針delete current; // 釋放當前邊的內存current = next; // 移動到下一條邊}}// 釋放鄰接表數組本身占用的內存delete[] graph->adjLists;// 釋放訪問標記數組占用的內存delete[] graph->isVisited;
}int main()
{// 創建一個包含4個頂點的圖Graph *graph = createGraph(4);addEdge(graph, 0, 1);addEdge(graph, 0, 2);addEdge(graph, 1, 2);addEdge(graph, 2, 0);addEdge(graph, 2, 3);// 深度優先遍歷圖cout << "深度優先遍歷(從頂點2開始):" << endl;DFS(graph, 2);delete graph;graph = NULL;return 0;
}
newNode1->next = graph->adjList[src];
意思是 新節點的 next 指針指向當前 src 鄰接鏈表的第一個節點。
這樣,新節點 newNode1 被插入到鏈表的頭部
graph->adjList[src] = newNode1;
將 src 的鄰接鏈表的頭部更新為 newNode1。這樣,newNode1 成為鏈表的新頭節點
結果是
深度優先遍歷(從頂點2開始):
2 0 1 3
1.2 廣度優先遍歷(BFS)
廣度優先遍歷是圖的另一種遍歷方式,它從根節點開始,首先訪問離根節點最近的節點(即根節點的所 有鄰接節點),然后逐層向外訪問,直到訪問完所有節點。
- 從圖中某頂點v出發,在訪問了v之后依次訪問v的各個未曾訪問過的鄰接點
- 然后分別從這些鄰接點出發依次訪問它們的鄰接點,并使得“先被訪問的頂點的鄰接點先于后被訪問的頂點的鄰接點被訪問,直至圖中所有已被訪問的頂點的鄰接點都被訪問到。
- 如果此時圖中尚有頂點未被訪問,則需要另選一個未曾被訪問過的頂點作為新的起始點,重復上述過程,直至圖中所有頂點都被訪問到為止(隊列為空)。
示例
先選擇一個起始的頂點·
2的鄰接點有兩個,全部入隊,隊列元素為3, 0
3沒有鄰接點,出列,輸出3,隊列元素為0
0的鄰接點是1和2,但是2已經訪問過了,1入隊,0出列,輸出0,隊列元素為1
1的鄰接點是2,但是2已經訪問過了,0出列,輸出0,隊列元素為空,同時訪問完畢 整個廣度優先遍歷完成,輸出結果為2->3->0->1
代碼如下
#include <iostream>
using namespace std;
#include <queue>
// 定義圖的邊(頂點值默認從0開始)用兩個頂點之間的連接表示邊
typedef struct Node
{int vertex; // 下一個頂點的值struct Node *next; // 下一條頂點(如果當前頂點沒有更多的鄰接點,則此指針為NULL)
} Node;// 定義圖
typedef struct Graph
{int numVertices; // 頂點數Node **adjLists; // 鄰接表,指向指針數組的指針,每個指針都指向一個Node鏈表的頭部。數組// 的大小numVertices 數組元素則是指向從該頂點出發的第一條邊的指針(如果頂點沒有鄰接點,則為// NULL)。bool *isVisited; // 標記看看是不是訪問過 數組的大小numVertices (頂點數)
}Graph;// 創建一個圖,包含vertices個頂點
Graph *createGraph(int vertices)
{Graph *graph = new Graph();if (!graph){perror("創建失敗");return nullptr;}// /初始化頂點數graph->numVertices = vertices;graph->adjLists = new Node *[vertices]; // 表示一個包含 vertices 個 Node* 元素的數組。在C++中,數組名在大多數情況下會退化成指向數組首元素的指針// new Node*[vertices] 返回的是一個指向包含 vertices 個 Node* 元素的數組的指針,其類型是 Node**,這與 adjLists 的類型匹配// /為鄰接表申請內存for (int i = 0; i < vertices; ++i){graph->adjLists[i] = nullptr;}// 標注位申請內存graph->isVisited = new bool [vertices] { 0 };return graph;
}// 添加邊 src起點 dest終點
void addEdge(Graph *graph, int src, int dest)
{// 創建邊Node *newNode = new Node();// //設置鄰接點newNode->vertex = dest;// 將新邊添加到起點的鏈表中newNode->next = graph->adjLists[src];// 更新第一天條邊graph->adjLists[src] = newNode;如果是無向圖,也需要添加反向邊Node* newNode2 = (Node*)malloc(sizeof(Node));// Node* newNode2 = new Node;// newNode2->vertex = src;// newNode2->link = graph->adjLists[dest];// graph->adjLists[dest] = newNode2;
}// 深度優先遍歷
void DFS(Graph *graph, int v)
{// 輸出遍歷到的節點cout << v << " ";// 遞歸訪問所有未訪問的鄰接頂點// 第一個頂點Node *adjList = graph->adjLists[v];// 標記當前節點為已訪問graph->isVisited[v] = true;while (adjList){// 鄰接點未被訪問到if (!graph->isVisited[adjList->vertex]){ // 遞歸調用 訪問他的所有鄰接點DFS(graph, adjList->vertex);}// 往后遍歷每一條邊adjList = adjList->next;}
}// 廣度優先遍歷
void BFS(Graph* graph, int startVertex)
{// 初始化隊列 queue <int> q;// / 標記所有頂點為未訪問for(int i = 0; i < graph->numVertices ; ++i){graph->isVisited[i] = false;} // 將起始頂點加入隊列,并標記為已訪問 q.push(startVertex);graph->isVisited[startVertex] = true;// 循環條件是隊列不為空while (!q.empty()){// 從隊列中取出第一個頂點int vertex = q.front(); //出隊 q.pop();// 訪問該頂點 cout << "Visited " << vertex << endl;// 遍歷訪問的頂點的所有鄰接點 Node* current = graph->adjLists[vertex];while (current){ //鄰接點 終點int adjVertex = current->vertex;// 如果鄰接點未被訪問,則加入隊列并標記為已訪問if(!graph->isVisited[adjVertex]) {q.push(adjVertex);graph->isVisited[adjVertex] = true;}// 移動到下一個鄰接點 current = current->next;}}
}
// 清理Graph對象
void freeGraph(Graph *graph)
{if (!graph){return;}// 釋放鄰接表中每個鏈表占用的內存for (int i = 0; i < graph->numVertices; ++i){Node *current = graph->adjLists[i];while (current){Node *next = current->next; // 保存下一條邊的指針delete current; // 釋放當前邊的內存current = next; // 移動到下一條邊}}// 釋放鄰接表數組本身占用的內存delete[] graph->adjLists;// 釋放訪問標記數組占用的內存delete[] graph->isVisited;
}int main()
{// 創建一個包含4個頂點的圖Graph *graph = createGraph(4);addEdge(graph, 0, 1);addEdge(graph, 0, 2);addEdge(graph, 1, 2);addEdge(graph, 2, 3);addEdge(graph, 2, 0);// 深度優先遍歷圖cout << "深度優先遍歷(從頂點2開始):" << endl;DFS(graph, 2);cout << endl;cout << "廣度優先遍歷(從頂點2開始):" << endl;BFS(graph, 2);delete graph;graph = NULL;return 0;
}
-
問題1:初始化的時候把頂點numVertices 初始化為0了而且創建時候沒有再賦值,直接初始化傳入節點就行了
-
問題2 graph->isVisited[v] = true; 放在循環外面,那么下面訪問了之后會再標記嗎
當然會,這是遞歸,會調用自己