AHU算法
AHU 算法用于判断两棵有根树是否同构。
判断树同构外还有一种常见的做法是 树哈希。
建议配合参考资料里给的例子观看。
树同构的定义¶
有根树同构¶
对于两棵有根树
且
无根树同构¶
对于两棵无根树
成立,那么称无根树
简单的说就是,如果能够通过把树
问题的转化¶
无根树同构问题可以转化为有根树同构问题。具体方法如下:
对于无根树
- 如果这两棵无根树重心数量不同,那么这两棵树不同构。
- 如果这两颗无根树重心数量都为
1 c_1 c_2 T_1(V_1,E_1,c_1) T_2(V_2,E_2,c_2) T_1(V_1, E_1) T_2(V_2,E_2) - 如果这两颗无根树重心数量都为
2 c_1,c^\prime_1 c_2,c^\prime_2 T_1(V_1,E_1,c_1) T_2(V_2,E_2,c_2) T_1(V_1,E_1,c^\prime_1) T_2(V_2,E_2,c_2) T_1(V_1, E_1) T_2(V_2,E_2)
所以,只要解决了有根树同构问题,我们就可以把无根树同构问题根据上述方法转化成有根树同构的问题,进而解决无根树同构的问题。
假设有一个可以
朴素的 AHU 算法¶
朴素的 AHU 算法是基于括号序的。
原理 1¶
我们知道一段合法的括号序和一棵有根树唯一对应,而且一棵树的括号序是由它的子树的括号序拼接而成的。如果我们通过改变子树括号序拼接的顺序,从而获得了一段新的括号序,那么新括号序对应的树和原括号序对应的树同构。
原理 2¶
树的同构关系是传递的。既如果
推论¶
考虑求树括号序的递归算法,我们在回溯时拼接子树的括号序。如果在拼接的时候将字典序小的序列先拼接,并将最后的结果记为
将以节点
命名算法¶
AHU 算法¶
复杂度证明¶
对于一颗有
优化的 AHU 算法¶
朴素的 AHU 算法的缺点是树的
原理 1¶
对树进行层次划分,第
原理 2¶
在同一层内,节点的
注意,这里的排名是对两棵树而言的,假设节点
推论¶
我们可以将节点原来的
这样用整数和数组来代替字符串,既不会影响算法的正确性,又很大的降低了算法的复杂度。
复杂度证明¶
首先注意到第
- 我们可以使用基数排序在
O(L+|\Sigma|) |\Sigma| - 我们可以使用快速排序在
O(L \log m) O(\log m) \ell_1 \ell_2 O(\min\{\ell_1,\ell_2\})
在 AHU 算法中,第
例题¶
题意翻译:给你两颗无根树,判断两棵树是否同构。
参考代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | // Tree Isomorphism, O(nlogn)
// replace quick sort with radix sort ==> O(n)
// Author: _Backl1ght
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5;
const int maxn = N << 1;
int n;
struct Edge {
int v, nxt;
} e[maxn << 1];
int head[maxn], sz[maxn], f[maxn], maxv[maxn], tag[maxn], tot, Max;
vector<int> center[2], L[maxn], subtree_tags[maxn];
void addedge(int u, int v) { // 建图
e[tot].v = v;
e[tot].nxt = head[u];
head[u] = tot++;
e[tot].v = u;
e[tot].nxt = head[v];
head[v] = tot++;
}
void dfs_size(int u, int fa) { // 找到 size 值
sz[u] = 1;
maxv[u] = 0;
for (int i = head[u]; i; i = e[i].nxt) {
int v = e[i].v;
if (v == fa) continue;
dfs_size(v, u);
sz[u] += sz[v];
maxv[u] = max(maxv[u], sz[v]);
}
}
void dfs_center(int rt, int u, int fa, int id) {
maxv[u] = max(maxv[u], sz[rt] - sz[u]);
if (Max > maxv[u]) {
center[id].clear();
Max = maxv[u];
}
if (Max == maxv[u]) center[id].push_back(u); // 如果相等就 push_back
for (int i = head[u]; i; i = e[i].nxt) {
int v = e[i].v;
if (v == fa) continue;
dfs_center(rt, v, u, id);
}
}
int dfs_height(int u, int fa, int depth) { // 递归查找 height
L[depth].push_back(u);
f[u] = fa;
int h = 0;
for (int i = head[u]; i; i = e[i].nxt) {
int v = e[i].v;
if (v == fa) continue;
h = max(h, dfs_height(v, u, depth + 1));
}
return h + 1;
}
void init(int n) { // 一开始的处理
for (int i = 1; i <= 2 * n; i++) head[i] = 0;
tot = 1;
center[0].clear();
center[1].clear();
int u, v;
for (int i = 1; i <= n - 1; i++) {
scanf("%d %d", &u, &v);
addedge(u, v);
}
dfs_size(1, -1);
Max = n;
dfs_center(1, 1, -1, 0);
for (int i = 1; i <= n - 1; i++) {
scanf("%d %d", &u, &v);
addedge(u + n, v + n);
}
dfs_size(1 + n, -1);
Max = n;
dfs_center(1 + n, 1 + n, -1, 1);
}
bool cmp(int u, int v) { return subtree_tags[u] < subtree_tags[v]; }
bool rootedTreeIsomorphism(int rt1, int rt2) {
for (int i = 0; i <= 2 * n + 1; i++) L[i].clear(), subtree_tags[i].clear();
int h1 = dfs_height(rt1, -1, 0);
int h2 = dfs_height(rt2, -1, 0);
if (h1 != h2) return false;
int h = h1 - 1;
for (int j = 0; j < (int)L[h].size(); j++) tag[L[h][j]] = 0;
for (int i = h - 1; i >= 0; i--) {
for (int j = 0; j < (int)L[i + 1].size(); j++) {
int v = L[i + 1][j];
subtree_tags[f[v]].push_back(tag[v]);
}
sort(L[i].begin(), L[i].end(), cmp);
for (int j = 0, cnt = 0; j < (int)L[i].size(); j++) {
if (j && subtree_tags[L[i][j]] != subtree_tags[L[i][j - 1]]) ++cnt;
tag[L[i][j]] = cnt;
}
}
return subtree_tags[rt1] == subtree_tags[rt2];
}
bool treeIsomorphism() {
if (center[0].size() == center[1].size()) {
if (rootedTreeIsomorphism(center[0][0], center[1][0])) return true;
if (center[0].size() > 1)
return rootedTreeIsomorphism(center[0][0], center[1][1]);
}
return false;
}
int main() {
int T;
scanf("%d", &T);
while (T--) {
scanf("%d", &n);
init(n);
puts(treeIsomorphism() ? "YES" : "NO");
}
return 0;
}
|
参考资料¶
本文大部分内容译自 Paper 和 Slide。参考资料里的证明会更加全面和严谨,本文做了一定的简化。
对 AHU 算法的复杂度分析,以及字符串的线性时间基数排序算法可以参见 The Design and Analysis of Computer Algorithms 的 3.2 节 Radix sorting,以及其中的 Example 3.2。
build本页面最近更新:,更新历史
edit发现错误?想一起完善? 在 GitHub 上编辑此页!
people本页面贡献者:Backl1ght
copyright本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用