在计算机科学中,树形结构是一类重要的非线性数据结构,二叉树是一种重要的树形结构。
二叉树是n个数据的有限集,它或为空集(n=0),或含有唯一的称为根的元素,其余元素分成两个互不相交的子集,每个子集自身也是一颗二叉树,分别称为根的左子树和右子树,集合为空的二叉树简称为空数,二叉树中的元素也称为结点。
二叉树中其左、右子数均为空的结点称之为叶子结点,反之所有非叶子结点称之为分支结点。结点的子数的个数作为结点的度的量度,则叶子结点的度为0,二叉树中结点度数最大为2。二叉树中叶子结点的最大层次数定义为二叉树的深度。
若二叉树中所有的分支结点的度数都为2,且叶子结点都在同一层次上,则称这类二叉树为满二叉树。对满二叉树从上到下从左到右从1开始的编号,则任意一颗二叉树都可以和同深度的满二叉树相对比,假如一颗包含n个结点的二叉树中每个结点都可以和满二叉树中编号为1到n得结点一一对应,则称这类二叉树为完全二叉树。一颗深度为h的完全二叉树中,前h-1层中的结点都是“满”的,且第h层的结点都集中在左边。满二叉树本身也是一颗完全二叉树。
二叉树的几个基本性质:
性质一: 在二叉树的第i层上至多有2 ^ (i - 1) 个结点
性质二: 深度为k的二叉树至多有(2 ^ i) - 1个结点
性质三: 对任何一棵树T,如果其终端(度为0)结点数为n(0),度为2的结点数为 n(2),则n(0)=n(2) + 1
(n = n(0) + n(1) + n(2) n = 分支总数 + 1 分支总数 = n(1) + 2 * n(2) )
性质四: 具有n个结点的完全二叉树的深度为不大于log2(n)+1的整数。
性质五: 如果对一颗有n个结点的完全二叉树的结点按层序从1开始编号,则对任一编 号为 i 的结点,有
(1) 如果 i = 1,则编号为 i 的结点为二叉树的根,无双亲;如果 i > 1,则其双 亲结点的编号是不大于i/2
(2) 如果2 * i >n,则编号为 i 的结点无左孩子,否则其左孩子结点的编号为:2*i
(3) 如果2 * i + 1 >n,则编号为 i 的结点无右孩子,否则其右孩子结点为:2*i+1
二叉树的存储结构:顺序存储结构和链式存储表示
顺序存储结构:
用一组地址连续的存储单元存储二叉树中的数据,但是造成存储空间的很大浪费
链式存储结构:
包含三个域:数据域和分别指向左、右子树的指针域。
二叉树的二叉链表存储表示:
typedef struct BiTNode{
int data;
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
二叉树遍历递归算法:
先序遍历二叉树:
void Preorder(BiTree T,void (*visit)(BiTree)){ //先序遍历以T为根指针的二叉树
if(T -> data){
visit(T -> data) //通过函数指针*visit 访问根结点,以便灵活完成相应的操作
Preorder(T->lchild,visit);
Preorder(T->rchild,visit);
}
}
只要重新安排三个操作的次序就可以得到中序遍历和后序遍历的递归算法。
二叉树遍历非递归算法:
1、用栈来实现先序遍历
void Preorder1(BiTree *root){
Stack S;
while(!S.empty()||root!=NULL){
if(root != NULL){
Visit(root -> data);
S.push(root);
root = root->lchild;
}
else{
root = S.pop();
root = root->rchild;
}
}
}
或者:
void Preorder2(BiTree *root){
if(root != NULL){
stack s;
s.push(root);
while(!s.empty()){
root = s.pop();
Visit(root -> data);
if((root -> rchild) != NULL)
s.push(root->rchild);
if((root -> rchild) != NULL)
s.push(root->lchild);
}
}
}
2、不用栈实现二叉树非递归算法(回溯算法):
bool root -> flag = false;
void Preorder3(BiTree *root){
while(root != NULL){ //根结点设为NULL
if(!root -> flag){
Visit(root -> data);
flag = true;
}
if((root -> lchild) != NULL && !root->lchild->flag)
root = root -> lchild;
else if((root -> rchild) != NULL && !root->rchild->flag)
root = root -> rchild;
else
root = root -> parent;
}
}
中序遍历二叉树(递归算法):
void Preorder(BiTree T,void (*visit)(BiTree)){ //先序遍历以T为根指针的二叉树
if(T -> data){
Preorder(T->lchild,visit);
visit(T -> data); //通过函数指针*visit 访问根结点,以便灵活完成相应的操作
Preorder(T->rchild,visit);
}
}
非递归算法,用栈来实现:
void preorder(BiTree root,void (*visit)(BiTree)){
stack S;
if((root ->data) != NULL || !S.empty()){
if((root ->data) != NULL){
S.push(root ->data);
root = root ->lchild;
}
else{
(root ->data) = S.pop();
visit(root ->data);
root = root ->rchild;
}
}
}
后序遍历二叉树:
void Preorder(BiTree T,void (*visit)(BiTree)){ //先序遍历以T为根指针的二叉树
if(T -> data){
Preorder(T->lchild,visit);
Preorder(T->rchild,visit);
visit(T -> data); //通过函数指针*visit 访问根结点,以便灵活完成相应的操作
}
}
模拟递归,用栈来实现后序遍历二叉树:
void preorder(BiTree root,void (*visit)(BiTree)){
stack s;
if((root ->data) != NULL){
s.push(root ->data);
while(!s.empty()){
root = s.pop();
if(root ->flag)
visit(root ->data);
else{
if((root ->rchild) != NULL){
s.push(root ->rchild);
root ->rchild ->flag = false;
}
if((root ->lchild) != NULL){
s.push(root ->lchild);
root ->rchild ->flag = false;
}
s.push(root);
root ->flag = true;
}
}
}
}
层序遍历:
void preorder(BiTree root,void (*visit)(BiTree)){
queue Q;
if((root) != NULL)
Q.push(root);
while(!Q.empty()){
root = Q.front();
visit(root);
if((root ->lchild) != NULL)
Q.push(root ->lchild);
if((root ->rchild) != NULL)
Q.push(root ->rchild);
}
}
第一个思想,就是跟踪指针移动 用栈保存中间结果的实现方式,先序与中序难度一致,后序很困难。先序与中序只需要修改一下访问的位置即可。
第二个思想,直接用栈来模拟递归,先序非常简单;而中序与后序难度一致。先序简单是因为节点可以直接访问,访问完毕后无需记录。而中序与后序时,节点在弹栈后还不能立即访问,还需要等其他节点访问完毕后才能访问,因此节点需要设置标志位来判定,因此需要额外的O(n)空间。