最近在复现算法导论第二部分的内容,一上来就是和二叉树相关的堆排序(heapsort)算法。我的导师和我说过二叉树是人类想象出来的东西,只是心中有树。在做这些算法的时候不能直接观察到二叉树是一件很郁闷的事。。。实际上网上已有很多类似的画法,如使用linux工具,或者使用其他比较复杂的库。但是算法导论书中往往用一个数组作为二叉树的容器,上述工具就会显得有点繁琐。所以我自己写了一个函数,基本的思想就是根据一个数组,然后用空格和数字来构成树。比如我有一个数组testArray[14] = {27, 17, 3, 16, 13, 10, 1, 5, 7, 12, 4, 8, 9, 0};
,最后函数的输出是这样:
The height of this tree is: 4
27
17 13
16 12 10 1
5 7 3 4 8 9 0
输入其他长度的数组也可以画出:
27
17 13
16 12 10 1
5 7 3 4 8 9 0 56
5
树的高度
首先需要计算一下树的高度,为
c
e
i
l
(
log
2
A
.
l
e
n
g
t
h
)
ceil(\log_2 A.length)
ceil(log2A.length)。其中ceil()
为上取整函数。C语言中没有相应的函数,但是根据数学变化
log
a
b
=
log
n
b
/
log
n
a
\log_a b = \log_n b/ \log_n a
logab=lognb/logna,我们可以用C语言自带的
log
e
x
\log_e x
logex函数来进行计算:
//myUtil.h
#define lg2(x) static_cast<int>(ceil(log(x+1)/log(2)))
这里我直接把它写成头文件的定义,并且加上static_cast<int>()
函数把计算结果转化为int
类型。
元素间隔
我采用自上而下的方法进行计数,就是树的根行树为n=0
,子目录向下发展时n
递增。在头文件中定义了两个宏定义:
//myUtil.h
#define baseInterval 7
#define elementInterval 1
其中,baseInterval
指的是最底层行元素间距。elementInterval
指的是数据长度,我推荐设为1。对于因为数据长度造成的树的偏移,后面还会有处理。baseInterval
最好设为奇数,这样上层的元素可以坐在底层两元素的正上方。
interval
假设此时树有
h
e
i
g
h
t
height
height层,选取第
h
h
h层,也就是
n
=
h
e
i
g
h
t
−
h
−
1
n=height - h -1
n=height−h−1。这时我们可以从图中看出元素间隔和左侧间隔的数目,找不出规律可以先列举一下。当树的高度
h
e
i
g
h
t
=
4
height=4
height=4时:
n
=
1
,
i
n
t
e
r
v
a
l
=
7
∗
e
l
e
+
4
∗
(
b
a
s
e
−
1
)
n=1,interval=7*ele+4*(base-1)
n=1,interval=7∗ele+4∗(base−1)
n
=
2
,
i
n
t
e
r
v
a
l
=
3
∗
e
l
e
+
1
∗
(
b
a
s
e
−
1
)
n=2,interval=3*ele+1*(base-1)
n=2,interval=3∗ele+1∗(base−1)
…
…
……
……
其实已经可以很直接的看出每一行的元素间的间隔了,公式表示如下:
i
n
t
e
r
v
a
l
=
(
2
h
e
i
g
h
t
−
n
−
1
)
∗
e
l
e
+
b
a
s
e
∗
2
h
e
i
g
h
t
−
n
−
1
interval = (2^{height-n}-1)*ele+base*2^{height-n-1}
interval=(2height−n−1)∗ele+base∗2height−n−1
startblank
对于第一行前的空格元素数目,也可以通过观察统计出来:
n
=
1
,
i
n
t
e
r
v
a
l
=
3
∗
e
l
e
+
3
/
2
∗
(
b
a
s
e
−
1
)
n=1,interval=3*ele+3/2*(base-1)
n=1,interval=3∗ele+3/2∗(base−1)
n
=
2
,
i
n
t
e
r
v
a
l
=
1
∗
e
l
e
+
1
/
2
∗
(
b
a
s
e
−
1
)
n=2,interval=1*ele+1/2*(base-1)
n=2,interval=1∗ele+1/2∗(base−1)
…
…
……
……
规律也是很明显的,为了计算简便,实际上可以用已经算出的元素间间隔来表示:
s
t
a
r
t
B
l
a
n
k
=
(
i
n
t
e
r
v
a
l
B
l
a
n
k
−
e
l
e
m
e
n
t
I
n
t
e
r
v
a
l
−
b
a
s
e
I
n
t
e
r
v
a
l
+
1
)
/
2
;
startBlank = (intervalBlank-elementInterval-baseInterval+1)/2;
startBlank=(intervalBlank−elementInterval−baseInterval+1)/2;
计算的代码如下:
//myUtil.cpp
k_ele = static_cast<int>(pow(2, height-iDraw))-1;
k_base = static_cast<int>(pow(2, height-iDraw-1));
//元素间间隔
intervalBlank = k_ele*elementInterval+k_base*(baseInterval-1);
//左侧间隔
startBlank = (intervalBlank-elementInterval-baseInterval+1)/2;
空格数目微调
按照上面的计算画出来的图有点小瑕疵,就是当元素的位数超过1的时候,相应的行会向后移动:
The height of this tree is: 4
27
17 13
16 12 10 1
5 7 3 4 8 9 0 56
可以看到后面的数字向前攒动了,原因就是我设置的元素单元只能容纳一个长度的整形。所以我这里加了点微调,就是当数字位数超过1的时候,将空格相应的增加:
//judge digit of an INT
int get_digit(int x)
{
int length=0;
while(x)
{
x/=10;
length++;
}
return length;
}
源码
最后整体的代码如下:
//
// Created by king98 on 19-4-29.
// myUtil.c
#include <math.h>
#include <cstdio>
#include "myUtil.h"
//画二叉树的测试函数
void test_drawBINTREE()
{
uint32_t heap_size = 15;
int testArray[15] = {27, 17, 3, 16, 13, 10, 1, 5, 7, 12, 4, 8, 9, 0, 5};
drawBINTREE(testArray, heap_size);
}
void drawBINTREE(int A[], int size)
{
int height = lg2(size);
printf("The height of this tree is: %d\n", height);
int numRow;
int startBlank = 0;
int intervalBlank = 0;
int subscript = 0;
int tempLength;
int k_ele;
int k_base;
//自上而下
for(int iDraw=0; iDraw<height; iDraw++)
{
numRow = static_cast<int>(pow(2, iDraw));
k_ele = static_cast<int>(pow(2, height-iDraw))-1;
k_base = static_cast<int>(pow(2, height-iDraw-1));
if(height-iDraw>1)
{
intervalBlank = k_ele*elementInterval+k_base*(baseInterval-1);
startBlank = (intervalBlank-elementInterval-baseInterval+1)/2;
}
else
{
startBlank = 0;
intervalBlank = baseInterval;
}
//画此行第一个元素前面的空格
for(int iBlank=0; iBlank<startBlank; iBlank++){printf(" ");}
//输出此行第一个元素
printf("%d", A[subscript]);
subscript++;
//画接下来的空格组+元素
for(int iCouple=0; iCouple<numRow-1;iCouple++)
{
tempLength = get_digit(A[subscript-1]);
if(subscript<size)
{
for (int iBlank=0; iBlank<intervalBlank-tempLength+1; iBlank++){printf(" ");}
printf("%d", A[subscript]);
subscript++;
}
}
//换行
printf("\n");
}
}
//judge digit of an INT
int get_digit(int x)
{
int length=0;
while(x)
{
x/=10;
length++;
}
return length;
}
//
// Created by king98 on 19-4-29.
// myUtil.h
#ifndef TEST_MYUTIL_H
#define TEST_MYUTIL_H
#define lg2(x) static_cast<int>(ceil(log(x+1)/log(2)))
#define baseInterval 5
#define elementInterval 1
void test_drawBINTREE();
void drawBINTREE(int A[], int size);
int get_digit(int x);
#endif //TEST_MYUTIL_H