二叉树的存储结构和基本运算
顺序存储结构
完全二叉树
完全二叉树的性质
一个编号为i的节点
- 其双亲节点的编号为
i/2 - 左孩子编号为
2i - 右孩子编号为
2i+1


不使用下标为0的位置
非完全二叉树
用空节点将非完全二叉树补为完全二叉树

使用特殊字符标记补齐的空节点
特点
- 完全二叉树非常适合用顺序存储结构
- 用顺序存储结构存储一般的二叉树会造成大量的空间浪费
- 在顺序存储结构中,找到节点的双亲(
i/2)和孩子(左孩子2i右孩子2i+1)比较容易
链式存储结构
借鉴孩子兄弟表示法的思想,在节点中设置左孩子指针和右孩子指针
typedef int ElemType;
typedef struct _btnode{
ElemType data;
struct _btnode *lchild, *rchild;
}btnode;

特点
- 在存储非完全二叉树时比顺序结构节省空间
- 找一个节点的孩子容易,找双亲不容易
- 空指针个数 = 节点数 - 1
n个节点,对应2n个指针域,n-1个分支,所以非空指针域有n-1个,综上空指针域等于2n-(n-1)=n+1
遍历
用图形表示树形结构固然直观,但这样的示意图对计算机不太友好,计算机只会做循环和判断,也就是说它只会处理线性序列。从根节点出发,按照一定次序访问二叉树中的所有节点,使得每个节点只被访问一次,让二维的树形结构变成一个一维的线性序列。
先序遍历
若树不空
- 先访问根节点
- 遍历其左子树
- 遍历其右子树

遍历顺序是A、B、D、E、H、C、F、I、G
采用递归函数来实现这个算法
void
PreOrderTraversal( btnode *bt )
{
if( bt == NULL ){
return;
}
printf("%c", bt->data);
PreOrderTraversal( bt->lchild );
PreOrderTraversal( bt->rchild );
}
中序遍历
- 先遍历其左子树
- 访问根节点
- 再遍历右子树

遍历顺序是D、B、H、E、A、F、I、C、G
void
InOrderTraversal( btnode *bt )
{
if( bt == NULL ){
return;
}
InOrderTraversal( bt->lchild );
printf("%c", bt->data);
InOrderTraversal( bt->rchild );
}
后序遍历
- 遍历左子树
- 遍历右子树
- 访问根节点

遍历顺序是D、H、E、B、I、F、G、C、A
void
PostOrderTraversal( btnode *bt )
{
if( bt == NULL ){
return;
}
PostOrderTraversal( bt->lchild );
PostOrderTraversal( bt->rchild );
printf("%c", bt->data);
}
层序遍历

前序、中序和后序遍历都是一棵子树一棵子树的遍历,双亲和孩子的信息都压入了栈中保存了起来,而层序遍历按照层而不是子树的顺序访问,如果访问完了左孩子访问右孩子的时候,没有记录左孩子的信息,那么左孩子下面的子树将无法访问。所以层序遍历需要单独设置栈或队列来保存节点信息。
- 从树的第一层,也就是根节点开始
- 从上到下逐层遍历
- 在同一层中,按从左到右的顺序逐个访问
算法实现思路
- 从队列中取出一个元素
- 访问该元素指向的节点
- 若该元素所指向的左、右孩子节点非空,则将其左、右孩子的指针按顺序入队
void
LevelOrderTraversal( btnode *bt )
{
btnode *t;
lQueue *q;
do{
//初始化一个链队
q = ( lQueue* )malloc( sizeof( lQueue ) );
q->front = q->rear = NULL;
}while(0);
if( bt == NULL ){ //若为空树直接返回
return;
}
enQueue( q, bt ); //根节点进队
while( q->rear != NULL ){
btnode *t = q->front->address; //队头的树节点
if( t->lchild ){
/* 有左孩子则进队*/
enQueue( q, t->lchild );
}
if( t->rchild ){
/* 有右孩子则进队 */
enQueue( q, t->rchild );
}
deQueue( q );
}
}
建立二叉树
把二维的树形结构转换为一维的线性序列之后,就可以把二叉树的节点信息扔给计算机处理了。
/* 需要输入一个前序遍历的二叉树序列 */
void
createTree( btnode **t )
{
char ch;
scanf("%c", &ch);
if( ch == '#' ){
*t = NULL;
}else{
*t = ( btnode* )malloc( sizeof( btnode ) );
(*t)->data = ch;
createTree( &( (*t)->lchild ) );
createTree( &( (*t)->rchild ) );
}
}
输入样例是一个补齐之后的二叉树先序遍历序列(用#表示空节点)

ABD##EH###CF#I##G##
销毁二叉树
递归调用函数,找到叶子节点之后返回,销毁左子树;再进入另一棵子树,找到叶子节点之后返回,销毁右子树。
对应的递归模型
当
t
=
N
U
L
L
,
不
做
任
何
事
情
其
他
情
况
,
f
(
t
)
=
f
(
b
→
l
c
h
i
l
d
)
;
f
(
b
→
r
c
h
i
l
d
)
;
f
r
e
e
(
t
)
;
\begin{aligned} 当t&=NULL,不做任何事情\\ 其他情况,f(t)&=f(b→lchild);\\ &f(b→rchild);\\ &free(t); \end{aligned}
当t其他情况,f(t)=NULL,不做任何事情=f(b→lchild);f(b→rchild);free(t);
void
destroyTree( btnode *t )
{
if( t == NULL ){
return;
}
destroyTree( t->lchild );
destroyTree( t->rchild );
free( t );
}
输出所有的叶子节点
度为0的节点即为叶子节点,对应到代码上就是左孩子指针和右孩子指针都是NULL,在printf前面加上条件判断即可。
void
PreOrderPrintLeaves( btnode *bt )
{
if( bt == NULL ){
return;
}
if( !bt->lchild && !bt->rchild ){
printf("%c", bt->data);
}
PreOrderPrintLeaves( bt->lchild );
PreOrderPrintLeaves( bt->rchild );
}
中序后序层序都可以完成这个问题的求解。
求高度
整棵树的高度是一个大问题,左子树和右子树的高度是小问题。而整棵树的高度即为在左子树和右子树高度中取一个最大值。
对应的递归模型
当 t = N U L L , f ( t ) = 0 其 他 情 况 , f ( t ) = max { f ( b → l c h i l d ) , f ( b → r c h i l d ) } + 1 \begin{aligned} 当t&=NULL,f(t)=0\\ 其他情况,f(t)&=\max\{f(b→lchild),f(b→rchild)\}+1 \end{aligned} 当t其他情况,f(t)=NULL,f(t)=0=max{f(b→lchild),f(b→rchild)}+1
int
PostOrderGetHeight( btnode *bt )
{
int h_left, h_right;
if( bt == NULL ){
return 0;
}
h_left = PostOrderGetHeight( bt->lchild );
h_right = PostOrderGetHeight( bt->rchild );
return ( h_left > h_right ) ? ( h_left + 1 ) : ( h_right + 1 );
}

浙公网安备 33010602011771号