图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。在学习图的过程中,知道图中存储的数据称为顶点,无向图连接顶点之间关系的称为边,有向图连接顶点的称为弧,弧的起点为弧尾,终点为弧头。图可以根据边有无方向,分为无向图和有向图,只要存在有方向的边,则为有向图,全部为无方向边的图,则为无向图。
1.邻接表
接表是图的一种链式存储结构。由两部分组成:表头结点表和边表。邻接表中每个单链表的第一个结点存放有关顶点的信息,把这一结点看成链表的表头,其余结点存放有关边的信息
(1)表头结点表:包括数据域和链域,数据域存储顶点的名称,链域用于指向链表中第一个结点(与顶点邻接的第一个顶点)
(2)边表:包括邻接点域(指示与顶点邻接的点在图中的位置,即数组下标)、数据域(存储和边相关的信息,如权值)、链域(指示与顶点邻接的下一条边的结点)。
代码设计如下:
//
// Created by A on 2023/6/10.
//
#include <iostream>
#include <vector>
#include <cassert>
#include <stack>
#include <queue>
#include <algorithm>
using namespace std;
class SparseGraph {
public:
int n, m;
bool directed;
vector<vector<int> > g;
SparseGraph(int n, bool directed) {
this->n = n;
this->m = m;
this->directed = directed;
for (int i = 0; i < n; ++i)
g.push_back(vector<int>());
}
~SparseGraph() {
}
int V() {
return n;
}
int E() {
return m;
}
void addEgde(int v, int w) {
assert(v >= 0 && v < n);
assert(w >= 0 && w < n);
g[v].push_back(w);
if (!directed)
g[w].push_back(v);
++m;
}
bool hasEdge(int v, int w) {
assert(v >= 0 && v < n);
assert(w >= 0 && w < n);
for (int i = 0; i < g[v].size(); ++i) {
if (g[v][i] == w)
return true;
}
return false;
}
void Print() {
for (int i = 0; i < g.size(); ++i) {
cout << "边" << i << ":";
for (int j = 0; j < g[i].size(); ++j) {
cout << g[i][j] << " ";
}
cout << endl;
}
}
void dfs(int v) {
vector<bool> visited(n, false);
stack<int> s;
s.push(v);
while (!s.empty()) {
int tmp = s.top();
if (!visited[tmp])
cout << tmp << " ";
visited[tmp] = true;
s.pop();
int size = g[tmp].size();
for (int i = 0; i < size; ++i) {
int b = g[tmp][i];
if (!visited[b])
s.push(b);
}
}
cout << endl;
}
void bfs(int v) {
vector<bool> visited(n, false);
queue<int> que;
que.push(v);
while (!que.empty()) {
int tmp = que.front();
if (!visited[tmp])
cout << tmp << " ";
visited[tmp] = true;
que.pop();
int size = g[tmp].size();
for (int i = 0; i < size; ++i) {
int b = g[tmp][i];
if (!visited[b])
que.push(b);
}
}
cout << endl;
}
};
int main() {
int vertex, edge;
cin >> vertex >> edge;
SparseGraph myGraph(vertex, false);
for (int i = 0; i < edge; ++i) {
int from, to;
cin >> from >> to;
assert(from >= 0 && from < vertex);
assert(from >= 0 && from < vertex);
myGraph.addEgde(from, to);
}
myGraph.Print();
myGraph.dfs(0);
myGraph.bfs(0);
return 0;
}
2.邻接矩阵
邻接矩阵是表示顶点之间相邻关系的矩阵。他由V和E集合,其中,V是顶点,E是边。因此,用一个一维数组存放图中所有顶点数据;用一个二维数组存放顶点间关系(边或弧)的数据,这个二维数组称为邻接矩阵。邻接矩阵又分有向图邻接矩阵和无向图邻接矩阵。
代码设计如下:
//
// Created by A on 2023/6/10.
//
#include <iostream>
#include <vector>
#include <cassert>
#include <stack>
#include <queue>
using namespace std;
class DenseGraph {
public:
int n, m;
bool directed;
vector<vector<int> > g;
DenseGraph(int n, bool directed) {
this->n = n;
this->m = m;
this->directed = directed;
for (int i = 0; i < n; ++i)
g.push_back(vector<int>(n, 0));
}
~DenseGraph() {
}
int V() {
return n;
}
int E() {
return m;
}
void addEgde(int v, int w) {
assert(v >= 0 && v < n);
assert(w >= 0 && w < n);
if (hasEdge(v, w))
return;
g[v][w] = 1;
if (!directed)
g[w][v] = 1;
++m;
}
bool hasEdge(int v, int w) {
assert(v >= 0 && v < n);
assert(w >= 0 && w < n);
return (g[v][w] == 1);
}
void Print() {
for (int i = 0; i < g.size(); ++i) {
for (int j = 0; j < g[i].size(); ++j) {
cout << g[i][j] << " ";
}
cout << endl;
}
}
void dfs(int v) {
assert(v >= 0 && v < n);
vector<bool> visited(n, false);
stack<int> s;
s.push(v);
while (!s.empty()) {
int tmp = s.top();
if (!visited[tmp])
cout << tmp << " ";
visited[tmp] = true;
s.pop();
int size = g[tmp].size();
for (int i = 0; i < size; ++i) {
int b = g[tmp][i];
if (b == 1 && !visited[i])
s.push(i);
}
}
cout << endl;
}
void bfs(int v) {
assert(v >= 0 && v < n);
vector<bool> visited(n, false);
queue<int> que;
que.push(v);
while (!que.empty()) {
int tmp = que.front();
if (!visited[tmp])
cout << tmp << " ";
visited[tmp] = true;
que.pop();
int size = g[tmp].size();
for (int i = 0; i < size; ++i) {
int b = g[tmp][i];
if (b == 1 && !visited[i])
que.push(i);
}
}
cout << endl;
}
};
int main() {
int vertex, edge;
cin >> vertex >> edge;
DenseGraph myGraph(vertex, false);
for (int i = 0; i < edge; ++i) {
int from, to;
cin >> from >> to;
assert(from >= 0 && from < vertex);
assert(from >= 0 && from < vertex);
myGraph.addEgde(from, to);
}
myGraph.Print();
myGraph.dfs(0);
myGraph.bfs(0);
return 0;
}
3.深度优先遍历(DFS)
图的深度优先遍历(Depth-First Search,DFS)是一种遍历图的方式,它从图的某个顶点开始,沿着一条路径一直走到底,直到不能继续为止,然后返回到上一个节点,继续尝试其他路径,直到所有的节点都被访问过为止。简单来说,就是尽可能深地访问每个节点,如果没有路可走就返回上一个节点。
具体实现时,可以使用递归或栈来实现深度优先遍历。在递归实现中,从起始节点开始,递归地遍历与该节点相邻的节点,直到遇到没有未被访问的相邻节点为止。在栈实现中,从起始节点开始,将其压入栈中,然后弹出栈顶节点,遍历该节点的未被访问的相邻节点,并将其压入栈中,直到栈为空为止。
4.图的广度优先遍历(BFS)
图的广度优先遍历(Breadth-First Search,BFS)是一种遍历图的方式,它从图的某个顶点开始,先访问该节点,然后访问与该节点相邻的所有节点,再依次访问这些相邻节点的相邻节点,直到所有节点都被访问过为止。简单来说,就是逐层访问节点,先访问距离起始节点最近的节点。
具体实现时,可以使用队列来实现广度优先遍历。从起始节点开始,将其加入队列,然后弹出队首节点,依次访问该节点的未被访问的相邻节点,并将其加入队列尾部,直到队列为空为止。需要注意的是,在访问每个节点时,要标记已经访问过的节点,以避免重复访问。
与深度优先遍历相比,广度优先遍历可以找到最短路径,因为它首先访问距离起始节点最近的节点。但是,广度优先遍历需要使用队列,空间复杂度比深度优先遍历高。
运行结果如下:
6 8
0 1
0 2
0 5
1 2
1 3
1 4
3 4
3 5
图的邻接表实现:
边0:1 2 5
边1:0 2 3 4
边2:0 1
边3:1 4 5
边4:1 3
边5:0 3
深度优先遍历结果:
0 5 3 4 1 2
广度优先遍历结果:
0 1 2 5 3 4
6 8
0 1
0 2
0 5
1 2
1 3
1 4
3 4
3 5
图的邻接矩阵实现:
0 1 1 0 0 1
1 0 1 1 1 0
1 1 0 0 0 0
0 1 0 0 1 1
0 1 0 1 0 0
1 0 0 1 0 0
深度优先遍历结果:
0 5 3 4 1 2
广度优先遍历结果:
0 1 2 5 3 4