最近在写c语言的课程设计时,因为想要直观地观察二叉树的一个形状,特此花费时间写了一个算法,会java等其他语言的化也可以直接套用,因为打印没有使用很多c语言的东西。
算法讲解:这一部分说简单其实也不简单,但是说很难的话其实也没有那么难。懒得码很多字讲解,把核心将一遍大家应该都会看得懂得。至少学到二叉树这儿,你得编程水平不算是一个小白了。想要完全弄懂的话,最好买一个格子本,自己也动手画画就知道了。为了激起你的好奇心,先给你看看个最终的一些输出,这个是摩斯编码哦。
1.计算每一层的偏移量(这个偏移量指的是相对于最后一层的距离,图纸中有标注)。offset = 3*2^(maxDepth-3)-1;(后面我画了一个图,可以先把那个图片看看再回来看这儿也行)。
2.当前层的元素结构输出字符'─'的数量=offset;
3.当前层两个元素之间的间隔数=2*offset+1;
4.以上三个规律需要自己画一个完全二叉树来找到,如果你的倒数第2层的基本结构不一样的话,上述规律可能会有所不同。
5.先将任意的一颗二叉树都理解为满二叉树;
6.将一颗二叉树按照层序遍历的方式获取其所有的节点数量,转为一个数组,这个算法实现其实也很简单,后面再讲,想必大家都知道怎么弄了,使用队列即可实现;
7.从步骤6中逐一获取每一层的元素,怎么获取其实很简单,第一次拿到的肯定是根节点,索引为0,如果根节点有左右孩子,那么第二层的节点元素就是1,2;每次依赖上一层的就能计算出下一层的节点的一个开始和结束的索引位置;
8.①输出偏移距,距离最后一层元素的间隔,输出offset个空格即可(这一步只在每一层的时候进行输出,以下六步都需要每输出一个元素就输出一次);
②输出制表符’┌‘;
③输出offset个制表符’─‘;
④输出节点存储的字符,如果是字符串或者其他数字类型的,重新计算一下offset就好了,为了方便就没有搞那么复杂了,留给读者去实现吧;
⑤输出offset个制表符’─‘;
⑥输出制表符’┐‘;
⑦输出同一层元素间的间隔数2*offset+1;
9.如果树是满二叉树,上述代码就没有问题了,但是其实如果不是呢?如果不是其实也很简单,如果没有元素,我们也按照步骤8进行输出,只不过,输出全部换为空格进行占位即可。那么我们该怎么知道有没有元素呢?看到这儿你应该会发现其实我们的打印算法实现是基于层序遍历的,因此总是可以知道下一层哪些位置没有元素。于是在每一层遍历的时候都使用连个数组进行存储这些信息,一个用来存储当前层元素的信息,用currRecord表示;一个存储下一层的元素信息,用p表示;在每一层打印完后就将p的记录信息复制到currRecord。这个currRecord的大小总是当前层的满元素的大小,即2^(n-1),而p的大小则是下一层满元素时的大小,即2^(n),(第一层时currRecord的大小肯定是1嘛,根元素肯定不为NULL咯,所有初始时就设置为currRecord[0]=0);
10.经过步骤9,我们在每次打印的时候就要先遍历currRecord数组了,0表示该位置有元素,-1表示没有;if(currRecord[k] == 0){执行步骤8}else{输出结构占位空格,输出的数量=步骤8输出的字符数量}。
11.最后一层特殊处理话,为什么最后一层需要特殊拿出来处理呢?因为最后一层没有结构呀,最后一层肯定全部是叶子节点,为了处理最后一层,需要拿到它的上一层节点。最后一层的输出间隔和你自己设置的基本节点结构有关了,这儿根据我的解决方案自己也实现一下吧,建议就不要改了,使用5个空格最好了。
12.最后你就可以拥有一颗你自己的二叉树了,通过上面的算法讲解,看下面的代码你就会很快弄懂了。然后就可以扩展自己的功能,输出字符串,数字啥的类型了。
typedef char BNElemType;
typedef struct BinNode BinNode;
struct BinNode{
//左孩子
BinNode * lChild;
//右孩子
BinNode * rChild;
//存储的元素
BNElemType data;
};
/**
* 打印二叉树图像(打印字符类型) <br>
* @param root 二叉树根节点
* @param size 二叉树节点数量
* @param getChar 将节点存储元素转为char类型的函数
*/
void printBinTree(BinNode * root,int size,char (*getChar)(BNElemType ele)){
if(root == NULL) return;
//┌───A───┐ 这样的一个形状称之为一个结构,表示一个非最后一层的元素的节点
//将每一层的节点都放入队列中,这儿你自己实现这个功能也可以
BinNode ** queue = toArray(root, size);
//一层一层打印
int maxDepth = getMaxDepth(root); //获取二叉树的最大深度
int offset =(int) (3 * pow(2,(maxDepth-3))-1);//偏移量(距离控制台最左边的距离)
int count = 1; //当前打印层数
int start=0,end=0; //每一层的开始,结束指针
int * currRecord = malloc(sizeof(int));//当前层的打印记录
currRecord[0] = 0;
int lenLast3 = 0;
int currRecordLen =1;
while (count < maxDepth){
int num = 0;//用来统计当前层的孩子数量
//输出当前层所有元素
//输出当前层的偏移量
for (int j = 0; j < offset; ++j) {
printf(" ");
}
//下一层应该有的节点数量
int shouldNum = (int) pow(2,count);
int p[shouldNum];
//生成记录
int z = start;
for (int i = 0; i < currRecordLen; ++i) {
if(currRecord[i] == 0){
p[2*(i+1)-2] = queue[z]->lChild !=NULL ? 0 : -1;
p[2*(i+1)-1] = queue[z]->rChild !=NULL ? 0 : -1;
z++;
} else{
p[2*(i+1)-1] = -1;//右
p[2*(i+1)-2] = -1;//左
}
}
int k = 0;
int pos = start;
while (k < currRecordLen){
if(currRecord[k] == 0){
//输出形状 ─┌ ┐
int n = offset; //输出字符'─'的数量
if(count == maxDepth-1) n++; //在倒数第二层时保证至少有一个'─'
if(queue[pos]->lChild != NULL){
printf("┌");
for (int j = 0; j < n; ++j) {
printf("─");
}
num++;
} else {
//如果不是最后一层则需要输出展位,同时在下一层的这个位置需要输出结构占位
for (int j = -1; j < n; ++j) {
printf(" ");//1
}
}
printf("\033[31m%c\033[0m",getChar(queue[pos]->data));
if(queue[pos]->rChild != NULL){
for (int j = 0; j < n; ++j) {
printf("─");
}
printf("┐");
num++;
} else {
for (int j = -1; j < n; ++j) {
printf(" ");//1
}
}
//输出同一层中相连两个节点间的间隔
//当前层的结构长度
int length = 2*offset+1 ;
for (int j = 0; j < length; ++j) {
printf(" ");
}
++pos;
++k;
} else{ //输出占位 = 结构占位 + 间隔
//输出结构占位
int length = count==maxDepth-1 ? 5 : 2*offset+3;
for (int i = 0; i < length; ++i) {
printf(" ");
}
//输出同一层中相连两个节点间的间隔
int length2 = 2*offset+1 ;
for (int j = 0; j < length2; ++j) {
printf(" ");
}
k++;
}
}
//如果没有到最后一层,则需要将下一层的记录赋给currRecord
if(maxDepth - count !=1){
free(currRecord);
currRecord = malloc(shouldNum * sizeof(int));
for (int i = 0; i < shouldNum; ++i) {
currRecord[i] = p[i];
}
currRecordLen = shouldNum;
}
//下一层的偏移量
offset = (offset-1)/2;
count++;
printf("\n");
//更新下一层节点的开始和结束位置
if(count < maxDepth){
start = end+1;
end = end+num;
}
}
int pos = start;
int k = 0;
while (k<currRecordLen){
if(currRecord[k] == 0){
if(queue[pos]->lChild!=NULL){
printf("\033[31m%c\033[0m",getChar(queue[pos]->lChild->data));
printf(" ");//3
} else{
printf(" ");//4
}
if(queue[pos]->rChild !=NULL){
printf("\033[31m%c\033[0m",getChar(queue[pos]->rChild->data));
printf(" ");//1
} else{
printf(" ");//2
}
++pos;
++k;
} else{
printf(" ");//6 = 5(结构占位) + 1(元素间隔)
k++;
}
}
//释放申请的内存
free(currRecord);
free(queue);
printf("\n");
}
/**
* 将树按层从左到右的顺序转为数组
* @param root 二叉树根节点
* @param size 树的节点数量
* @return 数组实现
*/
BinNode ** toArray(BinNode * root, int size){
if(root == NULL) return NULL;
BinNode ** queue = malloc(sizeof(BinNode*) * size);
int enqueue = 0;//入队指针
int dequeue = 0; //出队指针
queue[enqueue++] = root; //先放入根节点
while (enqueue<size){
BinNode * node = queue[dequeue++];
if(node->lChild != NULL){
queue[enqueue++] = node->lChild;
}
if(node->rChild != NULL){
queue[enqueue++] = node->rChild;
}
}
return queue;
}
/**
* 获取由某个节点构成的生成树的最大深度
* @param node 生成树的根节点
* @return
*/
int getMaxDepth(BinNode * node){
if(node == NULL) return 0;
int depthL , depthR;
depthL = getMaxDepth(node->lChild);
depthR = getMaxDepth(node->rChild);
return 1 + (depthL > depthR ? depthL : depthR);
}
下面是一些打印测试: