什么是图
在计算机科学和数学领域,图是一种用来表示对象之间关系的数据结构。图由结点和边组成,结点表示对象,边表示对象之间的关系。图可以用来描述真实世界中的各种事物之间的关系,如:社交网络中的用户和关注关系、物流网络中的仓库和路线等等。
无向图和有向图
在图中,边可以有方向,也可以没有方向。如果边没有方向,则称为无向图。如果边有方向,则称为有向图。
邻接矩阵
邻接矩阵是一种最常用的表示图的方法之一。邻接矩阵是一个二维数组,矩阵中的行和列代表图中的结点,矩阵中的值表示结点之间是否有边相连。
对于无向图来说,邻接矩阵是一个对称矩阵,因为边是没有方向的,所以矩阵中对称的两个元素表示的是同一条边。
对于有向图来说,邻接矩阵不一定是对称的,因为边是有方向的,所以矩阵中的值表示的是起点指向终点的边是否存在。
下面是使用邻接矩阵表示无向图的代码(C++):
#include <iostream>
using namespace std;
#define MAX 100
int graph[MAX][MAX]; // 定义二维数组表示邻接矩阵
int n, m;
int main() {
// 输入图的节点数和边数
cout << "请输入节点数和边数:";
cin >> n >> m;
// 初始化邻接矩阵
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
graph[i][j] = 0;
// 输入每条边的两个端点
int u, v;
cout << "请输入每条边的两个端点:" << endl;
for (int i = 1; i <= m; i++) {
cin >> u >> v;
graph[u][v] = graph[v][u] = 1; // 将邻接矩阵对应位置设为1
}
// 输出邻接矩阵
cout << "邻接矩阵为:" << endl;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
cout << graph[i][j] << " ";
}
cout << endl;
}
return 0;
}
// 其中,graph 数组的大小为 MAX * MAX,n 表示节点数,m 表示边数,输入时依次输入每条边的两个端点,并将邻接矩阵对应位置设为1。最后输出邻接矩阵即可。
下面是使用邻接矩阵表示有向图的代码(C++):
// 在有向图中,邻接矩阵是一个 $n \times n$ 的矩阵,其中元素 $a_{ij}$ 表示从节点 $i$ 到节点 $j$ 是否有一条有向边。
// 下面是一个示例代码用邻接矩阵实现有向图,并输出此有向图的邻接矩阵:
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n = 4; // 节点数
vector<vector<int>> graph(n, vector<int>(n)); // 邻接矩阵
// 添加边
graph[0][1] = 1;
graph[0][2] = 1;
graph[1][3] = 1;
graph[2][3] = 1;
// 输出邻接矩阵
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
cout << graph[i][j] << " ";
}
cout << endl;
}
return 0;
}
// 上面这段代码创建了一个包含 4 个节点的有向图,节点编号为 0 - 3,其中节点 0 和节点 2 相连,节点 0 和节点 1 相连,节点 1 和节点 3 相连,节点 2 和节点 3 相连。运行代码后,输出如下:
// 0 1 1 0
// 0 0 0 1
// 0 0 0 1
// 0 0 0 0
// 其中第 i行第 j列的元素a[i][j] 如果为 1,则表示从节点 i 到节点 j 存在一条有向边。如果为 0,则表示不存在。
邻接表
邻接表是另一种常用的表示图的方法。邻接表由一个数组和一些链表组成,数组中每个元素对应图中的一个结点,链表中存储着与该结点相邻的结点。
对于无向图来说,邻接表中的每个结点的链表中存储的都是与该结点相邻的结点。
对于有向图来说,邻接表中的每个结点的链表中存储的是以该结点为起点的边所连接的结点。
下面是使用邻接表表示无向图的代码(C++):
#include <iostream>
#include <vector>
using namespace std;
class Graph {
private:
int numVertices;
vector<vector<int>> adjList;
public:
Graph(int numVertices) {
this->numVertices = numVertices;
adjList.resize(numVertices);
}
void addEdge(int v, int w) {
adjList[v].push_back(w);
adjList[w].push_back(v);
}
void printGraph() {
for (int v = 0; v < numVertices; v++) {
cout << "Vertex " << v << ": ";
for (auto w : adjList[v])
cout << w << " ";
cout << endl;
}
}
};
int main() {
Graph g(5);
g.addEdge(0, 1);
g.addEdge(0, 4);
g.addEdge(1, 2);
g.addEdge(1, 3);
g.addEdge(1, 4);
g.addEdge(2, 3);
g.addEdge(3, 4);
g.printGraph();
return 0;
}
// 这个示例中,我们使用一个 vector<vector<int>>类型的邻接表来存储图的信息。在 Graph 类中,我们定义了 numVertices 作为顶点数,并在构造函数中初始化了邻接表。然后我们定义了 addEdge() 方法,用于添加边。因为这是一个无向图,所以我们在添加边时需要将 v 和 w 互相连接。
// 最后,我们定义了 printGraph() 方法,用于打印整个图的信息。在这个方法中,我们对每个顶点都输出与之相邻的顶点。
// 在 main()函数中,我们示例化了一个 Graph 对象并添加了几条边。最后,我们调用 printGraph() 方法来打印图的信息,输出结果如下:
// Vertex 0: 1 4
// Vertex 1: 0 2 3 4
// Vertex 2: 1 3
// Vertex 3: 1 2 4
// Vertex 4: 0 1 3
// 这个结果可以帮助我们理解邻接表的数据结构存储方式,即以每个顶点为索引,值是一个数组,表示与之相邻的所有顶点。
下面是使用邻接表表示有向图的代码(C++):
// 在有向图中,邻接表是一种链式存储结构,每个节点对应一个链表,记录该节点指向的所有其他节点。
// 下面是一个示例代码用邻接表实现有向图,并输出此有向图的邻接表:
#include <iostream>
#include <vector>
using namespace std;
// 邻接表节点
struct ListNode {
int val; // 节点编号
ListNode* next; // 指向下一个节点的指针
ListNode(int x = 0) : val(x), next(nullptr) {}
};
int main() {
int n = 4; // 节点数
vector<ListNode*> graph(n, nullptr); // 邻接表
// 添加边
graph[0] = new ListNode(1);
graph[0]->next = new ListNode(2);
graph[1] = new ListNode(3);
graph[2] = new ListNode(3);
// 输出邻接表
for (int i = 0; i < n; ++i) {
cout << i << " -> ";
ListNode* cur = graph[i];
while (cur) {
cout << cur->val << " ";
cur = cur->next;
}
cout << endl;
}
return 0;
}
//
// 上面这段代码创建了一个包含 4 个节点的有向图,节点编号为 0 - 3,其中节点 0 指向节点 1 和节点 2,节点 1 指向节点 3,节点 2 指向节点 3。运行代码后,输出如下:
// 0 -> 1 2
// 1 -> 3
// 2 -> 3
// 3 ->
// 其中,第 i 个邻接表的第一个节点就是编号为 i 的节点,其后面的节点就是从节点 i 出发可以到达的所有节点。
总结
邻接矩阵和邻接表都是一种用于表示图的数据结构,它们各自有它们的优缺点,适用于不同的场景。无论是邻接矩阵还是邻接表,都是图算法设计的基础。