二叉搜索树的黑框树状显示(标准C语言实现)
最近在用C语言实现平衡二叉搜索树,用了半天时间实现了树的基本功能后,发现我的二叉树打印方法还是停留在简单的逐行打印,此时的效果图如下:
一行的每两个数值分别对应上一行一个数值的左右孩子,用星号表示空。无疑这种表示方法十分鸡肋,利用人工点数的方式确定节点的左右孩子,实在不方便调试和扩展功能,如果节点多了可能还会造成开发者白内障,危害极大。
出于开发效率和个人身体健康考虑,我觉得我的程序应该可以有一种直观的二叉搜索树显示方式,比如树状打印就是很好的方式,百度浏览了一番找不到前辈们的代码,估计不怕麻烦用C语言写这个的人比较少。既然这样,那就自己动手,写一个二叉搜索树的树状显示函数,冲!
ps:
本程序用MacOS系统下的Xcode编译器开发,未使用windows下的API,整颗树状图都是用空格移动坐标进行逐行打印,具有不错的兼容性,男女老少皆可食用。
正文分割线
先上实现效果图:
接下来就是大概的实现方法:
可以看到效果图中除了数值和括号主要用到的符号就是竖线和下划线,但是在写这个方法的初期我们可以先忽略下划线和竖线,我们可以首先实现数值打印,让节点的值在控制台中对应的坐标中显示,如图:
开始的这一步想了挺久,最后想到数值在中序遍历序列中的位置既可以用来确定数值的横坐标,如果观察上面的图应该也可以发现,数值的横坐标也是按照从小到达逐渐递增的,而纵坐标则可以通过树的深度来确定,想通这一点,实现上面图中的效果就不难了。
typedef int ElemType;
typedef struct node
{
ElemType key;
struct node *lchild, *rchild;
int height;
}BNode,*BTree;
中序遍历二叉树并储存在数组中:
/*定义全局变量用于确定数组下标,因为使用了递归遍历的方法,
下标很难通过传参的方式确定,所以使用了全局变量,其实可以使用栈结构来储存但是懒于再写栈的代码了。
现在的方法虽然有点影响代码的结构性不过暂时没想到其他方便的方法*/
树的结构体:
int a=0;//全局变量
void saveInTraversal(BTree root,int *term)
{
if(!root)
return;
else
{
saveInTraversal(root->lchild,term);
term[a++]=root->key;
saveInTraversal(root->rchild,term);
}
}
在中序遍历序列中查找某个数值的位置:
/**
*@brief 中序遍历序列中查找某个数值的位置
*@param term 中序遍历序列数组
*@param n 数组长度
*@param x 需要搜索的数值
*/
int findNum(int *term, int x, int n)
{
for(int i=0;i<n;i++)
if(term[i]==x)
return i+1;
return -1;
}
获取节点深度:
int deepth(BTree root)
{
if (!root)
{
return 0;
}
int left=deepth(root->lchild);
int right=deepth(root->rchild);
return left>=right ? left+1:right+1;
}
获取节点的子节点个数:
int count(BTree T,int num)
{
if(!T)
return num;
else
{
num=count(T->lchild,num);
num=count(T->rchild,num);
}
num++;
return num;
}
显示函数(无下划线和竖线):
#define MAX(a,b) ((a)>(b)?(a):(b)) //取两者最大值
#define MAX_DIGITS 4 //二叉树树节点值的最大位数。比如树中最大的节点值为1000,则设为4,假设最大为10000,则应设为5,如果设置的该常量数字比节点最大值位数小,则会导致位置错乱
#define MAX_NODE 100//二叉树树的最大节点树目
void display(BTree root)
{
int absotiveDistance[2];//绝对距离,[1]表示上一个兄弟节点,[0]-[1]用于计算节点的相对距离
int array[MAX_NODE]={0};//用于储存搜索二叉树的中序遍历序列
int relativeDistance=0;//相对距离
//定义标志节点N,节点的子孩子为空时用于占位识别
BTree N;
N=(BTree)malloc(sizeof(BNode));
N->key=-1;
N->lchild=NULL;
N->rchild=NULL;
N->height=0;
int deep=deepth(root);
saveInTraversal(root, array);
//临时变量,用于层序遍历
int k=0;
int j=1;
int n=1;
BTree term[MAX_NODE]={NULL};//指针数组,储存树节点,用于层序遍历
term[0]=root;
while(n!=deep+1)
{
absotiveDistance[0]=0;
absotiveDistance[1]=0;
if(term[k]->lchild!=NULL&&term[k]->lchild!=N)
term[j++]=term[k]->lchild;
else
term[j++]=N;
if(term[k]->rchild!=NULL&&term[k]->rchild!=N)
term[j++]=term[k]->rchild;
else
term[j++]=N;
k++;
if(k==pow(2,n)-1)
{
for(int i=pow(2,n-1)-1;i<k;i++)
{
absotiveDistance[0]=findNum(array, term[i]->key, count(root, 0))*MAX_DIGITS;
if(absotiveDistance[0]==-MAX_DIGITS)
continue;
relativeDistance=absotiveDistance[0]-absotiveDistance[1];
absotiveDistance[0]+=digits(term[i]->key)+2;
absotiveDistance[1]=absotiveDistance[0];
for(int m=0;m<relativeDistance;m++)
{
printf(" ");
}
if(term[i]!=N)
{
printf("(%d)",term[i]->key);
}
}
printf("\n");
printf("\n");
n++;
}
}
}
这里有必要解释一下我使用的层序遍历方法,因为别人看的话的确会难以理解。
首先层序遍历是难以用递归去实现的,需要用一个节点指针数组去进行储存层序序列。
(更新(2019.7.12):上面这里有误,评论区大佬已经用精简的层序遍历递归算法糊我脸上了,具体看评论区大神【可爱的LYF】的代码!!流下了不学无术的泪水)
完成了这一步之后,就实现了节点在特定的位置打印,不过为了更方便观看,还需要加上用下划线和竖线组成的“枝干”
这里我只阐述一下我自己的实现思路
因为没有用到坐标定位打印,所以只能逐层去打印,这样子的话一层的下划线长度和竖线位置都需要从上一层就确定好,即在打印一层的时候需要有预见性的打印对应左右孩子位置长度的下划线。
此时可以定义两个变量,通过用findNum()获取左右孩子的位置,再计算相对位置,便可以实现预见性打印相应长度的下划线了,如图:
接下来就是竖线的打印。
这里我们可以定义一个数组,用于储存竖线的位置。
而竖线的位置的获取方法和下划线的相似,通过获取左右孩子的位置就可以确定了。
最后贴上所有源码:
————————————————————————fun.c——————————————————————————————
//
// fun.c
// AVL数
//
// Created by 川十 on 2019/2/20.
// Copyright © 2019年 川十. All rights reserved.
//
#include "fun.h"
#define MAX(a,b) ((a)>(b)?(a):(b))
#define MAX_DIGITS 4
#define MAX_NODE 100
#define MAX_TMP 10000 //MAX_NODE^2
int a=0;
void display(BTree root)
{
int k=0;
int j=1;
int n=1;
int deep=deepth(root);
BTree N;
N=(BTree)malloc(sizeof(BNode));
N->key=0;
N->lchild=NULL;
N->rchild=NULL;
N->height=0;
BTree term[MAX_TMP]={NULL};
term[0]=root;
while(n!=deep+1)
{
if(term[k]->lchild!=NULL&&term[k]->lchild!=N)
term[j++]=term[k]->lchild;
else
term[j++]=N;
if(term[k]->rchild!=NULL&&term[k]->rchild!=N)
term[j++]=term[k]->rchild;
else
term[j++]=N;
k++;
if(k==pow(2,n)-1)
{
for(int i=pow(2,n-1)-1;i<k;i++)
{
if(term[i]!=N)
printf("%d ",term[i]->key);
else
printf("* ");
}
printf("\n");
n++;
}
}
}
int deepth(BTree root)
{
if (!root)
{
return 0;
}
int left=deepth(root->lchild);
int right=deepth(root->rchild);
return left>=right ? left+1:right+1;
}
BTree creatTree(BTree root, int n)
{
srand((unsigned) time(NULL));
root->rchild=NULL;
root->lchild=NULL;
root->height=0;
root->key=abs(rand())%1000;
int term[MAX_NODE]={0};
int flag;
term[0]=root->key;
for(int i=1;i<n;i++)
{
flag=0;
int num=abs(rand())%1000;
for(int j=0;j<i;j++)
{
if(term[j]==num)
{
flag=1;
i--;
}
}
if(!flag)
{
root=insert(root, num);
term[i]=num;
}
}
return root;
}
BTree insert(BTree t,int x)
{
if(!t)
{
t=(BTree)malloc(sizeof(BNode));
t->key=x;
t->lchild=NULL;
t->rchild=NULL;
t->height=0;
}
else if(x<t->key)
{
t->lchild=insert(t->lchild,x);
if(height(t->lchild)-height(t->rchild)==2)
{
if(x<t->lchild->key)
t=LL_rotation(t);
else
t=LR_rotation(t);
}
}
else if(x>t->key)
{
t->rchild=insert(t->rchild,x);
if(height(t->rchild)-height(t->lchild)==2)
{
if(x>t->rchild->key)
t=RR_rotation(t);
else
t=RL_rotation(t);
}
}
else
{
printf("不允许插入相同节点!\n");
}
t->height=MAX(height(t->lchild),height(t->rchild))+1;
return t;
}
BTree LL_rotation(BTree T)
{
BTree k2=T->lchild;
T->lchild=k2->rchild;
k2->rchild=T;
T->height=MAX(height(T->rchild),height(T->lchild))+1;
k2->height=MAX(height(T->rchild),height(T->lchild))+1;
return k2;
}
BTree LR_rotation(BTree T)
{
T->lchild=RR_rotation(T->lchild);
T=LL_rotation(T);
return T;
}
BTree RR_rotation(BTree T)
{
BTree k2=T->rchild;
T->rchild=k2->lchild;
k2->lchild=T;
T->height=MAX(height(T->rchild),height(T->lchild))+1;
k2->height=MAX(height(T->rchild),height(T->lchild))+1;
return k2;
}
BTree RL_rotation(BTree T)
{
T->rchild=LL_rotation(T->rchild);
T=RR_rotation(T);
return T;
}
int height(BTree T)
{
if(!T)
return 0;
else
return T->height;
}
void preTraversal(BTree root)
{
if(!root)
return;
else
{
printf("%d ",root->key);
preTraversal(root->lchild);
preTraversal(root->rchild);
}
}
void inTraversal(BTree root)
{
if(!root)
return;
inTraversal(root->lchild);
printf("%d ",root->key);
inTraversal(root->rchild);
}
void saveInTraversal(BTree root,int *term)
{
if(!root)
return;
else
{
saveInTraversal(root->lchild,term);
term[a++]=root->key;
saveInTraversal(root->rchild,term);
}
}
int count(BTree T,int num)
{
if(!T)
return num;
else
{
num=count(T->lchild,num);
num=count(T->rchild,num);
}
num++;
return num;
}
void display2(BTree root)
{
int absotiveDistance[2];//绝对距离,[1]表示上一个兄弟节点,[0]-[1]用于计算节点的相对距离
int array[MAX_NODE]={0};//用于储存搜索二叉树的中序遍历序列
int relativeDistance=0;//相对距离
int leftLineNum=0;//节点左边需打印的下划线符号个数
int rightLineNum=0;//节点右边需打印的下划线符号个数
int rightLineNum2=0;//零时变量,用于储存需打印节点的上一个兄弟节点右边打印了的下划线符号个数,便于计算需移动位数。(relativeDistance-rightLineNum2)
int vertiLineArray[MAX_NODE]={0};//竖线位置存储
int vertiLineNum=0;//用于记录下一行需打印的竖线个数
//定义标志节点N,节点的子孩子为空时用于占位识别
BTree N;
N=(BTree)malloc(sizeof(BNode));
N->key=-1;
N->lchild=NULL;
N->rchild=NULL;
N->height=0;
int deep=deepth(root);
saveInTraversal(root, array);
//临时变量,用于层序遍历
int k=0;
int j=1;
int n=1;
BTree term[MAX_TMP]={NULL};//指针数组,储存树节点,用于层序遍历
term[0]=root;
while(n!=deep+1)
{
absotiveDistance[0]=0;
absotiveDistance[1]=0;
if(term[k]->lchild!=NULL&&term[k]->lchild!=N)
term[j++]=term[k]->lchild;
else
term[j++]=N;
if(term[k]->rchild!=NULL&&term[k]->rchild!=N)
term[j++]=term[k]->rchild;
else
term[j++]=N;
k++;
if(k==pow(2,n)-1)
{
rightLineNum2=0;
vertiLineNum=0;
for(int i=pow(2,n-1)-1;i<k;i++)
{
leftLineNum=0;
rightLineNum=0;
absotiveDistance[0]=findNum(array, term[i]->key, count(root, 0))*MAX_DIGITS;
if(absotiveDistance[0]==-MAX_DIGITS)
continue;
relativeDistance=absotiveDistance[0]-absotiveDistance[1];
absotiveDistance[0]+=digits(term[i]->key)+2;
absotiveDistance[1]=absotiveDistance[0];
if(term[i]->lchild!=N&&term[i]->lchild!=NULL)
{
leftLineNum=(absotiveDistance[0]-findNum(array, term[i]->lchild->key, count(root, 0))*MAX_DIGITS)-digits(term[i]->key)-4;
vertiLineArray[vertiLineNum++]=findNum(array, term[i]->lchild->key, count(root, 0))*MAX_DIGITS+2;
}
if(term[i]->rchild!=N&&term[i]->rchild!=NULL)
{
rightLineNum=(findNum(array, term[i]->rchild->key, count(root, 0))*MAX_DIGITS-absotiveDistance[0])+2;
vertiLineArray[vertiLineNum++]=findNum(array, term[i]->rchild->key, count(root, 0))*MAX_DIGITS+1;
}
for(int m=0;m<(rightLineNum2==0?(relativeDistance-leftLineNum) : (relativeDistance-leftLineNum-rightLineNum2));m++)
{
printf(" ");
}
for(int m=0;m<leftLineNum;m++)
{
printf("_");
}
if(term[i]!=N)
{
printf("(%d)",term[i]->key);
}
for(int m=0;m<rightLineNum;m++)
printf("_");
rightLineNum2=rightLineNum;
}
printf("\n");
if(vertiLineNum)
{
for(int m=0;m<vertiLineNum;m++)
{
for(int j=0;j<(m==0?vertiLineArray[m] : vertiLineArray[m]-vertiLineArray[m-1]);j++)
printf(" ");
printf("|");
vertiLineArray[m]++;
}
}
printf("\n");
n++;
}
}
}
int findNum(int *term, int x, int n)
{
for(int i=0;i<n;i++)
if(term[i]==x)
return i+1;
return -1;
}
int digits(int x)
{
int num=0;
while(x!=0)
{
x/=10;
num++;
}
return num;
}
————————————————————————fun.h—————————————————————————————
//
// fun.h
// AVL数
//
// Created by 川十 on 2019/2/20.
// Copyright © 2019年 川十. All rights reserved.
//
#ifndef fun_h
#define fun_h
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
typedef int ElemType;
typedef struct node
{
ElemType key;
struct node *lchild, *rchild;
int height;
}BNode,*BTree;
void display(BTree root);
void preTraversal(BTree root);
void inTraversal(BTree root);
void saveInTraversal(BTree root,int *term);
int digits(int x);
int findNum(int *term, int x, int n);
int count(BTree T,int num);
int deepth(BTree root);
BTree insert(BTree t,int x);
int height(BTree T);
BTree creatTree(BTree root, int n);
void display2(BTree T);
BTree LL_rotation(BTree T);
BTree LR_rotation(BTree T);
BTree RR_rotation(BTree T);
BTree RL_rotation(BTree T);
#endif /* fun_h */
————————————————————————main.c————————————————————————————
//
// main.c
// AVL数
//
// Created by 川十 on 2019/2/20.
// Copyright © 2019年 川十. All rights reserved.
//
#include "fun.h"
int main(int argc, const char * argv[]) {
BTree T;
T=(BTree)malloc(sizeof(BNode));
T=creatTree(T, 20);
// printf("%d\n",deepth(T));
// printf("%d\n",count(T,0));
// preTraversal(T);
// inTraversal(T);
printf("\n");
display(T);
printf("\n");
display2(T);
}