树与图的遍历
时间戳
按照深度优先搜索的顺序进行遍历的过程中,按照每个结点第一次被访问(vis[x]被赋值为1时,开始递归时)的顺序,依次给予这n个点1~n的整数标记,这个标记就是时间戳。
树的DFS序
我们在进行深度优先遍历的时候,对于每个结点,在进入递归和即将回溯时都记录一次该点的编号,最后产生一个长度为2n的结点序列就被称为树的DFS序。
void dfs(int u) { a[++ m] = u; vis[u] = 1; for(int i = Head[u]; i != -1; i = Next[i]) { int v = To[i]; if(vis[v]) continue; dfs(v); } a[++ m] = u; return; }
DFS序的特点:每个结点恰好在序列中出现两次, 设这两个位置分别为l[x]和r[x],那么,[l[x], r[x]] 就是以x为根的子树。这可以用于把子树问题转化为区间的修改和查询问题。
树的深度(Deep)
树的深度是一种自顶向下的统计,我们已知根节点的深度为0。若u的深度为Deep[u],那么他的儿子结点Deep[v]的深度就是Deep[v] = Deep[v] + 1。在深度优先搜索的过程中,我们可以遍历出每个结点的深度。
// main函数中:Deep[root] = 0; void dfs(int u) { for(int i = Head[u]; i != -1; i = Next[i]) { int v = To[i]; if(Deep[v]) continue; Deep[v] = Deep[u] + 1; dfs(v); } return; }
树的子树大小和重心
树的子树大小是一个自底向上的统计量,对于叶子结点,他的子树大小为1,而对于任意一个非叶子结点的子树和的大小都是他的所有子树的子树大小的和加1。
void dfs(int u) { Son_size[u] = 1; vis[u] = 1; for(int i = Head[u]; i != -1; i = Next[i]) { int v = To[i]; if(vis[v]) continue; dfs(v); Son_size[u] += Son_size[v]; } return; }
对于一颗树的重心Zroot可以满足去掉Zroot后Zroot的子树森林中Son_size最大的子树的Son_size最小。
void dfs(int u) { int max_prat = 0; Son_size[u] = 1; vis[u] = 1; for(int i = Head[u]; i != -1; i = Next[i]) { int v = To[i]; if(vis[v]) continue; dfs(v); Son_size[u] += Son_size[v]; max_part = max(max_part, size[v]); } max_part = max(max_partm, size[n - u]); if(max_part < ans) { ans = max_part; // 全局变量ans是以现在的根为树的重心的树的max_part pos = u; // 全局变量pos记录重心 } return; }
广度优先搜索
广度优先搜索就是利用队列然后进行一层一层的搜索,相比于DFS避免了爆递归栈的危险。
另外BFS还有以下两个重要的性质:
- 在访问完第 i 层后才会开始访问第 i + 1 层。
- 任意时刻,队列中之多有两层的结点,且第 i 层的一定全部位于第 i + 1 层的后面。(“两段性” 和 "单调性“)。
拓扑序
对于一张有向无环图,对于任意一条由 u 指向 v 的边,我们都使u在序列中的位置位于v之前,这样所形成的序列就是拓扑序,对于拓扑序我们可以用于在递推条件与相邻的出发点有关的递推关系之中。