一、引入概念————图
众所周知,我们常用到的数据结构有:集合,树,链表,队列,栈等等,那么今天,我们认识一种新的数据结构——图
概念:图,既是某类具体事物和这些事物之间的联系
我们具体阐述为:图是由顶点(vertex)和边(edge)组成的
-
顶点---具体事物 ( 顶点的集合V )
-
边---具体事物之间的联系( 边的集合E )
那么我们可以得到图的定义为 G = (V, E)
二、图的分类
- 无向图,即边没有指定方向的图
无向图的术语 两个顶点之间如果有边连接,那么就视为两个顶点相邻。
路径:相邻顶点的序列。
圈:起点和终点重合的路径。
连通图:任意两点之间都有路径连接的图。
度:顶点连接的边数叫做这个顶点的度。
树:没有圈的连通图。
森林:没有圈的非连通图。
2.有向图,即边具有指定方向的图 ( 有向图中的边又称为弧,起点称为弧头,终点称为弧尾 )
在有向图中,边是单向的:每条边所连接的两个顶点是一个有序对,他们的邻接性是单向的。
具体的说,如果存入一条边 (x, y) 我们只能说y是x的相邻点,而不能认为x是y的相邻点,因为有向图决定了这条边的方
向是明确的
有向路径:相邻顶点的序列。
有向环:一条至少含有一条边且起点和终点相同的有向路径。
有向无环图(DAG):没有环的有向图。(认识DAG)
度:一个顶点的入度与出度之和称为该顶点的度。
1)入度:以顶点为弧头的边的数目称为该顶点的入度
2)出度:以顶点为弧尾的边的数目称为该顶点的出度
图的属性
一般来讲,我们只需记录每条边的起点与终点,但有时题目背景下会为图增添更多新的属性,即带权图——
边上带有权值的图(在不同问题中,权值意义不同,可以是距离、时间、价格、代价等不同属性)
三、图的存储
这里以无向图为例
1.邻接矩阵:即使用n * n空间复杂度的01矩阵来表示两个点之间有无联系
scanf("%d %d", &x, &y);
g[x][y] = g[y][x] = 1;
2.邻接表:通过把“从顶点0出发有到顶点2,3,5的边”这样的信息保存在链表中来表示图
vector<int> G[Maxn];
int u, v;
u = read();
v = read();
g[u].push_back(v);
g[v].push_back(u);
另外,对于带权重的无向图,我们可以将vector声明为Node类型,具体实现如下
struct Node {
int next;
int val;
// 内部构造函数
Node(int x, int y) {
next = x;
val = y;
}
};
vector<Node> g[Maxn];
int u, v, w;
u = read();
v = read();
w = read();
g[u].push_back(Node(v, w));
g[v].push_back(Node(u, w));
3.链式前向星:实际可以看作数组模拟的邻接表,具体实现可参考代码的注释与计算的推导
int n, m, tot;
int head[Maxn], ver[Maxn], edge[Maxn], next[Maxn];
// Next表示与当前存储边起点相同的上一条边的编号
// head[i]表示以i为起点的最后一条边的编号
// 存储的均为下标
void add_edge(int u, int v, int w) {
ver[++tot] = v;
// 边的终点
edge[tot] = w;
// 权值
next[tot] = head[u];
// 当前边的插入,使得之前同起点的最后一条边成为当前边的上一条边
head[u] = tot;
// 更新当前边起点归属下最后一条边的编号
}
// 插入端点为(u, v)权重为w的无向边
int u, v, w;
scanf("%d %d %d", &u, &v, &w);
add_edge(u, v, w);
add_edge(v, u, w);
Node s[Maxn];
int cnt = 0;
// 遍历i与其相连接的点的存储下标
for (int j = head[i]; j; j = next[j]) {
s[cnt].val = edge[j];
s[cnt].id = ver[j];
cnt++;
}