一、二叉树的概念
二叉树是n(n>=0)个结点的有限集合:
- 可为空二叉树,即n=0;
- 可以是由一个根节点和两个互不相交的被称为根的左子树和右子树组成,左子树和右子树又分别是一颗二叉树。
特点:二叉树为有序树,每个结点至多只有两颗子树(即二叉树中不存在度大于2的结点)且二叉树的子树有左右之分,其次序不能任意颠倒(若左、右子树颠倒,则成为另一颗不同的二叉树)。
二叉树有五种基本形态:
二叉树与度为2的有序树的区别:
- 度为2的树至少有3个结点,而二叉树可以为空;
- 度为2的有序树的孩子的左右次序是相对于另一个孩子而言的,若某个结点只有一个孩子,则这个孩子就无须区分其左右次序;而二叉树无论其孩子数是否为2,都需要确定其左右次序,二叉树的结点次序是确定的。
特殊的二叉树:
- 满二叉树:一颗高度为h,且含有 -1 个结点的二叉树称为满二叉树。满二叉树的叶子结点都集中在二叉树的最下一层,并且除叶子结点之外的每个结点度数均为2。
对满二叉树按层序编号:编号从根结点起(根结点编号为1),自上而下,自左向右。对于编号为 i 的结点,若有双亲,则其双亲为 ,若有做孩子,则左孩子为 2i ,若有右孩子,则右孩子为 2i+1。
- 完全二叉树:高度为 h,有 n 个结点的二叉树,当且仅当其每个结点都与高度为 h 的满二叉树中编号为 1-n 的结点一一对应。
特点:
- 若 i<= ,则结点 i 为分支结点,否则为叶结点;
- 叶结点只可能在层次最大的两层上出现;
- 若有度为1的结点,则只可能只有一个,且该结点只有左孩子而无右孩子;
- 出现某结点(编号为i)为叶结点或只有左孩子,则编号大于 i 的结点均为叶结点;
- 若n为奇数,则每个分支结点均有左孩子和右孩子;若n为偶数,则编号最大的分支结点(编号为 n/2)只有左孩子,没有右孩子,其余分支结点左右孩子均有;
- 二叉排序树:左子树上所有结点的关键字均小于根结点的关键字;右子树上所有结点的关键字均大于根结点的关键字;左子树和右子树又各是一颗二叉树。
- 平衡二叉树:树上任意一个结点的左子树和右子树的深度之差不超过1。
二叉树的性质:
- 非空二叉树上的叶结点数等于度为2的结点数加1,(结点总数 n=分支总数+1;结点总数 = ;结合得 );
- 非空二叉树上第K层上至多有个结点(k>=1);
- 高度为h的二叉树至多有个结点(h>=1);
- 具有n个(n>0)结点的完全二叉树的高度为或。
- 对于完全二叉树,编号从根结点起(根结点编号为1),自上而下,自左向右,依次编号1,2,...,n,有以下关系:
① 当 i>1 时,结点 i 的双亲的编号为 ,即当 i 为偶数时,其双亲的编号为 i/2 ,它双亲的左孩子,当 i 为奇数时,其双亲的编号为 (i-1)/2 ,它双亲的右孩子。
② 当 2i<=n 时,结点 i 的左孩子编号为 2i ,否则无左孩子。
③ 当 2i+1<=n 时,结点 i 的右孩子编号为 2i+1 ,否则无右孩子。
④ 结点 i 所在层次(深度)为。
二叉树的存储结构:
二叉树的存储结构有顺序存储和链式存储。
对于链式存储:n个数据,有2n个指针;除根结点,每个结点都要前驱结点,则n-1个指针不为空,有n+1个空链域。
//二叉树链式存储结构
typedef struct BiTNode
{
ElemType data; //数据域
struct BiTNode *lchild,*rchild; //左、右孩子指针
}BiTNode,*BiTree;
二、二叉树的遍历
二叉树的遍历是指按某条搜索路径访问树中每个结点,使得每个结点均被访问一次,而且仅被访问一次。
先序遍历(PreOrder)
若二叉树为空,则什么也不做;否则:
- 访问根结点;
- 先序遍历左子树;
- 先序遍历右子树。
递归算法:(如上图,先序递归为根->左->右;为第一次经过时访问)
void PreOrder(BiTree T)
{
if(T != NULL)
{
visit(T); //访问根结点
PreOrder(T->lchild); //递归遍历左子树
PreOrder(T->rchild); //递归遍历右子树
}
}
非递归算法:
void PreOreder(BiTree T)
{
InitStack(S);
BiTree p = T; //初始化栈S、p是遍历指针
while(p || !IsEmpty(S) //栈不空或p不空时循环
{
if(p) //一路向左;if(p!=NULL)
{
visit(p); //访问当前结点
Push(S,p); //入栈
p = p -> lchild; //左孩子不空,一路向左走
}
else //出栈,并转向出栈结点的右子树
{
Pop(S,p); //栈顶元素出栈
p = p -> rchild; //向右子树走,p赋值为当前结点的右孩子
} //返回while循环继续进入if-else语句
}
}
中序遍历(InOrder)
若二叉树为空,则什么也不做;否则:
- 中序遍历左子树;
- 访问根结点;
- 中序遍历右子树。
递归算法:(如首图,先序递归为左->根->右;为第二次经过时访问)
void InOrder(BiTree T)
{
if(T != NULL)
{
InOrder(T->lchild); //递归遍历左子树
visit(T); //访问根结点
InOrder(T->rchild); //递归遍历右子树
}
}
非递归算法:(与先序规则相同)
void InOreder(BiTree T)
{
InitStack(S);
BiTree p = T; //初始化栈S、p是遍历指针
while(p || !IsEmpty(S) //栈不空或p不空时循环
{
if(p) //一路向左
{
Push(S,p); //当前结点入栈
p = p -> lchild; //左孩子不空,一路向左走
}
else //出栈,并转向出栈结点的右子树
{
Pop(S,p); //栈顶元素出栈
visit(p); //访问出栈结点
p = p -> rchild; //向右子树走,p赋值为当前结点的右孩子
} //返回while循环继续进入if-else语句
}
}
后序遍历(PostOrder)
若二叉树为空,则什么也不做;否则:
- 后序遍历左子树;
- 后序遍历右子树;
- 访问根结点。
递归算法:(如首图,先序递归为左->右->根;为第三次经过时访问)
void PostOrder(BiTree T)
{
if(T != NULL)
{
InOrder(T->lchild); //递归遍历左子树
InOrder(T->rchild); //递归遍历右子树
visit(T); //访问根结点
}
}
非递归算法:
void PostOrder(BiTree T)
{
InitStack(S);
BiTNode *p = T;
BiTNode *r = NULL;
while(p ||! IsEmpty(S))
{
if(p) //走到左边
{
push(S,p);
p =-> lchild;
}
else //向右
{
GetTop(S,p); //读栈顶结点(非出栈)
if(p -> rchild && p -> rchild != r) //若右子树存在,且未被访问过
p = p -> rchild; //转向右
else //否则弹出结点并访问
{
pop(S,p); //将结点弹出
visit(p -> data); //访问该结点
r = p; //记录最近访问过的结点
p = NULL; //结点访问完后,重置p指针
}
}
}
}
层次遍历
- 初始化一个辅助队列;
- 根结点入队;
- 若队列非空,则队头结点出队,访问该结点,并将其左、右孩子插入队尾(若有的话);
- 重复3直至队列为空。
void LevelOrder(BiTree T)
{
LinkQueue Q; //创建辅助链队列(方便拓展)
InitQueue(Q); //初始化
BiTree P; //定义一个指向二叉树的指针
EnQueue(Q,T); //将根结点入队
while(!IsEmpty(Q))
{
DeQueue(Q,P); //队头结点出队
visit(P); //访问出队结点
if(P -> lchild != NULL)
EnQueue(Q,P -> lchild); //左孩子入队
if(P -> rchild != NULL)
EnQueue(Q,P -> rchild); //右孩子出队
}
}
//二叉树的结点(链式存储)
typedef struct BiTNode
{
char data; //数据域
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
//链式队列结点
typedef struct LinkNode
{
BiTNode *data; //入队时不用保存结点的真实数据,只需保存此结点的指针
struct LinkNode *next;
}LinkNode; //链
typedef struct
{
LinkNode *front,*rear; //队头队尾
}LinkQueue; //队