二叉树的图形化展示(到终端/文件)【C】

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/NICE__FUTURE/article/details/83317012
先亮结果

输出到终端效果:输出到终端效果图
输出到文件效果:
输出到文件效果

写在前面
怎么会想到把二叉树图形化

好玩……是一方面,另一方面是:学写二叉树的时候需要检查自己建立的二叉树结构对不对,在C语言里面的做法会是把当前树节点及左右子树节点地址及对应数据打印出来,然后通过节点联系对应来检查,我暑假写的时候就是这么干的。
不过,我当时想到了Java,Java只有引用,那到时候要怎么处理会更好些?
于是,我就有了图形化打印的想法。


##### 思路
(以下方法能实现,但不是 队列 的正确使用姿势,不建议采用了)
另外,可以参考这里


涉及的数据结构
  • 不论是将二叉树打印到终端还是文件,图形化输出都是按层进行的。
    (我尝试过按递归遍历的顺序打印,但都没有处理好)
  • 于是,涉及的数据结构有:二叉树(本身)、队列(两个)
    • 二叉树,本身就是要将二叉树的数据打印输出
    • 队列1(内队列)Queue,用来存储每一层的数据,一层占用一个内队列
    • 队列2(外队列)QueueQueue, 用来存储每一层对应队列的头节点地址等信息,一个内队列的信息占用一个外队列节点,一棵二叉树用一个外队列
      (如果能按遍历顺序打印,那可以不用队列结构;如果有更好的思路,可以只用一个队列,用两个队列是我目前想到的方法)
实现思路
  • 创建一个外队列
  • 递归遍历二叉树,每进入一层
    • 如果是第一次进入,那么先给外队列增加一个节点
    • 在外队列中找到当前层对应的内队列,将当前树节点数据添加到内队列中
  • 遍历结束,二叉树所有数据都已被获得。依次弹出外队列的节点,每个节点对应着一个内队列,所以再将内队列节点依次弹出,弹出的同时打印数据
  • 在打印数据前后加入一定数量的短横线和竖线进行美化
开始C代码实现
前提

这是我写的简单队列的接口,作为内队列。外队列与此类似
(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");
		}
	}
}
程序完成
写在后面

简单测试函数及队列的实现代码等可以参见我的代码仓库
用两个队列实现起来略显笨重,如果文章有错误,欢迎留言指出。

展开阅读全文

没有更多推荐了,返回首页