二叉树(binary tree)基本定义、性质、理解、总结+代码实现
约定:层序编号:二叉树从根结点开始按照从上到下,从左到右的顺序从1开始给结点编号。
二叉树(binary tree)
定义:
一个有限的结点集合。这个集合或者为空,或者由一个根结点和两棵互不相交的称为左子树(subtree)和右子树的二叉树组成。
二叉树是一种特殊的树形数据结构,二叉树的定义也是递归的,这决定了对二叉树的算法设计往往设计递归思想
二叉树并不等于二次树,两者的区别:
二次树必须有一个度为2的结点,而二叉树无要求;
二叉树严格区分左右子树,而二次树无要求。
1. 二叉树的其中两种形态
(1)满(full)二叉树:
所有分支结点都有左右子树,并且叶子结点都集中在最高层。/ 高度为h且有2h-1个结点的二叉树(根据树的性质)。
特点:
所有叶子结点都在最高层;
只有度为0和2的结点。
(2)完全(complete)二叉树:
满足:
1) 最下面两层的结点的度小于等于2;
2) 最下面一层的叶子结点靠左排列。
的二叉树。
特点:
1)所有叶子结点只出现在最下面两层;
2)最下面一层的叶子结点依次排列在最左边;
3)如果有度为1的结点,只可能有一个,并且这个结点只有左孩子;
按层序编号时,一旦出现编号为i的结点是叶子结点或者是只有左孩子的结点,则编号大于i的结点都为叶子结点;
4)当结点总数是奇数时,n1=0,偶数时,n1=1。
两种形态的关系:满二叉树是完全二叉树的特例
2. 二叉树的性质
(1)二叉树的叶子结点数等于双分支结点数加1
证明:
由树的性质得:
n = n0 + n1 + n2(结点总数) = n1 + 2n2 + 1(所有结点度数之和加1 )
推出:n0 = n2 + 1
(2)同树的性质二(m次树第i层的最大结点数)
(3)同树的性质三(由树高h确定树的最大结点数)
(4)完全二叉树中层序编号为i的结点(1<= i <=n , n>=1,n为结点数)
有以下性质:
1) 若i <= (n/2)向下取整,则编号为i的结点为分支结点,否则为叶子结点;
性质1)等价于:完全二叉树中最后一个叶子结点的层序编号n除以2向下取整,将得到最后一个分支结点的层序编号;或者这样理解:某结点的双亲结点的层序编号可由其编号除以2向下取整得到。
2) 若n为奇数,则每个分支结点都有左右孩子,若为偶数,则最大编号分支结点只有左孩子;
奇数的情况:一个根结点 + 偶数个孩子结点(后继结点)
偶数的情况:一个根结点 + 偶数个右孩子结点 + 奇数个左孩子结点
3)编号为i的结点的左孩子结点编号为2i,右孩子编号为2i+1;
4)编号为i的结点的双亲结点编号为(i/2)向下取整
(5)具有n个(n>0)结点的完全二叉树的高度为(log2(n+1))向下取整,或(log2n)向上取整加1
3. 二叉树的存储结构
(1) 顺序存储结构
存储结构实现:一维数组,
结点类型声明实现代码:
#define MaxSize 50
typedef char ElemType;
typedef ElemType SqBTree[MaxSize];
定义并初始化一棵二叉树:
SqBTree btree = {'', 'A', 'B', 'C', 'D'};
为了对应二叉树的层序编号,在C/C++中,数组首元素位置不用,因此根结点在数组中的编号——下标1,对应逻辑结构中的层序编号——1。
适用情况:完全二叉树、满二叉树
不太适用:一般二叉树
因为既要存储结点值又要存储结点之间的关系,因此不能直接按顺序将结点存储到数组中,而要采用方法:先用空结点补全为一棵完全二叉树,然后进行层序编号,再将非空结点存储到对应编号的数组位置上。这会导致的问题是:有些二叉树的形态会浪费较大或极大的存储空间。如一棵树高为h的右单支树,实际结点有h个,但需要2h-1个结点空间存储。
优点:数组下标对应逻辑结构的层序编号,查找某类结点可直接计算得出,如编号为i的结点的孩子结点(2i和2i+1)及双亲结(i/2向下取整)、确定编号为i的结点的所在层数等等。
缺点:顺序存储结构的固有缺陷:不便于插入和删除操作。
总结:顺序存储结构比较适合存储完全二叉树、满二叉树。
(2) 二叉链(binary link list)
二叉链基本概念:用结构体作为二叉树的结点,其中包含data域和左、右孩子指针域,这样一种存储结构
二叉链结点类型声明:
typedef struct node
{
ElemType data;
struct node *lchild;
struct node *rchild;
}BTNode;
结点默认按层序编号的排序,结点之间的逻辑关系用指针域实现。
适用情况:所有形态的二叉树
优点:相对于顺序存储结构,二叉链的空间随情况而分配,具有很好的灵活性及很好的存储空间利用率,因此适用于二叉树的所有形态。
不足(作为链式存储结构):不具备随机存取特性,因为存储单元不是连续分配的,但因为树这种数据结构是递归的,所以大多数情况下可以采用递归算法实现对数据结点的访问。
4. 二叉树ADT
{
数据对象:
D = { ai | 1<=i<=n,n>=0 }
数据关系:
R = { <ai , aj> | 1<= i,j <=n }
基本运算:
CreateBTree(&b);
DestroyBTree(&b);
DispBTree(b);
BTreeHeight(b, n);
LChild(t,p);
RChild(t,p);
Parent(t,p);
……
}
5. 基本运算实现代码
(1)创建二叉树:
//利用栈创建二叉树
void CreateBTree(BTNode * &btree_p, char *str)
{
BTNode *node_stack[50], *node;
int top = -1, index = 0, flag;
btree_p = NULL; //尽量在定义指针时进行指针的初始化
char ch = str[index];
while(ch != '\0')
{
switch(ch)
{
case '(':
top++;
node_stack[top] = node;
flag = 1;
break;
case ')':
top--;
break;
case ',':
flag = 2;
break;
default:
node = (BTNode *)malloc(sizeof(BTNode));
node->data = ch;
node->lchild = node->rchild = NULL; //一定要记得初始化指针域
if(!btree_p)
btree_p = node;
else
{
switch(flag)
{
case 1:
node_stack[top]->lchild = node;
break;
case 2:
node_stack[top]->rchild = node;
break;
}
}
break;
}
index++;
ch = str[index];
}
}
(2)销毁:
//递归销毁二叉树
void DestroyBTree(BTNode * &btree_p)
{
if(btree_p != NULL)
{
DestroyBTree(btree_p->lchild);
DestroyBTree(btree_p->rchild);
free(btree_p);
}
}
(3)输出二叉树(括号表示法形式):
void DispBTree(BTNode *btree_p)
{
if(btree_p != NULL)
{
printf("%c",btree_p->data);
if(btree_p->lchild != NULL
|| btree_p->rchild != NULL)
{
printf("(");
DispBTree(btree_p->lchild);
if(btree_p->rchild)
printf(",");
DispBTree(btree_p->rchild);
printf(")");
}
}
}
(4)前序遍历输出二叉树(中、后序遍历同理):
void PreTravel(BTNode *btree_p)
{
if(btree_p != NULL)
{
printf("%c",btree_p->data);
PreTravel(btree_p->lchild);
PreTravel(btree_p->rchild);
}
#if 0
此乃递归出口,因为函数返回值是void类型,因此可缺省,由编译器默认执行
else
return ;
#endif
}
这里只列出最基本的运算,其他运算的实现就不再一一列出,有需要的朋友可以去我的GitHub上查阅。
最后附上:GitHub源码地址
有问题的朋友欢迎私信交流