一种调试 线段树 / 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] ...
随机推荐
- idea高效实用快捷键【待补充】
1.快捷键 ctrl+alt+L代码格式化 2.快捷键 ctrl+h查看hierarchy,只能查看向上向下继承关系,而不能看实现了哪些接口. 3,选中右键--Diagram可以查看实现了哪些接口 4 ...
- ES6语法特性
ES6语法特性 简介 ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了. 它的目标,是使得 JavaScript 语言 ...
- three.js+vue智慧社区web3d数字孪生三维地图
案例效果截图如下: 具体案例场景和功能,详见b站视频: https://www.bilibili.com/video/BV1Bb421E7WL/?vd_source=7d4ec9c9275b9c7d1 ...
- HAL+CubeIDE,STM32F407ZGT6正点原子探索者,舵机驱动,从零开始
CubeIDE_HAL库_从零开始玩舵机 1.材料准备 开发板:正点原子STM32F407ZGT6探索者 舵机:SG90 舵机线材分辨:褐色 / 红色 / 橘黄色 -- GND / VCC / PWM ...
- MQTT应用:Air780EP低功耗4G模组AT开发
终于要讲一讲MQTT应用! 本文应各位大佬邀请,详细讲解Air780EP模组MQTT应用的多个AT命令. Air780EP是低功耗4G模组之一,支持全系列的AT指令以及LuatOS脚本二次开发. ...
- 五、FreeRTOS学习笔记-任务创建和删除(动态方式)
1任务控制块:保存任务的一些信息 (STM32的栈是由告高地址向低地址延伸的,由上向下生长) (STM32的堆是由告低地址向高地址延伸的,由下向上生长) 第一步申请内存 如下如所示步骤找到xTaskC ...
- Chrome插件之油猴(详尽版本)
官方文档: https://www.tampermonkey.net/documentation.php#google_vignette 1.注释语法: // @match https://passp ...
- Dockerfile&Docker-Compose之基础
使用了很久的docker,之前却从来没有总结过, 于是开此篇来记录平常使用Dockerfile和docker-compose.yaml的点滴, 先从基础命令开始哦 [Dockerfile] Docke ...
- Zcmu-1178
思路: 分析题目要求的就是由2,3,5,7单独相乘或者组合相乘的数字. 所以将数字循环起来相乘,之后结果按从大到小地无重复放进数组当中. 学长 #include<set> #include ...
- 移动端NES网页模拟器(3)
前言 前面2个章节已经封装好了摇杆和NES虚拟按键,现在配合jsnes这个包来完成一个移动端版的NES模拟器. 这是插件的github地址:bfirsh/jsnes 这个包可以直接拿来用,但是没有适配 ...