树和图的存储和遍历
树和图的存储和遍历
树和图的存储
- 树是一种特殊的图(无环连通图),因此可以把树当作图来处理
- 图分为有向图(a->b)和无向图(a-b),但我们在算法题中如果遇到无向图,就直接(a->b)和(b->a)都建立,因此无向图就是特殊的有向图。
- 存储方式
(1)邻接矩阵 g[N][N]
- g[a][b] = 0 代表 节点a->b没有边
- g[a][b] = 1 代表 a->b有边
- g[b][a] = 1 代表 b->a有边
- g[a][b] = w 代表 a->b 该边的权重或者长度为w
- 空间复杂度为O(n²),适合存储稠密图
(2)邻接表
我们可以想一下对于任意一个结点u, 需要记录邻边的哪些信息。
这些信息应该包括这条邻边的终点,权重,以及下一条邻边的编号。
- 每个点上都有一个单链表,存的是这个点可以走到哪些点
- h数组的下标为结点的编号,e,ne数组的下标为边的编号,e数组的值为该边的终点,idx为边的编号
- 邻接表初始化(先将h数组都置为-1再插入节点)
1
2
3
4
5
6
7
8// N代表点的个数,M代表边的条数
// n个结点的树最多有n - 1条边,如果考虑无向边需要开两倍的n - 1来存储
int h[N],e[2*N],ne[2*N],idx; //有n个单链表就有n个头节点
memset(h,-1,sizeof(h)); //所有头节点都指向-1
// h[i]:第 i 个节点的第一条邻边的 idx
// e[idx]:存储 idx 这条边的终点,也就是与第 i 个节点相连的某一个点
// ne[idx]:存储 与第 idx 条边 同起点的 下一条边的 idx,也就是邻接表中的下一个节点
// idx:用于标识每条边的下标,存的是边的编号 - 邻接表插入元素
1
2
3
4
5
6
7
8
9
10
11
12//有一条a->b的边
void add(int a,int b){
e[idx] = b;
ne[idx] = h[a]; // 新插入的边是插到链表头(头插法)
h[a] = idx; //更新链表头
idx++;
}
//树的边数等于节点数减1
for(int i=0;i<n-1;i++){
cin >> a >> b;
add(a,b); add(b,a); //无向图
}
树和图的遍历
遍历前要记得将头指针数组设置为 -1
对于树和图的遍历,不管是DFS还是BFS,因为每个点只会被遍历一次,所以时间复杂度与点和边的数量成线性关系,为O(n + m)
dfs 和 bfs 的不同
不同:
参数上:
- void dfs(int u) //u代表层数 调用: dfs(1);
- int bfs() //没有参数 调用:cout << bfs(); //在函数里面会返回需要的答案
变量上
bfs 多了一个距离数组,初始时要将其置为-1memset(d,-1,sizeof(d))
dfs 用st[N] 当作状态数组 ,bfs可以使用距离数组 d 来充当状态数组 ;代码结构
dfs: 递归结构
bfs: 初始化 + while(队列非空)的循环
相同:
- 初始化上
要在开始时将当前正在访问的点设置为true
dfs
从节点编号1开始遍历,沿着节点的邻接表一路深搜
1 |
|
例题:景区导游
1 |
|
bfs
距离从小到大来遍历,取第一次遍历到的结果(每个点只遍历一次)。
遍历框架:
1 |
|
数组模拟队列常见操作
1 |
|
例题:图中点的层次
每条边长度都为1的最短路问题
1 |
|
LCA
定义:在有根树上,两点的祖先有公共部分,这些点叫做他们的公共祖先,而其中深度最深的点,叫作它们的最近公共祖先(LCA ,Lowest Common Ancestors)
求树上两个点距离的时候,可以预处理出每个点到根节点的距离,然后两点间最短距离公式为:dist[a->b] = dist[a]+dist[b]-2*dist[p]