圖是非常重要的數據結構,現實中的很多問題都歸結於圖的問題,這裏我們討論一下圖,關於圖的基礎:頂點、邊等概念可參考文檔:Graph Data Stucture,這裏不再細述。
圖的表示
圖有兩種表示方法:鄰接矩陣、鄰接鏈表。不同的場景及算法可能需要不同的圖表示方式,一般情況下當結點數量非常龐大時,會造成矩陣非常稀疏,空間開銷會較大,此時使用鄰接鏈表的表示方式會佔用較少的空間。而如果是稠密矩陣或者需要快速判斷任意兩個結點是否有邊相連等情況,可能鄰接矩陣更合適。
鄰接矩陣
#include<iostream>
using namespace std;
// 無向有權圖
class Graph {
public:
Graph(int numVertices) {
this->numVertices = numVertices;
matrix = new int*[numVertices];
for (int i = 0; i < numVertices; ++i) {
matrix[i] = new int[numVertices];
for (int j = 0; j < numVertices; ++j) {
matrix[i][j] = 0;
}
}
}
void addEdge(int v1, int v2, int value = 1) {
matrix[v1][v2] = value;
matrix[v2][v1] = value;
}
void removeEdge(int v1, int v2) {
matrix[v1][v2] = 0;
matrix[v2][v1] = 0;
}
bool isEdge(int v1, int v2) {
return matrix[v1][v2] != 0 ? true : false ;
}
void print() {
for (int i = 0; i < numVertices; ++i) {
cout << i << " : ";
for (int j = 0; j < numVertices; ++j) {
cout << matrix[i][j] << " ";
}
cout << endl;
}
}
~Graph() {
for (int i = 0; i < numVertices; ++i) {
delete[] matrix[i];
}
delete[] matrix;
}
private:
int **matrix;
int numVertices;
};
鄰接鏈表
#include<iostream>
#include<list>
using namespace std;
//有向無權圖
class Graph {
public:
Graph(int numVertices) {
this->numVertices = numVertices;
adjLists = new list<int>[numVertices];
}
void addEdge(int src, int dst) {
adjLists[src].push_back(dst);
}
void removeEdge(int src, int dst) {
adjLists[src].remove(dst);
}
void print() {
for (int i = 0; i < numVertices; ++i) {
cout << i << " : ";
for (auto it = adjLists[i].begin(); it != adjLists[i].end(); ++it) {
cout << *it << " ";
}
cout << endl;
}
}
~Graph() {
delete[] adjLists;
}
private:
list<int> *adjLists;
int numVertices;
};
圖的遍歷
遍歷分兩種,深度優先與廣度優先。在遍歷時結點有兩種狀態:已訪問、未訪問,這樣如果一個結點在遍歷過程中已訪問的話就可以避免再次訪問。
深度優先遍歷
深度優先算法的過程如下:
- 可從任意一個頂點開始,將之放入棧中。
- 從棧頂彈出一個頂點,放入已訪問列表中。
- 將剛剛彈出的頂點的未訪問過的鄰接結點放入棧中。
- 重複步驟2、3直到棧爲空爲止。
void depthFirstSearch(int vertex) {
bool visited[this->numVertices];
for (auto i = 0; i < this->numVertices; ++i) {
visited[i] = false;
}
stack<int> m_stack;
m_stack.push(vertex);
while (!m_stack.empty()) {
auto v = m_stack.top();
if (visited[v]) {
m_stack.pop();
continue;
}
visit(v);
visited[v] = true;
m_stack.pop();
for (auto it = this->adjLists[v].begin(); it != this->adjLists[v].end(); ++ it) {
if (!visited[*it]) {
m_stack.push(*it);
}
}
}
}
廣度優先遍歷
廣度優先遍歷的步驟如下:
- 可從任意一個頂點開始,將之放入隊列中。
- 從隊列首部彈出一個頂點,放入已訪問列表中。
- 將剛剛彈出的頂點的未訪問過的鄰接結點放入隊列尾部。
- 重複步驟2、3直到隊列爲空爲止。
void breadthFirstSearch(int vertex) {
bool visited[this->numVertices];
for (auto i = 0; i < this->numVertices; ++i) {
visited[i] = false;
}
queue<int> m_queue;
m_queue.push(vertex);
while(!m_queue.empty()) {
auto v = m_queue.front();
if (visited[v]) {
m_queue.pop();
continue;
}
visit(v);
visited[v] = true;
m_queue.pop();
for (auto it = this->adjLists[v].begin(); it != this->adjLists[v].end(); ++it) {
if (!visited[*it]) {
m_queue.push(*it);
}
}
}
}
更多內容可關注公衆號: