圖的基礎(chǔ)操作可分為對(duì)“邊”的操作和對(duì)“頂點(diǎn)”的操作。在“鄰接矩陣”和“鄰接表”兩種表示方法下,實(shí)現(xiàn)方式有所不同。
給定一個(gè)頂點(diǎn)數(shù)量為 n 的無向圖,則各種操作的實(shí)現(xiàn)方式如圖 9-7 所示。
圖 9-7 鄰接矩陣的初始化、增刪邊、增刪頂點(diǎn)
以下是基于鄰接矩陣表示圖的實(shí)現(xiàn)代碼。
graph_adjacency_matrix.cpp
/* 基于鄰接矩陣實(shí)現(xiàn)的無向圖類 */
class GraphAdjMat {
vector<int> vertices; // 頂點(diǎn)列表,元素代表“頂點(diǎn)值”,索引代表“頂點(diǎn)索引”
vector<vector<int>> adjMat; // 鄰接矩陣,行列索引對(duì)應(yīng)“頂點(diǎn)索引”
public:
/* 構(gòu)造方法 */
GraphAdjMat(const vector<int> &vertices, const vector<vector<int>> &edges) {
// 添加頂點(diǎn)
for (int val : vertices) {
addVertex(val);
}
// 添加邊
// 請(qǐng)注意,edges 元素代表頂點(diǎn)索引,即對(duì)應(yīng) vertices 元素索引
for (const vector<int> &edge : edges) {
addEdge(edge[0], edge[1]);
}
}
/* 獲取頂點(diǎn)數(shù)量 */
int size() const {
return vertices.size();
}
/* 添加頂點(diǎn) */
void addVertex(int val) {
int n = size();
// 向頂點(diǎn)列表中添加新頂點(diǎn)的值
vertices.push_back(val);
// 在鄰接矩陣中添加一行
adjMat.emplace_back(vector<int>(n, 0));
// 在鄰接矩陣中添加一列
for (vector<int> &row : adjMat) {
row.push_back(0);
}
}
/* 刪除頂點(diǎn) */
void removeVertex(int index) {
if (index >= size()) {
throw out_of_range("頂點(diǎn)不存在");
}
// 在頂點(diǎn)列表中移除索引 index 的頂點(diǎn)
vertices.erase(vertices.begin() + index);
// 在鄰接矩陣中刪除索引 index 的行
adjMat.erase(adjMat.begin() + index);
// 在鄰接矩陣中刪除索引 index 的列
for (vector<int> &row : adjMat) {
row.erase(row.begin() + index);
}
}
/* 添加邊 */
// 參數(shù) i, j 對(duì)應(yīng) vertices 元素索引
void addEdge(int i, int j) {
// 索引越界與相等處理
if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) {
throw out_of_range("頂點(diǎn)不存在");
}
// 在無向圖中,鄰接矩陣沿主對(duì)角線對(duì)稱,即滿足 (i, j) == (j, i)
adjMat[i][j] = 1;
adjMat[j][i] = 1;
}
/* 刪除邊 */
// 參數(shù) i, j 對(duì)應(yīng) vertices 元素索引
void removeEdge(int i, int j) {
// 索引越界與相等處理
if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) {
throw out_of_range("頂點(diǎn)不存在");
}
adjMat[i][j] = 0;
adjMat[j][i] = 0;
}
/* 打印鄰接矩陣 */
void print() {
cout << "頂點(diǎn)列表 = ";
printVector(vertices);
cout << "鄰接矩陣 =" << endl;
printVectorMatrix(adjMat);
}
};
設(shè)無向圖的頂點(diǎn)總數(shù)為 n、邊總數(shù)為 m ,則可根據(jù)圖 9-8 所示的方法實(shí)現(xiàn)各種操作。
圖 9-8 鄰接表的初始化、增刪邊、增刪頂點(diǎn)
以下是基于鄰接表實(shí)現(xiàn)圖的代碼示例。細(xì)心的同學(xué)可能注意到,我們?cè)卩徑颖碇惺褂?nbsp;Vertex 節(jié)點(diǎn)類來表示頂點(diǎn),而這樣做是有原因的。
graph_adjacency_list.cpp
/* 基于鄰接表實(shí)現(xiàn)的無向圖類 */
class GraphAdjList {
public:
// 鄰接表,key: 頂點(diǎn),value:該頂點(diǎn)的所有鄰接頂點(diǎn)
unordered_map<Vertex *, vector<Vertex *>> adjList;
/* 在 vector 中刪除指定節(jié)點(diǎn) */
void remove(vector<Vertex *> &vec, Vertex *vet) {
for (int i = 0; i < vec.size(); i++) {
if (vec[i] == vet) {
vec.erase(vec.begin() + i);
break;
}
}
}
/* 構(gòu)造方法 */
GraphAdjList(const vector<vector<Vertex *>> &edges) {
// 添加所有頂點(diǎn)和邊
for (const vector<Vertex *> &edge : edges) {
addVertex(edge[0]);
addVertex(edge[1]);
addEdge(edge[0], edge[1]);
}
}
/* 獲取頂點(diǎn)數(shù)量 */
int size() {
return adjList.size();
}
/* 添加邊 */
void addEdge(Vertex *vet1, Vertex *vet2) {
if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2)
throw invalid_argument("不存在頂點(diǎn)");
// 添加邊 vet1 - vet2
adjList[vet1].push_back(vet2);
adjList[vet2].push_back(vet1);
}
/* 刪除邊 */
void removeEdge(Vertex *vet1, Vertex *vet2) {
if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2)
throw invalid_argument("不存在頂點(diǎn)");
// 刪除邊 vet1 - vet2
remove(adjList[vet1], vet2);
remove(adjList[vet2], vet1);
}
/* 添加頂點(diǎn) */
void addVertex(Vertex *vet) {
if (adjList.count(vet))
return;
// 在鄰接表中添加一個(gè)新鏈表
adjList[vet] = vector<Vertex *>();
}
/* 刪除頂點(diǎn) */
void removeVertex(Vertex *vet) {
if (!adjList.count(vet))
throw invalid_argument("不存在頂點(diǎn)");
// 在鄰接表中刪除頂點(diǎn) vet 對(duì)應(yīng)的鏈表
adjList.erase(vet);
// 遍歷其他頂點(diǎn)的鏈表,刪除所有包含 vet 的邊
for (auto &adj : adjList) {
remove(adj.second, vet);
}
}
/* 打印鄰接表 */
void print() {
cout << "鄰接表 =" << endl;
for (auto &adj : adjList) {
const auto &key = adj.first;
const auto &vec = adj.second;
cout << key->val << ": ";
printVector(vetsToVals(vec));
}
}
};
設(shè)圖中共有 n 個(gè)頂點(diǎn)和 m 條邊,表 9-2 對(duì)比了鄰接矩陣和鄰接表的時(shí)間和空間效率。
表 9-2 鄰接矩陣與鄰接表對(duì)比
觀察表 9-2 ,似乎鄰接表(哈希表)的時(shí)間與空間效率最優(yōu)。但實(shí)際上,在鄰接矩陣中操作邊的效率更高,只需要一次數(shù)組訪問或賦值操作即可。綜合來看,鄰接矩陣體現(xiàn)了“以空間換時(shí)間”的原則,而鄰接表體現(xiàn)了“以時(shí)間換空間”的原則。
更多建議: