用C(以及其他命令式语言)打印二叉树

时间:2010-12-23 20:34:43

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

5 个答案:

答案 0 :(得分:4)

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

您的代码遗失<wchar.h>;如果没有它,它会相当干净地编译 - GCC抱怨缺少非静态函数的原型以及swprintf()fputwc()),但添加<wchar.h>会产生很多与{{swprintf()相关的严重警告。 1}};他们实际上是在诊断一个错误。

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类型已定义但未使用 - 您不希望让像我这样的人在您的代码中获得便宜的镜头。与未使用的结构成员类似;甚至不要将它们留作注释,即使您可能希望将它们保留在VCS的代码中(尽管为什么?)。您使用的是版本控制系统(VCS),不是吗?这是一个修辞问题 - 如果你不使用VCS,现在就开始使用VCS,然后再失去你重视的东西。

在设计方面,你想避免做一些事情,比如要求主程序运行一个模糊的system()命令 - 你的代码应该处理这些问题(可能有一个初始化函数,也许是一个终结函数撤消对终端设置所做的更改。)

不喜欢嵌套函数的另一个原因:我无法弄清楚如何获得函数的声明。似乎看似合理的替代方案不起作用 - 但我没有去阅读关于它们的GCC手册。

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

Minor nit:您可以告诉那些不使用'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 };

然后,任何功能都可以使用它们。

答案 1 :(得分: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进行格式化。

答案 2 :(得分:2)

此代码应该来自: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;
}

答案 3 :(得分:0)

也许你可以看一下它可能适合你的Bresenham's line algorithm

答案 4 :(得分:0)

这是[{3}}提到的“快速而肮脏”方法的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)

但我不能说它符合我原来的要求......