输出到终端效果: 输出到文件效果:
好玩……是一方面,另一方面是:学写二叉树的时候需要检查自己建立的二叉树结构对不对,在C语言里面的做法会是把当前树节点及左右子树节点地址及对应数据打印出来,然后通过节点联系对应来检查,我暑假写的时候就是这么干的。 不过,我当时想到了Java,Java只有引用,那到时候要怎么处理会更好些? 于是,我就有了图形化打印的想法。
##### 思路 (以下方法能实现,但不是 队列 的正确使用姿势,不建议采用了) 另外,可以参考这里
这是我写的简单队列的接口,作为内队列。外队列与此类似 (C原生不支持泛型和面向对象,所以外队列要重新定义。)
#ifndef _QUEUE_H_ #define _QUEUE_H_ #include <stdbool.h> #include "binaryTree.h" //二叉树的头文件,里面有Item数据类型的定义 //定义结构体类型 typedef struct _node { struct _item item; struct _node *pnext; } Node; typedef struct _queue { struct _node *phead; struct _node *ptail; int total; } Queue; //声明队列具有的操作 Item* NewData(const char* name, int number); //创建空数据 Queue* NewQueue(); //创建空队列节点 void InitializeQueue(Queue *queue); //初始化队列 bool IsEmpty(const Queue *queue); //检查队列是否为空 int GetLength(const Queue *queue); //得到队列长度 void Append(Queue *queue, Item item); //队列增加值,只能在末尾追加 Item Remove(Queue *queue); //队列删除值,只能在头部删除 void Traverse(Queue *queue, void (*fun)(Node *node)); //遍历节点并执行回调函数 #endif定义函数showTree雏形
void showTree(const Tree *tree) { //声明普通变量,如:int floor,需要一个变量在递归时指示当前层数,然后来定位对应的内队列 //声明外队列 //递归遍历取得所有数据 //将数据处理输出 }递归遍历二叉树,获得所有数据
//递归遍历二叉树,tree为 以当前节点为根节点 的子树,floor记录当前所在层数,queueQueue为外队列 static void traverseTree(const Tree *tree, int *floor, QueueQueue *queueQueue) { //空树打印提示到错误流 if (TreeIsEmpty(tree)) { fprintf(stderr, "Empty tree, no data.\n"); return; } //临时树节点,存储以当前节点为根节点的子树 Tree tempTree; InitializeTree(&tempTree); tempTree = *tree; Trnode *p = tempTree.root; //存储当前节点,也是临时子树的根节点 //层数初始值为0,当层数>=外队列长度的时候,说明这是第一次进入这一层,所以要为这一层新建一个节点 if (*floor >= GetLengthQueue(queueQueue)) { AppendQueue(queueQueue, *NewDataQueue(NewQueue())); } //获取与当前层对应的内队列,将当前树节点数据追加到内队列末尾 Queue *queue = GetItemQueue(queueQueue, *floor).queue; Append(queue, p->item); //数据就在这时被存到队列中了 //下面是递归操作 if (TreeHasEmpty(&tempTree)) { //到达树的末梢,往回走 (*floor)--; return; } else { if (p->left != NULL) { //左边不为空,往左走 tempTree.root = p->left; (*floor)++; traverseTree(&tempTree, floor, queueQueue); } if (NULL != p->right) { //右边不为空,往右走 tempTree.root = p->right; (*floor)++; traverseTree(&tempTree, floor, queueQueue); } } (*floor)--; //两边都走过了,往回走 return; }这时可以完善showTree函数了
void showTree(const Tree *tree, FILE *fp) { //fp参数,为stdout则为标准输出,将打印到终端 //如果不为stdout,则应该为文件句柄,内容将输出到文件 if (fp == NULL) { fp = fopen("./tree.txt", "w"); } //如果未指定文件句柄则默认输出到程序当前执行路径 /*------------------------------------------------------------------------*/ //声明普通变量,如:int floor,需要一个变量在递归时指示当前层数,然后来定位对应的内队列 int floor = 0; //声明外队列 QueueQueue queueQueue; InitializeQueueQueue(&queueQueue); //递归遍历取得所有数据 traverseTree(tree, &floor, &queueQueue); //将数据处理输出,下一步实现 /*------------------------------------------------------------------------*/ //关闭文件句柄 if (fp != stdout) { fclose(fp); //关闭文件句柄 if (fp == NULL) { printf("tree.txt数据文件已保存到程序当前执行路径\n"); } else { printf("数据已保存到指定文件\n"); } } }数据都拿到了,接下来就是处理输出了(先做在终端中输出的那部分),在showTree函数中实现数据输出
void showTree(const Tree *tree, FILE *fp) { int floor = 0; if (fp == NULL) { fp = fopen("./tree.txt", "w"); } //如果未指定文件句柄则默认输出到程序当前执行路径 //外层队列 QueueQueue queueQueue; InitializeQueueQueue(&queueQueue); //递归遍历二叉树 traverseTree(tree, &floor, &queueQueue); /*------------------------------------------------------------------------*/ //将数据处理输出 while (queueQueue.total != 0) { //处理外队列,将节点逐个弹出 Queue *queue = RemoveQueue(&queueQueue).queue; while (queue->total != 0) { //处理内队列,将节点逐个弹出 Item item = Remove(queue); //打印数据前的空白 //打印数据前的短横线 fprintf(fp, "%d", item.value); //打印当前队列节点的数据 //打印数据后的短横线 //打印数据后的空白 } fprintf(fp, "\n"); //换行 } /*------------------------------------------------------------------------*/ if (fp != stdout) { fclose(fp); //关闭文件句柄 if (fp == NULL) { printf("tree.txt数据文件已保存到程序当前执行路径\n"); } else { printf("数据已保存到指定文件\n"); } } }让打印出来的结果好看一点,加入短横线和竖线的打印。这里需要计算调整。定义printBlocks 负责打印空白,定义printLines负责打印短横线。
//打印空白块的函数,此方法不用对外开放,所以加static修饰。 static void printBlocks(FILE *fp, char opt, int num) { //fp参数,为stdout则打印到终端,为文件句柄自然打印到对应文件 //opt选项,为'l'进入第一个分支,打印数据前面的空白,为'r'时同理进入第二个分支 //num参数,打印的数量,这需要在showTree函数中计算好 num *= BLOCK_LENGTH; //宏定义每块空白的长度,默认为1 if (opt == 'l') { //打印数据左边的空白 while (num > 0) { fprintf(fp, " "); num--; } } else if (opt == 'r') { //打印数据右边的空白 while (num > 1) { fprintf(fp, " "); num--; } } } //打印短横线与打印空白同理 static void printLines(FILE *fp, char opt, int num) { num *= BLOCK_LENGTH; //宏定义每块短横线的长度,默认为1 if (opt == 'l') { //打印数据左边的短横线 if (num > 0) fprintf(fp, "|"); while (num > 1) { fprintf(fp, "-"); num--; } } else if (opt == 'r') { //打印数据右边的短横线 while (num > 1) { fprintf(fp, "-"); num--; } fprintf(fp, "|"); } }在showTree函数中完善打印部分
void showTree(const Tree *tree, FILE *fp) { int floor = 0; if (fp == NULL) { fp = fopen("./tree.txt", "w"); } //如果未指定文件句柄则默认输出到程序当前执行路径 //外层队列 QueueQueue queueQueue; InitializeQueueQueue(&queueQueue); //递归遍历二叉树 traverseTree(tree, &floor, &queueQueue); while (queueQueue.total != 0) { //处理外队列,将节点逐个弹出 Queue *queue = RemoveQueue(&queueQueue).queue; while (queue->total != 0) { //处理内队列,将节点逐个弹出 Item item = Remove(queue); //计算需要多少块空白(短横线),num=2^(floor-1),num为空白块(短横线)数量,floor为层数,也是节点数 int num = (int)pow(2, queueQueue.total-1); num = (num < 0) ? 0 : num; /*------------------------------------------------------------------------*/ printBlocks(fp, 'l', num); //打印数据前的空白,'l'表示"left" printLines(fp, 'l', num); //打印数据前的短横线 fprintf(fp, "%d", item.value); //打印当前队列节点的数据 printLines(fp, 'r', num); //打印数据后的短横线,'r'表示"right" printBlocks(fp, 'r', num); //打印数据后的空白 /*------------------------------------------------------------------------*/ } fprintf(fp, "\n"); //换行 } if (fp != stdout) { fclose(fp); //关闭文件句柄 if (fp == NULL) { printf("tree.txt数据文件已保存到程序当前执行路径\n"); } else { printf("数据已保存到指定文件\n"); } } }简单测试函数及队列的实现代码等可以参见我的代码仓库 用两个队列实现起来略显笨重,如果文章有错误,欢迎留言指出。