一种调试 线段树 / Treap / Splay / 左偏树 / LCT 等树形结构的技巧
前言
如果我们需要观察程序运行过程中,某一个变量、某一个序列的变化情况,你可以在修改的地方打断点 debug,或者直接在需要的地方输出就行了。
但是对于一些树形结构,我们不好将其直观地呈现出来,常常只是输出每一个结点的值,但是这就丢失了结点之间的连边情况。有时候不得不手动画图。
所以我们经常累死。
于是,为了让我们活着,我想到了一种轻量级的,在终端直观呈现树形结构的方法。
正文
经典例子
回顾如下场景:
- Windows 下命令行中,我们使用
tree
来观察目录结构。
比如,在某一目录下,使用 tree /A /F
的输出如下:
+---.vscode
| launch.json
|
+---blog-prettier
| LICENSE
| README.md
|
+---web server
| | checkstatues.log
| | client.html
| | data.txt
| | gen-key.py
| | main_service.log
| | script-obfsed.js
| | test.html
| |
| \---fetch-new-url
| | README.md
| |
| \---docs
| test
|
\---test
a.html
b.html
index.html
script.js
style.css
这种经典的方法显然可以运用到我们的调试中。
分析
二叉树
我们不妨来考虑简单的二叉树,例如线段树、Treap、Splay 等平衡树。
我们考虑一种最简单的递归过程,仅在参数中传递输出的前缀。简单码出以下代码:
void output(int x, string pre) {
cout << pre << "-" << x << ": " << tr[x].val << endl;
if (!x) return;
output(tr[x].son[1], pre + " |");
output(tr[x].son[0], pre + " |");
}
void output() {
output(root, ">");
}
这里先输出再 return
是为了让输出的二叉树更好看,不然遇到一个孩子不知道是左儿子还是右儿子。
将右儿子作为第一个儿子输出,是为了符合二叉查找树。
可能的输出:一棵不断插入的 Splay
>-1: 1
> |-0: 0
> |-0: 0
>-2: 1
> |-1: 1
> | |-0: 0
> | |-0: 0
> |-0: 0
>-3: 4
> |-0: 0
> |-1: 1
> | |-0: 0
> | |-2: 1
> | | |-0: 0
> | | |-0: 0
>-4: 5
> |-0: 0
> |-3: 4
> | |-0: 0
> | |-1: 1
> | | |-0: 0
> | | |-2: 1
> | | | |-0: 0
> | | | |-0: 0
>-5: 1
> |-3: 4
> | |-4: 5
> | | |-0: 0
> | | |-0: 0
> | |-2: 1
> | | |-1: 1
> | | | |-0: 0
> | | | |-0: 0
> | | |-0: 0
> |-0: 0
>-6: 4
> |-3: 4
> | |-4: 5
> | | |-0: 0
> | | |-0: 0
> | |-0: 0
> |-5: 1
> | |-1: 1
> | | |-0: 0
> | | |-2: 1
> | | | |-0: 0
> | | | |-0: 0
> | |-0: 0
这对于考场上调试来说已经足够了,仅需将头逆时针旋转 \(45^\circ\) 就能看到一棵完美的二叉树了。你可以在每个结点之后输出更多的信息。
但是,我们怎样达到更完美的效果呢,比如第二个孩子之前不输出树杈、第二个孩子后输出空行(多个第二个孩子仅输出一个空行)等等。
我们仅需多记录是否是第一个孩子即可。
void output(int x, string pre, bool firstSon) {
cout << pre << (firstSon ? "+" : "\\") << "---" << x << ": " << tr[x].val << endl;
if (!x) return;
pre += firstSon ? "|" : " ";
output(tr[x].son[1], pre + " ", true);
output(tr[x].son[0], pre + " ", false);
if (firstSon) cout << pre << endl;
}
void output() {
output(root, "", false);
}
效果见文末。
多叉树
多叉树就只能是 LCT 了吧,还有什么扭曲的树你必须要打印出来的?
虽然好像打印出来还是不方便调试……
我们加以改进,由于有了虚实链之分,我们在空节点不直接 return
,而是输出一条边。然后把是否是第一个孩子,变成是否是最后一个孩子。
代码:
vector<int> edge[N];
void output(int x, string pre, bool lastSon, bool real) {
cout << pre << (!lastSon ? "+" : "\\") << "---";
if (x) cout << x << ": " << tr[x].val << endl;
else cout << "null" << endl;
pre += !lastSon ? (real ? "|" : "`") : " ";
if (x && (tr[x].son[0] || tr[x].son[1] || edge[x].size())) {
pushdown(x);
output(tr[x].son[1], pre + " ", false, true);
output(tr[x].son[0], pre + " ", edge[x].empty(), false);
for (int y : edge[x])
output(y, pre + " ", y == edge[x].back(), false);
}
if (!lastSon) cout << pre << endl;
}
void output(int n) {
for (int i = 1; i <= n; ++i)
edge[i].clear();
for (int i = 1; i <= n; ++i)
if (isRoot(i))
edge[tr[i].fa].emplace_back(i);
cout << "==== LCT forest ====" << endl;
for (int i = 1; i <= n; ++i)
if (!tr[i].fa)
output(i, "", true, false);
cout << "====================" << endl;
}
效果见文末。
代码
二叉树
void output(int x, string pre, bool firstSon) {
cout << pre << (firstSon ? "+" : "\\") << "---" << x << ": " << tr[x].val << endl;
if (!x) return;
pre += firstSon ? "|" : " ";
output(tr[x].son[1], pre + " ", true);
output(tr[x].son[0], pre + " ", false);
if (firstSon) cout << pre << endl;
}
void output() {
output(root, "", false);
}
多叉树 LCT
vector<int> edge[N];
void output(int x, string pre, bool lastSon, bool real) {
cout << pre << (!lastSon ? "+" : "\\") << "---";
if (x) cout << x << ": " << tr[x].val << endl;
else cout << "null" << endl;
pre += !lastSon ? (real ? "|" : "`") : " ";
if (x && (tr[x].son[0] || tr[x].son[1] || edge[x].size())) {
pushdown(x);
output(tr[x].son[1], pre + " ", false, true);
output(tr[x].son[0], pre + " ", edge[x].empty(), false);
for (int y : edge[x])
output(y, pre + " ", y == edge[x].back(), false);
}
if (!lastSon) cout << pre << endl;
}
void output(int n) {
for (int i = 1; i <= n; ++i)
edge[i].clear();
for (int i = 1; i <= n; ++i)
if (isRoot(i))
edge[tr[i].fa].emplace_back(i);
cout << "==== LCT forest ====" << endl;
for (int i = 1; i <= n; ++i)
if (!tr[i].fa)
output(i, "", true, false);
cout << "====================" << endl;
}
输出效果
可能的输出:一棵不断插入的 Splay
\---1: 1
+---0: 0
\---0: 0
\---2: 1
+---1: 1
| +---0: 0
| \---0: 0
|
\---0: 0
\---3: 4
+---0: 0
\---1: 1
+---0: 0
\---2: 1
+---0: 0
\---0: 0
\---4: 5
+---0: 0
\---3: 4
+---0: 0
\---1: 1
+---0: 0
\---2: 1
+---0: 0
\---0: 0
\---5: 1
+---3: 4
| +---4: 5
| | +---0: 0
| | \---0: 0
| |
| \---2: 1
| +---1: 1
| | +---0: 0
| | \---0: 0
| |
| \---0: 0
|
\---0: 0
\---6: 4
+---3: 4
| +---4: 5
| | +---0: 0
| | \---0: 0
| |
| \---0: 0
|
\---5: 1
+---1: 1
| +---0: 0
| \---2: 1
| +---0: 0
| \---0: 0
|
\---0: 0
可能的输出:一棵带有左右边界的不断插入的 Treap
\---2: inf
+---0: 0
\---1: -inf
+---3: 1
| +---0: 0
| \---0: 0
|
\---0: 0
\---2: inf
+---0: 0
\---1: -inf
+---3: 1
| +---0: 0
| \---0: 0
|
\---0: 0
\---2: inf
+---0: 0
\---1: -inf
+---3: 1
| +---4: 4
| | +---0: 0
| | \---0: 0
| |
| \---0: 0
|
\---0: 0
\---2: inf
+---0: 0
\---1: -inf
+---3: 1
| +---5: 5
| | +---0: 0
| | \---4: 4
| | +---0: 0
| | \---0: 0
| |
| \---0: 0
|
\---0: 0
\---2: inf
+---0: 0
\---1: -inf
+---3: 1
| +---5: 5
| | +---0: 0
| | \---4: 4
| | +---0: 0
| | \---0: 0
| |
| \---0: 0
|
\---0: 0
\---2: inf
+---0: 0
\---1: -inf
+---3: 1
| +---5: 5
| | +---0: 0
| | \---4: 4
| | +---0: 0
| | \---0: 0
| |
| \---0: 0
|
\---0: 0
可能的输出:一棵不断插入的无旋 Treap
\---1: 1
+---0: 0
\---0: 0
\---1: 1
+---0: 0
\---2: 1
+---0: 0
\---0: 0
\---3: 4
+---0: 0
\---1: 1
+---0: 0
\---2: 1
+---0: 0
\---0: 0
\---3: 4
+---4: 5
| +---0: 0
| \---0: 0
|
\---1: 1
+---0: 0
\---2: 1
+---0: 0
\---0: 0
\---5: 1
+---3: 4
| +---4: 5
| | +---0: 0
| | \---0: 0
| |
| \---1: 1
| +---0: 0
| \---2: 1
| +---0: 0
| \---0: 0
|
\---0: 0
\---5: 1
+---6: 4
| +---3: 4
| | +---4: 5
| | | +---0: 0
| | | \---0: 0
| | |
| | \---0: 0
| |
| \---1: 1
| +---0: 0
| \---2: 1
| +---0: 0
| \---0: 0
|
\---0: 0
可能的输出:一棵动态开点线段树
\---[1, 5]: 1
+---[1, 3]: 0
\---[4, 5]: 1
+---[4, 4]: 0
\---[5, 5]: 1
\---[1, 5]: 6
+---[1, 3]: 0
\---[4, 5]: 6
+---[4, 4]: 0
\---[5, 5]: 6
\---[1, 5]: 10
+---[1, 3]: 0
\---[4, 5]: 10
+---[4, 4]: 4
\---[5, 5]: 6
\---[1, 5]: 12
+---[1, 3]: 2
| +---[1, 2]: 0
| \---[3, 3]: 2
|
\---[4, 5]: 10
+---[4, 4]: 4
\---[5, 5]: 6
\---[1, 5]: 15
+---[1, 3]: 5
| +---[1, 2]: 3 (with lazy = 3)
| | +---[1, 1]: 0
| | \---[2, 2]: 0
| |
| \---[3, 3]: 2
|
\---[4, 5]: 10
+---[4, 4]: 4
\---[5, 5]: 6
\---[1, 5]: 15
+---[1, 3]: 5
| +---[1, 2]: 3 (with lazy = 3)
| | +---[1, 1]: 0
| | \---[2, 2]: 0
| |
| \---[3, 3]: 2
|
\---[4, 5]: 10
+---[4, 4]: 4
\---[5, 5]: 6
\---[1, 5]: 19
+---[1, 3]: 5
| +---[1, 2]: 3 (with lazy = 3)
| | +---[1, 1]: 0
| | \---[2, 2]: 0
| |
| \---[3, 3]: 2
|
\---[4, 5]: 14
+---[4, 4]: 6
\---[5, 5]: 8
\---[1, 5]: 19
+---[1, 3]: 5
| +---[1, 2]: 3 (with lazy = 3)
| | +---[1, 1]: 0
| | \---[2, 2]: 0
| |
| \---[3, 3]: 2
|
\---[4, 5]: 14
+---[4, 4]: 6
\---[5, 5]: 8
\---[1, 5]: 24 (with lazy = 1)
+---[1, 3]: 5
| +---[1, 2]: 3 (with lazy = 3)
| | +---[1, 1]: 0
| | \---[2, 2]: 0
| |
| \---[3, 3]: 2
|
\---[4, 5]: 14
+---[4, 4]: 6
\---[5, 5]: 8
\---[1, 5]: 24 (with lazy = 1)
+---[1, 3]: 5
| +---[1, 2]: 3 (with lazy = 3)
| | +---[1, 1]: 0
| | \---[2, 2]: 0
| |
| \---[3, 3]: 2
|
\---[4, 5]: 14
+---[4, 4]: 6
\---[5, 5]: 8
可能的输出:一棵树状数组
这玩意你还要调试?
可能的输出:左偏树森林
==== 左偏树 1 ====
\---5: <4, 2>
+---3: <4, 3>
| +---4: <5, 2>
| | +---0: <0, 0>
| | \---7: <9, 4>
| | +---0: <0, 0>
| | \---0: <0, 0>
| |
| \---0: <0, 0>
|
\---0: <0, 0>
==== 左偏树 2 ====
\---1: <3, 3>
+---6: <8, 4>
| +---0: <0, 0>
| \---2: <9, 3>
| +---0: <0, 0>
| \---0: <0, 0>
|
\---0: <0, 0>
==== 左偏树 1 ====
\---5: <4, 2>
+---3: <4, 3>
| +---4: <5, 2>
| | +---0: <0, 0>
| | \---7: <9, 4>
| | +---0: <0, 0>
| | \---0: <0, 0>
| |
| \---0: <0, 0>
|
\---1: <5, 3>
+---6: <8, 4>
| +---0: <0, 0>
| \---2: <9, 3>
| +---0: <0, 0>
| \---0: <0, 0>
|
\---0: <0, 0>
==== 左偏树 1 ====
\---3: <4, 3>
+---4: <5, 2>
| +---0: <0, 0>
| \---7: <9, 4>
| +---0: <0, 0>
| \---0: <0, 0>
|
\---1: <5, 3>
+---6: <8, 4>
| +---0: <0, 0>
| \---2: <9, 3>
| +---0: <0, 0>
| \---0: <0, 0>
|
\---0: <0, 0>
==== 左偏树 1 ====
\---4: <5, 2>
+---1: <5, 3>
| +---6: <10, 4>
| | +---0: <0, 0>
| | \---2: <9, 3>
| | +---0: <0, 0>
| | \---0: <0, 0>
| |
| \---7: <11, 4>
| +---0: <0, 0>
| \---0: <0, 0>
|
\---0: <0, 0>
==== 左偏树 1 ====
\---1: <5, 3>
+---6: <10, 4>
| +---0: <0, 0>
| \---2: <9, 3>
| +---0: <0, 0>
| \---0: <0, 0>
|
\---7: <11, 4>
+---0: <0, 0>
\---0: <0, 0>
==== 左偏树 1 ====
\---6: <10, 4>
+---2: <11, 3>
| +---0: <0, 0>
| \---7: <11, 4>
| +---0: <0, 0>
| \---0: <0, 0>
|
\---0: <0, 0>
==== 左偏树 1 ====
\---2: <11, 3>
+---0: <0, 0>
\---7: <11, 4>
+---0: <0, 0>
\---0: <0, 0>
==== 左偏树 1 ====
\---7: <11, 4>
+---0: <0, 0>
\---0: <0, 0>
==== 左偏树 1 ====
\---0: <0, 0>
可能的输出:Link Cut Tree
==== LCT forest ====
\---1: 114
\---2: 514
\---3: 19
\---4: 19
\---5: 810
====================
link 1 and 2 success
==== LCT forest ====
\---2: 514
+---null
|
+---null
`
\---1: 114
\---3: 19
\---4: 19
\---5: 810
====================
cut 1 and 2 success
==== LCT forest ====
\---1: 114
\---2: 514
\---3: 19
\---4: 19
\---5: 810
====================
link 1 and 2 success
==== LCT forest ====
\---2: 514
+---null
|
+---null
`
\---1: 114
\---3: 19
\---4: 19
\---5: 810
====================
link 2 and 3 success
==== LCT forest ====
\---3: 19
+---null
|
+---null
`
\---2: 514
+---null
|
+---null
`
\---1: 114
\---4: 19
\---5: 810
====================
cut 1 and 3 failed
==== LCT forest ====
\---1: 114
+---2: 514
| +---3: 19
| |
| \---null
|
\---null
\---4: 19
\---5: 810
====================
link 1 and 3 failed
==== LCT forest ====
\---1: 114
+---3: 19
| +---null
| |
| \---2: 514
|
\---null
\---4: 19
\---5: 810
====================
link 4 and 5 success
==== LCT forest ====
\---1: 114
+---3: 19
| +---null
| |
| \---2: 514
|
\---null
\---5: 810
+---null
|
+---null
`
\---4: 19
====================
link 2 and 5 success
==== LCT forest ====
\---5: 810
+---null
|
+---null
`
+---2: 514
` +---1: 114
` |
` +---null
` `
` \---3: 19
`
\---4: 19
====================
modify value 5 to 233333 success
==== LCT forest ====
\---5: 233333
+---null
|
+---null
`
+---2: 514
` +---1: 114
` |
` +---null
` `
` \---3: 19
`
\---4: 19
====================
access 3 success
==== LCT forest ====
\---5: 233333
+---2: 514
| +---3: 19
| |
| +---null
| `
| \---1: 114
|
+---null
`
\---4: 19
====================
split 2 ~ 4 success
==== LCT forest ====
\---4: 19
+---null
|
\---5: 233333
+---null
|
\---2: 514
+---null
|
+---null
`
+---1: 114
`
\---3: 19
====================
split 2 ~ 5 success
==== LCT forest ====
\---5: 233333
+---null
|
+---2: 514
` +---null
` |
` +---null
` `
` +---1: 114
` `
` \---3: 19
`
\---4: 19
====================
一种调试 线段树 / Treap / Splay / 左偏树 / LCT 等树形结构的技巧的更多相关文章
- 【BZOJ 1367】 1367: [Baltic2004]sequence (可并堆-左偏树)
1367: [Baltic2004]sequence Description Input Output 一个整数R Sample Input 7 9 4 8 20 14 15 18 Sample Ou ...
- 洛谷.3273.[SCOI2011]棘手的操作(左偏树)
题目链接 还是80分,不是很懂. /* 七个操作(用左偏树)(t2表示第二棵子树): 1.合并:直接合并(需要将一个t2中原有的根节点删掉) 2.单点加:把这个点从它的堆里删了,加了再插入回去(有负数 ...
- HDU 1512 Monkey King(左偏树+并查集)
[题目链接] http://acm.hdu.edu.cn/showproblem.php?pid=1512 [题目大意] 现在有 一群互不认识的猴子,每个猴子有一个能力值,每次选择两个猴子,挑出他们所 ...
- 左偏树 / 非旋转treap学习笔记
背景 非旋转treap真的好久没有用过了... 左偏树由于之前学的时候没有写学习笔记, 学得也并不牢固. 所以打算写这么一篇学习笔记, 讲讲左偏树和非旋转treap. 左偏树 定义 左偏树(Lefti ...
- 【BZOJ 2333 】[SCOI2011]棘手的操作(离线+线段树|可并堆-左偏树)
2333: [SCOI2011]棘手的操作 Description 有N个节点,标号从1到N,这N个节点一开始相互不连通.第i个节点的初始权值为a[i],接下来有如下一些操作: U x y: 加一条边 ...
- 洛谷 P3377 【模板】左偏树(可并堆)
洛谷 P3377 [模板]左偏树(可并堆) 题目描述 如题,一开始有N个小根堆,每个堆包含且仅包含一个数.接下来需要支持两种操作: 操作1: 1 x y 将第x个数和第y个数所在的小根堆合并(若第x或 ...
- 『左偏树 Leftist Tree』
新增一道例题 左偏树 Leftist Tree 这是一个由堆(优先队列)推广而来的神奇数据结构,我们先来了解一下它. 简单的来说,左偏树可以实现一般堆的所有功能,如查询最值,删除堆顶元素,加入新元素等 ...
- 洛谷P4331 [BOI2004] Sequence 数字序列 [左偏树]
题目传送门 数字序列 题目描述 给定一个整数序列 a1,a2,⋅⋅⋅,an ,求出一个递增序列 b1<b2<⋅⋅⋅<bn ,使得序列 ai 和 bi 的各项之差的绝对 ...
- BZOJ 1455 罗马游戏 ——左偏树
[题目分析] 左偏树的模板题目,大概就是尽量维护树的深度保持平衡,以及尽可能的快速合并的一种堆. 感觉和启发式合并基本相同. 其实并没有快很多. 本人的左偏树代码自带大常数,借鉴请慎重 [代码] #i ...
- 【BZOJ-1455】罗马游戏 可并堆 (左偏树)
1455: 罗马游戏 Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 1355 Solved: 561[Submit][Status][Discuss] ...
随机推荐
- AI Undetect是什么?
标题:AI UNDETECT:超越AI检测的反检测神器 在数字时代,人工智能的飞速发展已经渗透到我们生活的各个领域,包括教育.科技.网络内容制作等.越来越多的人依赖AI来生成各种内容,从学术作业.论文 ...
- 使用switch语句的注意事项
目录 case后需要手动break switch内的变量定义 变量没有定义在语句块内 变量定义在语句块内 表述多情况时不能用逗号 case后需要手动break switch(i){ case 1: 语 ...
- MySQL无开通SQL全审计下的故障分析方法
几年前MySQL数据库出现突然的从库延迟故障和CPU爆高时,如何排查具体原因,可能说已在腾讯云的MySQL库里开启了SQL全审计,记录了全部执行的SQL,再通过下面的方法就可以很容易找到原因: 1,实 ...
- 29、undo_2_1(事务槽、延迟块清除、构造CR块、ora-01555)
事务槽(不同于事务表里面的槽位(这个事务槽在数据块的头部)) 图解: 一个事务开始,要做的事情: 第一,事务表里面找槽位(undo段的段头块里有事务表,事务表有槽位,每一个槽位记录一个事务): 事务表 ...
- VScode 扩展推荐和配置
VScode 扩展推荐和配置 VSCode Extensions 推荐 Themes Dracula Official 拥有明亮的颜色和舒适的对比度,非常适合长时间编程. Nord 基于北极地区自然色 ...
- C#-公众号H5页面授权获取用户code、openid、unionid
一:配置信息 公众号设置: 1:设置 IP白名单(所在的服务器ip).记录公众号APPID和APPsecret; 2:设置 网页授权域名; 二:页面授权----[html中获取code] 1:页面引入 ...
- Collections Framework中的算法(之三)--不可变装饰器相关
本篇主要讲述Collections类中的unmodifiable相关的方法!这些方法都有一个共同含义就是使用此方法创建的对象都是不可变的!典型的装饰器模式的应用!下面的几篇都是装饰器模式在Java C ...
- Elasticsearch之基本使用
这里大概解答下各个目录.配置文件的作用: 目录 配置文件 描述 bin 放置脚本文件,如启动脚本 elasticsearch, 插件安装脚本等. config elasticserch.yml e ...
- Jetson Orin NX烧录+设备树更改?看这一篇就够了!
Jetson Orin NX烧录+设备树更改?看这一篇就够了! 笔者的设备为Jetson Orin NX 16GB + 达妙科技的Orin NX载板 本博客同步发表在CSDN:https://blog ...
- arthas 通过stack 命令查看堆栈调用
https://arthas.gitee.io/stack.html 很多时候我们都知道一个方法被执行,但这个方法被执行的路径非常多,或者你根本就不知道这个方法是从那里被执行了,此时你需要的是 sta ...