【问题标题】:Pretty-printing a binary tree in C (and other imperative languages)用 C(和其他命令式语言)漂亮地打印二叉树
【发布时间】:2011-05-30 04:00:41
【问题描述】:

第一次发帖,编程比较新,请耐心等待!

我对打印格式化二叉树的高效通用算法(在 CLI 环境中)和 C 实现都感兴趣。这是我自己编写的一些代码(这是一个非常简化的原始版本,也是支持许多 BST 操作的更大程序的一部分,但它应该可以编译):

#include <stdbool.h>    // C99, boolean type support
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#define DATATYPE_IS_DOUBLE
#define NDEBUG                      // disable assertions
#include <assert.h>

#define WCHARBUF_LINES          20  // def: 20
#define WCHARBUF_COLMS          800 // def: 80 (using a huge number, like 500, is a good idea,
                                    //          in order to prevent a buffer overflow :)
#define RECOMMENDED_CONS_WIDTH  150
#define RECOMMENDED_CONS_WIDTHQ "150"   // use the same value, quoted

/* Preprocessor directives depending on DATATYPE_IS_* : */
#if defined DATATYPE_IS_INT || defined DATATYPE_IS_LONG
    #define DTYPE           long int
    #define DTYPE_STRING    "INTEGER"
    #define DTYPE_PRINTF    "%*.*ld"
    #undef DATATYPE_IS_CHAR

#elif defined DATATYPE_IS_FLOAT
    #define DTYPE           float
    #define DTYPE_STRING    "FLOAT"
    #define DTYPE_PRINTF    "%*.*f"
    #undef DATATYPE_IS_CHAR

#elif defined DATATYPE_IS_DOUBLE
    #define DTYPE           double
    #define DTYPE_STRING    "DOUBLE"
    #define DTYPE_PRINTF    "%*.*lf"
    #undef DATATYPE_IS_CHAR

#elif defined DATATYPE_IS_CHAR
    #define DTYPE           char
    #define DTYPE_STRING    "CHARACTER"
    #define DTYPE_PRINTF    "%*.*c" /* using the "precision" sub-specifier ( .* ) with a  */
                                    /* character will produce a harmless compiler warning */
#else
    #error "DATATYPE_IS_* preprocessor directive undefined!"

#endif


typedef struct node_struct {
    DTYPE data;
    struct node_struct *left;
    struct node_struct *right;
    /* int height;  // useful for AVL trees */
} node;

typedef struct {
    node *root;
    bool IsAVL;     // useful for AVL trees
    long size;
} tree;


static inline
DTYPE get_largest(node *n){
    if (n == NULL)
        return (DTYPE)0;

    for(; n->right != NULL; n=n->right);

    return n->data;
}

static
int subtreeheight(node *ST){
    if (ST == NULL)
        return -1;

    int height_left  = subtreeheight(ST->left);
    int height_right = subtreeheight(ST->right);

    return (height_left > height_right) ? (height_left + 1) : (height_right + 1);
}


void prettyprint_tree(tree *T){
    if (T == NULL)  // if T empty, abort
        return;

#ifndef DATATYPE_IS_CHAR /* then DTYPE is a numeric type */
    /* compute spaces, find width: */
    int width, i, j;
    DTYPE max = get_largest(T->root);

    width = (max < 10) ? 1 :
            (max < 100) ? 2 :
            (max < 1000) ? 3 :
            (max < 10000) ? 4 :
            (max < 100000) ? 5 :
            (max < 1000000) ? 6 :
            (max < 10000000) ? 7 :
            (max < 100000000) ? 8 :
            (max < 1000000000) ? 9 : 10;
    assert  (max < 10000000000);

    width += 2; // needed for prettier results

#if defined DATATYPE_IS_FLOAT || defined DATATYPE_IS_DOUBLE
    width += 2; // because of the decimals! (1 decimal is printed by default...)
#endif // float or double

    int spacesafter = width / 2;
    int spacesbefore = spacesafter + 1;
    //int spacesbefore = ceil(width / 2.0);

#else /* character input */
    int i, j, width = 3, spacesbefore = 2, spacesafter = 1;

#endif // #ifndef DATATYPE_IS_CHAR

    /* start wchar_t printing, using a 2D character array with swprintf() : */

    struct columninfo{  // auxiliary structure
        bool visited;
        int  col;
    };

    wchar_t wcharbuf[WCHARBUF_LINES][WCHARBUF_COLMS];
    int line=0;
    struct columninfo eachline[WCHARBUF_LINES];

    for (i=0; i<WCHARBUF_LINES; ++i){       // initialization
        for (j=0; j<WCHARBUF_COLMS; ++j)
            wcharbuf[i][j] = (wchar_t)' ';
        eachline[i].visited = false;
        eachline[i].col = 0;
    }

    int height = subtreeheight(T->root);

    void recur_swprintf(node *ST, int cur_line, const wchar_t *nullstr){ // nested function,
                                                                            // GCC extension!
        float offset = width * pow(2, height - cur_line);

        ++cur_line;

        if (eachline[cur_line].visited == false) {
            eachline[cur_line].col = (int) (offset / 2);
            eachline[cur_line].visited = true;
        }
        else{
            eachline[cur_line].col += (int) offset;
            if (eachline[cur_line].col + width > WCHARBUF_COLMS)
                swprintf(wcharbuf[cur_line], L"  BUFFER OVERFLOW DETECTED! ");
        }

        if (ST == NULL){
            swprintf(wcharbuf[cur_line] + eachline[cur_line].col, L"%*.*s", 0, width, nullstr);
            if (cur_line <= height){
                /* use spaces instead of the nullstr for all the "children" of a NULL node */
                recur_swprintf(NULL, cur_line, L"          ");
                recur_swprintf(NULL, cur_line, L"          ");
            }
            else
                return;
        }
        else{
            recur_swprintf(ST->left,  cur_line, nullstr);
            recur_swprintf(ST->right, cur_line, nullstr);

            swprintf(wcharbuf[cur_line] + eachline[cur_line].col - 1, L"("DTYPE_PRINTF"",
                     spacesbefore, 1, ST->data);

          //swprintf(wcharbuf[cur_line] + eachline[cur_line].col + spacesafter + 1, L")");
            swprintf(wcharbuf[cur_line] + eachline[cur_line].col + spacesafter + 2, L")");
        }
    }

    void call_recur(tree *tr){  // nested function, GCC extension! (wraps recur_swprintf())
        recur_swprintf(tr->root, -1, L"NULL");
    }

    call_recur(T);

    /* Omit empty columns: */
    int omit_cols(void){        // nested function, GCC extension!
        int col;

        for (col=0; col<RECOMMENDED_CONS_WIDTH; ++col)
            for (line=0; line <= height+1; ++line)
                if (wcharbuf[line][col] != ' ' && wcharbuf[line][col] != '\0')
                    return col;

        return 0;
    }

    /* Use fputwc to transfer the character array to the screen: */
    j = omit_cols() - 2;
    j = (j < 0) ? 0 : j;

    for (line=0; line <= height+1; ++line){     // assumes RECOMMENDED_CONS_WIDTH console window!
        fputwc('\n', stdout);                   // optional blanc line
        for (i=j; i<j+RECOMMENDED_CONS_WIDTH && i<WCHARBUF_COLMS; ++i)
            fputwc(wcharbuf[line][i], stdout);
        fputwc('\n', stdout);
    }
}

也上传到a pastebin service,为了保留语法高亮

它工作得很好,虽然自动宽度设置可能会更好。预处理器的魔法有点傻(甚至丑陋)并且与算法没有真正的关系,但它允许在树节点中使用各种数据类型(我认为这是一个使用预处理器进行一些实验的机会 - 记住,我是新手!)。

主程序应该调用

system("mode con:cols="RECOMMENDED_CONS_WIDTHQ"lines=2000");

在调用 prettyprint_tree() 之前,在 cmd.exe 中运行时。

样本输出:

(106.0) (102.0) (109.0) (101.5) 空 (107.0) (115.0) NULL NULL (106.1) NULL (113.0) NULL 空 空 空 空

理想情况下,输出应该是这样的(我使用 wprintf() 系列函数的原因是无论如何都能打印 Unicode 字符):

(107.0) ┌──────┴──────┐ (106.1) 空 ┌────┴────┐ 空空

所以,我的问题:

  • 您如何看待这段代码? (也非常欢迎编码风格的建议!)
  • 能否以优雅的方式扩展它以包含画线字符? (很遗憾,我不这么认为。)
  • C 或其他命令式语言(或命令式伪代码)中的任何其他算法?
  • 有点不相关:您对嵌套函数(非便携式 GNU 扩展)有何看法?我认为这是编写函数的递归部分的一种优雅方式,而不必提供所有局部变量作为参数(也可用作实现隐藏技术),但这可能是我的 Pascal 过去 :-) 我感兴趣更有经验的程序员的意见。

提前感谢您的回复!

PS。问题不是this one 重复。


编辑: Jonathan Leffler 写了一个 excellent answer 几天后很可能会成为“公认的答案”(除非有人发布同样棒的东西!)。由于篇幅限制,我决定在这里回复而不是评论。

  • 上面的代码实际上是一个更大的“家庭作业”项目的一部分(在共享库中实现 BST 操作 + 使用该库的 CLI 应用程序)。然而,“prettyprint”功能不是要求的一部分;只是我决定自己添加的东西。
  • 我还添加了一个“转换为不带旋转的 AVL”函数,它使用“arraystr”作为中间表示;-) 我忘记了这里没有使用它。我已经编辑了代码以将其删除。此外,bool IsAVL 结构成员还没有被使用。只是没有在这个特定的功能中使用。为了呈现上面引用的代码,我不得不从各种文件中复制/粘贴代码并进行大量更改。这是一个我不知道如何解决的问题。我很乐意发布整个程序,但它太大了,而且用我的母语(不是英语!)进行了评论。
  • 整个项目大约有 1600 LOC(包括 cmets),具有多个构建目标(调试/发布/静态链接),并且它在启用 -Wall-Wextra 的情况下干净编译。根据构建目标自动启用/禁用断言和调试消息。我还认为嵌套函数不需要函数原型,毕竟嵌套函数不实现任何外部接口根据定义 - GCC 在这里当然没有抱怨。我不知道为什么 OSX 上有这么多警告 :(
  • 我在 Windows 7 上使用 GCC 4.4.1。
  • 尽管在 Windows 上编写和测试了这个程序,但我实际上是一个 Linux 用户......不过,我无法忍受 vim 并且我使用 nano(在 GNU 屏幕内)或 gedit 代替(射击我)!无论如何,我更喜欢 K&R 大括号样式:)
  • 可移植性并不重要,对于 Linux 用户来说,GCC 几乎是事实上的...事实上它在 Windows 下也能很好地工作,这是一个很好的奖励。
  • 我没有使用 VCS,也许我应该使用。我想试一试,但对于我的需求来说,它们似乎都太复杂了,我不知道如何选择一个:-)
  • 您对深度溢出的检查绝对正确,幸好它很容易添加。
  • 感谢L' ' 的建议!
  • 我觉得您的建议(封装“整个绘图代码,以便屏幕图像和相关信息在一个结构中”非常有趣...但我真的不明白你所说的“封装”是什么意思。您能否提供 3 或 4 行(伪)代码来显示可能的函数声明和/或可能的函数调用?

这是我的第一个“大型”(非平凡)程序,非常感谢您的建议。


编辑#2: 这是here提到的“快速而肮脏”方法的实现。
编辑#3:我决定将其拆分为separate answer,因为它是对 OP 的有效答案。)

许多回复提到Graphviz。我已经知道它(许多 Linux 应用程序都与它相关联),但我认为对于 10KB 的 CLI 可执行文件来说这太过分了。不过,我会牢记在心,以备不时之需。看起来很棒。

【问题讨论】:

  • 算法和/或例程的奖励积分 (1) 更短、更优雅且更易于理解,并且 (2) 不需要中间字符缓冲区(与我的不同)。
  • 如果您不想要中间缓冲区,您必须知道如何扫描树并生成每一行输出:您需要处理哪些节点,或者您需要处理哪些行画。可以做到,但不容易。一个问题是树越深越宽。如果你的树是合理平衡的,它会更容易处理;不平衡的树木使生活更加艰难。您可能需要在树上进行两次遍历,一次是为了确定必要的大小,因为第一行上第一个节点的位置受树底部节点的影响,一次是为了绘制。
  • @Jonathan:是的,我认为使用缓冲区毕竟要好得多(也更灵活)。我只是希望有一个我不知道的更好/更简单的知名算法。
  • 仔细看K&R;它们的功能以第 1 列中的大括号开头。但是,这是一个风格问题,而不是更严重的问题。
  • 可惜这个不得不是CLI。否则,graphviz 只是 乞求 用于这种事情。

标签: c language-agnostic binary-tree pretty-print


【解决方案1】:

您需要决定您的代码是否需要可移植。如果您可能需要使用 GCC 以外的编译器,那么嵌套函数对您的可移植性目标是致命的。我不会使用它们 - 但我的可移植性目标可能与您的不同。

您的代码丢失&lt;wchar.h&gt;;没有它,它编译得相当干净——GCC 抱怨你的非静态函数和swprintf()fputwc() 缺少原型,但是添加&lt;wchar.h&gt; 会产生很多与swprintf() 相关的严重警告;他们实际上是在诊断一个错误。

gcc -O -I/Users/jleffler/inc -std=c99 -Wall -Wextra -Wmissing-prototypes \
    -Wstrict-prototypes -Wold-style-definition -c tree.c
tree.c:88:6: warning: no previous prototype for ‘prettyprint_tree’
tree.c: In function ‘prettyprint_tree’:
tree.c:143:10: warning: no previous prototype for ‘recur_swprintf’
tree.c: In function ‘recur_swprintf’:
tree.c:156:17: warning: passing argument 2 of ‘swprintf’ makes integer from pointer without a cast
/usr/include/wchar.h:135:5: note: expected ‘size_t’ but argument is of type ‘int *’
tree.c:156:17: error: too few arguments to function ‘swprintf’
/usr/include/wchar.h:135:5: note: declared here
tree.c:160:13: warning: passing argument 2 of ‘swprintf’ makes integer from pointer without a cast
/usr/include/wchar.h:135:5: note: expected ‘size_t’ but argument is of type ‘int *’
tree.c:174:22: warning: passing argument 2 of ‘swprintf’ makes integer from pointer without a cast
/usr/include/wchar.h:135:5: note: expected ‘size_t’ but argument is of type ‘int *’
tree.c:174:22: warning: passing argument 3 of ‘swprintf’ makes pointer from integer without a cast
/usr/include/wchar.h:135:5: note: expected ‘const wchar_t * restrict’ but argument is of type ‘int’
tree.c:177:13: warning: passing argument 2 of ‘swprintf’ makes integer from pointer without a cast
/usr/include/wchar.h:135:5: note: expected ‘size_t’ but argument is of type ‘int *’
tree.c:177:13: error: too few arguments to function ‘swprintf’
/usr/include/wchar.h:135:5: note: declared here
tree.c: In function ‘prettyprint_tree’:
tree.c:181:10: warning: no previous prototype for ‘call_recur’
tree.c:188:9: warning: no previous prototype for ‘omit_cols’

(这是 MacOS X 10.6.5 上的 GCC 4.5.2。)

  • 请查看swprintf()的界面;它更像snprintf() 而不是sprintf()(这是一件好事™!)。

总体思路很有趣。我建议在提交代码进行分析时选择一种表示,并清理与代码分析无关的任何内容。例如,arraystr 类型已定义但未使用 - 您不想让像我这样的人在您的代码中获得便宜的机会。与未使用的结构成员类似;甚至不要将它们保留为 cmets,即使您可能希望将它们保留在 VCS 的代码中(尽管为什么?)。您正在使用版本控制系统 (VCS),不是吗?这是一个反问——如果您不使用 VCS,请立即开始使用,以免失去您重视的东西。

在设计方面,您希望避免做一些事情,例如要求主程序运行一个晦涩的 system() 命令 - 您的代码应该处理这些问题(可能使用初始化函数,也可能使用终结函数来撤消对终端设置进行了更改)。

不喜欢嵌套函数的另一个原因是:我不知道如何正确地声明函数。看似合理的替代方案不起作用 - 但我没有去阅读关于它们的 GCC 手册。

  • 您检查列宽溢出;你不检查深度溢出。如果您创建的树太深,您的代码将会崩溃并烧毁。

次要问题:您可以告诉不使用 'vi' 或 'vim' 的人进行编辑 - 他们不会将函数的左大括号放在第 1 列中。在 'vi' 中,第 1 列中的左大括号为您提供从函数内部任何位置开始函数的简单方法('[[' 向后跳转;']]' 跳转到下一个函数的开头)。

不要禁用断言。

一定要包含一个主程序和相关的测试数据——这意味着人们可以测试你的代码,而不仅仅是编译它。

使用宽字符常量而不是强制类型转换:

wcharbuf[i][j] = (wchar_t)' ';

wcharbuf[i][j] = L' ';

您的代码创建一个大屏幕图像(代码中的 20 行 x 800 列)并填写要打印的数据。这是一种合理的做法。小心,您可以安排处理画线字符。但是,我认为您需要重新考虑核心绘图算法。您可能希望封装整个绘图代码,以便屏幕图像和相关信息位于单个结构中,可以通过引用(指针)传递给函数。您将拥有一组函数来在您的树搜索代码指定的位置绘制各种位。您将具有在适当位置绘制数据值的功能;您将具有在适当位置绘制线条的功能。你可能没有嵌套函数——在我看来,当一个函数嵌套在另一个函数中时,阅读代码要困难得多。将函数设为静态是好的;将嵌套函数变成静态(非嵌套)函数。为他们提供所需的上下文 - 因此封装了屏幕图像。

  • 总体是一个好的开始;很多好主意。还有很多事情要做。

请求有关封装的信息...

您可以使用如下结构:

typedef struct columninfo Colinfo;

typedef struct Image
{
    wchar_t    image[WCHARBUF_LINES][WCHARBUF_COLUMNS];
    Colinfo    eachline[WCHARBUF_LINES];
} Image;

Image image;

您可能会发现添加一些额外成员很方便和/或明智;这将在实施过程中出现。然后你可以创建一个函数:

void format_node(Image *image, int line, int column, DTYPE value)
{
    ...
}

你也可以把一些常量,比如空格后变成枚举值:

enum { spacesafter = 2 };

这些可以被任何函数使用。

【讨论】:

  • 非常感谢您在回答中付出的时间和精力。请查看我的编辑以获取回复。
  • 感谢您提供更多示例。
【解决方案2】:

编码风格prettyprint_tree() 函数处理过多的计算和数据,难以阅读。例如,图像缓冲区的初始化和打印可以放在单独的函数中,width 计算也可以。我相信你可以用log 写一个公式来替换

width = (max < 10) ? 1 :
        (max < 100) ? 2 :
        (max < 1000) ? 3 :
        ...

计算。

我不习惯阅读嵌套函数和 C,这让我更难扫描您的代码。除非您不与他人共享您的代码或出于意识形态原因将代码绑定到 GCC,否则我不会使用这些扩展。

算法:对于一个用 C 编写的又快又脏的漂亮打印机,我永远不会使用你的布局风格。与您的算法相比,编写一个有序遍历来打印是不费吹灰之力的

   a
  / \
 b   c

作为

     c
 a
     b

而且我不介意歪着头。对于比这更漂亮的东西,我宁愿发射

digraph g { a -> b; a -> c; }

并将其留给dot 进行格式化。

【讨论】:

  • +1 用于提及 dot 和 URL。我知道该工具存在,但不记得它的名称。
  • 我怀疑涉及log()ceil() 的东西会更有效,但你说得对,当前基于比较的方法看起来很难看。其实prettyprint_tree()函数是迄今为止程序中最丑的(一共32个,大部分是get_largest()subtreeheight()的大小)。
  • 关于您的建议只是按顺序打印树并倾斜我的头(!)我会说这是一个聪明的想法,但没有抓住重点:这还不够漂亮! :P 尽管如此,我还是试了一下(见新答案)。
【解决方案3】:

此代码应来自:http://www.ihas1337code.com/2010/09/how-to-pretty-print-binary-tree.html

    #include <fstream>
#include <iostream>
#include <deque>
#include <iomanip>
#include <sstream>
#include <string>
#include <cmath>
using namespace std;

struct BinaryTree {
  BinaryTree *left, *right;
  int data;
  BinaryTree(int val) : left(NULL), right(NULL), data(val) { }
};

// Find the maximum height of the binary tree
int maxHeight(BinaryTree *p) {
  if (!p) return 0;
  int leftHeight = maxHeight(p->left);
  int rightHeight = maxHeight(p->right);
  return (leftHeight > rightHeight) ? leftHeight + 1: rightHeight + 1;
}

// Convert an integer value to string
string intToString(int val) {
  ostringstream ss;
  ss << val;
  return ss.str();
}

// Print the arm branches (eg, /    \ ) on a line
void printBranches(int branchLen, int nodeSpaceLen, int startLen, int nodesInThisLevel, const deque<BinaryTree*>& nodesQueue, ostream& out) {
  deque<BinaryTree*>::const_iterator iter = nodesQueue.begin();
  for (int i = 0; i < nodesInThisLevel / 2; i++) {
    out << ((i == 0) ? setw(startLen-1) : setw(nodeSpaceLen-2)) << "" << ((*iter++) ? "/" : " ");
    out << setw(2*branchLen+2) << "" << ((*iter++) ? "\\" : " ");
  }
  out << endl;
}

// Print the branches and node (eg, ___10___ )
void printNodes(int branchLen, int nodeSpaceLen, int startLen, int nodesInThisLevel, const deque<BinaryTree*>& nodesQueue, ostream& out) {
  deque<BinaryTree*>::const_iterator iter = nodesQueue.begin();
  for (int i = 0; i < nodesInThisLevel; i++, iter++) {
    out << ((i == 0) ? setw(startLen) : setw(nodeSpaceLen)) << "" << ((*iter && (*iter)->left) ? setfill('_') : setfill(' '));
    out << setw(branchLen+2) << ((*iter) ? intToString((*iter)->data) : "");
    out << ((*iter && (*iter)->right) ? setfill('_') : setfill(' ')) << setw(branchLen) << "" << setfill(' ');
  }
  out << endl;
}

// Print the leaves only (just for the bottom row)
void printLeaves(int indentSpace, int level, int nodesInThisLevel, const deque<BinaryTree*>& nodesQueue, ostream& out) {
  deque<BinaryTree*>::const_iterator iter = nodesQueue.begin();
  for (int i = 0; i < nodesInThisLevel; i++, iter++) {
    out << ((i == 0) ? setw(indentSpace+2) : setw(2*level+2)) << ((*iter) ? intToString((*iter)->data) : "");
  }
  out << endl;
}

// Pretty formatting of a binary tree to the output stream
// @ param
// level  Control how wide you want the tree to sparse (eg, level 1 has the minimum space between nodes, while level 2 has a larger space between nodes)
// indentSpace  Change this to add some indent space to the left (eg, indentSpace of 0 means the lowest level of the left node will stick to the left margin)
void printPretty(BinaryTree *root, int level, int indentSpace, ostream& out) {
  int h = maxHeight(root);
  int nodesInThisLevel = 1;

  int branchLen = 2*((int)pow(2.0,h)-1) - (3-level)*(int)pow(2.0,h-1);  // eq of the length of branch for each node of each level
  int nodeSpaceLen = 2 + (level+1)*(int)pow(2.0,h);  // distance between left neighbor node's right arm and right neighbor node's left arm
  int startLen = branchLen + (3-level) + indentSpace;  // starting space to the first node to print of each level (for the left most node of each level only)

  deque<BinaryTree*> nodesQueue;
  nodesQueue.push_back(root);
  for (int r = 1; r < h; r++) {
    printBranches(branchLen, nodeSpaceLen, startLen, nodesInThisLevel, nodesQueue, out);
    branchLen = branchLen/2 - 1;
    nodeSpaceLen = nodeSpaceLen/2 + 1;
    startLen = branchLen + (3-level) + indentSpace;
    printNodes(branchLen, nodeSpaceLen, startLen, nodesInThisLevel, nodesQueue, out);

    for (int i = 0; i < nodesInThisLevel; i++) {
      BinaryTree *currNode = nodesQueue.front();
      nodesQueue.pop_front();
      if (currNode) {
          nodesQueue.push_back(currNode->left);
          nodesQueue.push_back(currNode->right);
      } else {
        nodesQueue.push_back(NULL);
        nodesQueue.push_back(NULL);
      }
    }
    nodesInThisLevel *= 2;
  }
  printBranches(branchLen, nodeSpaceLen, startLen, nodesInThisLevel, nodesQueue, out);
  printLeaves(indentSpace, level, nodesInThisLevel, nodesQueue, out);
}

int main() {
  BinaryTree *root = new BinaryTree(30);
  root->left = new BinaryTree(20);
  root->right = new BinaryTree(40);
  root->left->left = new BinaryTree(10);
  root->left->right = new BinaryTree(25);
  root->right->left = new BinaryTree(35);
  root->right->right = new BinaryTree(50);
  root->left->left->left = new BinaryTree(5);
  root->left->left->right = new BinaryTree(15);
  root->left->right->right = new BinaryTree(28);
  root->right->right->left = new BinaryTree(41);

  cout << "Tree pretty print with level=1 and indentSpace=0\n\n";
  // Output to console
  printPretty(root, 1, 0, cout);

  cout << "\n\nTree pretty print with level=5 and indentSpace=3,\noutput to file \"tree_pretty.txt\".\n\n";
  // Create a file and output to that file
  ofstream fout("tree_pretty.txt");
  // Now print a tree that's more spread out to the file
  printPretty(root, 5, 0, fout);

  return 0;
}

【讨论】:

    【解决方案4】:

    也许你可以看看Bresenham's line algorithm,它可能适合你

    【讨论】:

    • 您能详细说明一下吗?乍一看,我看不出“布雷森汉姆算法”如何与我的问题相关,因为问题是关于在 CLI 中打印树,而不是关于使用(或创建自己)一些原始图形工具包。不过,感谢您的回答!
    • @FDFlock:Bresenham 的算法用于以任意角度绘制一条线,设置像素。直线绘图实际上不需要像您建议的图表中那样(尽管它可以将水平和垂直线作为退化情况处理),即使是像素化图像也是如此。此外,您不是用像素绘制的。
    • @Jonathan:我知道,因为我阅读了相关的维基百科文章。这就是为什么弗朗西斯科的回答在我看来真的很奇怪;-)
    【解决方案5】:

    这是here 中提到的“快速而肮脏”方法的 C 实现。它并没有变得更快和/或更脏:

    void shittyprint_tree(tree *T){ // Supposed to be quick'n'dirty!
                                    // When DTYPE is "char", width is a bit larger than needed.
        if (T == NULL)
            return;
    
        const int width = ceil(log10(get_largest(T->root)+0.01)) + 2;
        const wchar_t* sp64 = L"                                                                ";
    
        void nested(node *ST, int spaces){  // GCC extension
            if (ST == NULL){
                wprintf(L"\n");             // Can be commented to disable the extra blanc line.
                return;
            }
            nested(ST->right, spaces + width);
            wprintf(L"%*.*s("DTYPE_PRINTF")\n", 0, spaces, sp64, 1, 1, ST->data);
            nested(ST->left, spaces + width);
        }
    
        nested(T->root, 2);
    }
    

    示例输出(使用与之前相同的树):

    (115.0) (113.0) (109.0) (107.0) (106.1) (106.0) (102.0) (101.5)

    不过,我不能说它符合我最初的要求...

    【讨论】:

      猜你喜欢
      • 2016-08-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-04
      • 1970-01-01
      • 2015-01-12
      • 2013-04-12
      相关资源
      最近更新 更多