前言

如果我们需要观察程序运行过程中,某一个变量、某一个序列的变化情况,你可以在修改的地方打断点 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 等树形结构的技巧的更多相关文章

  1. 【BZOJ 1367】 1367: [Baltic2004]sequence (可并堆-左偏树)

    1367: [Baltic2004]sequence Description Input Output 一个整数R Sample Input 7 9 4 8 20 14 15 18 Sample Ou ...

  2. 洛谷.3273.[SCOI2011]棘手的操作(左偏树)

    题目链接 还是80分,不是很懂. /* 七个操作(用左偏树)(t2表示第二棵子树): 1.合并:直接合并(需要将一个t2中原有的根节点删掉) 2.单点加:把这个点从它的堆里删了,加了再插入回去(有负数 ...

  3. HDU 1512 Monkey King(左偏树+并查集)

    [题目链接] http://acm.hdu.edu.cn/showproblem.php?pid=1512 [题目大意] 现在有 一群互不认识的猴子,每个猴子有一个能力值,每次选择两个猴子,挑出他们所 ...

  4. 左偏树 / 非旋转treap学习笔记

    背景 非旋转treap真的好久没有用过了... 左偏树由于之前学的时候没有写学习笔记, 学得也并不牢固. 所以打算写这么一篇学习笔记, 讲讲左偏树和非旋转treap. 左偏树 定义 左偏树(Lefti ...

  5. 【BZOJ 2333 】[SCOI2011]棘手的操作(离线+线段树|可并堆-左偏树)

    2333: [SCOI2011]棘手的操作 Description 有N个节点,标号从1到N,这N个节点一开始相互不连通.第i个节点的初始权值为a[i],接下来有如下一些操作: U x y: 加一条边 ...

  6. 洛谷 P3377 【模板】左偏树(可并堆)

    洛谷 P3377 [模板]左偏树(可并堆) 题目描述 如题,一开始有N个小根堆,每个堆包含且仅包含一个数.接下来需要支持两种操作: 操作1: 1 x y 将第x个数和第y个数所在的小根堆合并(若第x或 ...

  7. 『左偏树 Leftist Tree』

    新增一道例题 左偏树 Leftist Tree 这是一个由堆(优先队列)推广而来的神奇数据结构,我们先来了解一下它. 简单的来说,左偏树可以实现一般堆的所有功能,如查询最值,删除堆顶元素,加入新元素等 ...

  8. 洛谷P4331 [BOI2004] Sequence 数字序列 [左偏树]

    题目传送门 数字序列 题目描述 给定一个整数序列 a1​,a2​,⋅⋅⋅,an​ ,求出一个递增序列 b1​<b2​<⋅⋅⋅<bn​ ,使得序列 ai​ 和 bi​ 的各项之差的绝对 ...

  9. BZOJ 1455 罗马游戏 ——左偏树

    [题目分析] 左偏树的模板题目,大概就是尽量维护树的深度保持平衡,以及尽可能的快速合并的一种堆. 感觉和启发式合并基本相同. 其实并没有快很多. 本人的左偏树代码自带大常数,借鉴请慎重 [代码] #i ...

  10. 【BZOJ-1455】罗马游戏 可并堆 (左偏树)

    1455: 罗马游戏 Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 1355  Solved: 561[Submit][Status][Discuss] ...

随机推荐

  1. 什么是SCI, SCIE, JCR和影响因子(IF)?

    SCI(Scientific Citation Index):是美国科学信息研究所(ISI)编辑出版的引文索引类刊物,创刊于1964年.分印刷版.光盘版和联机板等载体.印刷版.光盘版从全球数万种期刊中 ...

  2. DashText-快速开始

    快速开始 DashText,是向量检索服务DashVector推荐使用的稀疏向量编码器(Sparse Vector Encoder),DashText可通过BM25算法将原始文本转换为稀疏向量(Spa ...

  3. 解决Python的pip问题:WARNING: Retrying (Retry(total=1, connect=None, read=None, redirect=None, status=None))

    相关: pip安装第三方库报错Retrying (Retry(total=1, connect=None, read=None, redirect=None, status=None)) 国内镜像源下 ...

  4. 大便系统怎样安装RPM包

    alien包转换工具 如果我们有很喜欢的RPM包,而又没有deb版本. 怎么办~? 可以同过alien来转换或者直接安装,这个小家伙可是个很方便的东西! 基本命令如下: 首先通过apt-get ins ...

  5. (系列十一)Vue3框架中路由守卫及请求拦截(实现前后端交互)

    说明 该文章是属于OverallAuth2.0系列文章,每周更新一篇该系列文章(从0到1完成系统开发). 该系统文章,我会尽量说的非常详细,做到不管新手.老手都能看懂. 说明:OverallAuth2 ...

  6. win10子系统docker搭建gitlab Server

    心血来潮想搞一套cicd玩玩,结果开始就掉坑里了. 遇到问题 不会写文,所以语言组织比较差,将就看着吧!就当记录一下这个坑以后没准还能用的上. 参照https://blog.csdn.net/Mono ...

  7. 【THUPC 2024 初赛】 E 转化

    [THUPC 2024 初赛] 转化 我都能做出来,那就是大水题了. 思路 首先我们要确定最大可以变色的球的数量 \(tot\). 有如下两个贪心步骤: 所有颜色使用分裂操作,并更新 \(a_i\). ...

  8. Java Study For Five Day( 面向对象一)

    面向对象 1.面向对象的概念 2.理解面向对象 *面向对象其实是相对面向过程而言的,面向对象和面向过程都是一种思想,它们所强调的内容不一样. *面向对象:强调的是功能的行为,将功能进行了封装成了对象, ...

  9. Linux之远程挂载SSHFS

    SSHFS(Secure SHell FileSystem)是一个客户端,可以让我们通过 SSH 文件传输协议(SFTP)挂载远程的文件系统并且在本地机器上和远程的目录和文件进行交互. SFTP 是一 ...

  10. 设计模式:可复用面向对象软件的基础 pdf电子书分享

    <设计模式:可复用面向对象软件的基础>是引导读者走出软件设计迷宫的指路明灯,凝聚了软件开发界几十年设计经验的结晶.四位顶尖的面向对象领域专家精心选取了最具价值的设计实践,加以分类整理和命名 ...