前言

如果我们需要观察程序运行过程中,某一个变量、某一个序列的变化情况,你可以在修改的地方打断点 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
====================

标签: none

添加新评论