二叉树
一 二叉树的存储结构
1.1 顺序存储结构
#define MAX_SIZE 100
typedef telemtype sqbitree[MAX_SIZE];
1.2 链式存储结构
typedef struct BTNode
{
ElemType data ;
struct BTNode *Lchild , *Rchild;
}BTNode;
typedef struct BTNode_3
{
ElemType data ;
struct BTNode_3 *Lchild , *Rchild , *parent;
}BTNode_3;
二 遍历二叉树及其应用
2.1 DLR——先(根)序遍历
2.1.1 递归算法
算法的递归定义是:
若二叉树为空,则遍历结束;否则
【1】 访问根结点;
【2】先序遍历左子树(递归调用本算法);
【3】先序遍历右子树(递归调用本算法)。
void PreorderTraverse(BTNode *T)
{
if(T!=NULL)
{
visit(T->data) ; //访问根结点
PreorderTraverse(T->Lchild);
PreorderTraverse(T->Rchild);
}
}
说明:visit()函数是访问结点的数据域,其要求视具体问题而定。树采用二叉链表的存储结构,用指针变量T来指向。
2.1.2 非递归算法
设T是指向二叉树根结点的指针变量,非递归算法是:
若二叉树为空,则返回;否则,令p=T;
【1】访问p所指向的结点;
【2】q=p->Rchild ,若q不为空,则q进栈;
【3】p=p->Lchild ,若p不为空,转(1),否则转(4);
【4】退栈到p ,转(1),直到栈空为止。
#define MAX_NODE 50
void PreorderTraverse(BTNode *T)
{
BTNode *Stack[MAX_NODE],*p=T, *q;
int top=0;
if(T==NULL)
printf(“ Binary Tree is Empty!\n”);
else
{
do
{
visit(p-> data);
q = p->Rchild;
if(q!=NULL )
stack[++top]=q;
p = p->Lchild;
if (p==NULL)
{
p = stack[top];
top--;
}
}while(p!=NULL);
}
}
2.2 LDR——中(根)序遍历
2.2.1 递归算法
算法的递归定义是:
若二叉树为空,则遍历结束;否则
【1】中序遍历左子树(递归调用本算法);
【2】访问根结点;
【3】 中序遍历右子树(递归调用本算法)。
void InorderTraverse(BTNode *T)
{
if (T!=NULL)
{
InorderTraverse(T->Lchild);
visit(T->data); //访问根结点
InorderTraverse(T->Rchild);
}
}
2.1.2 非递归算法
设T是指向二叉树根结点的指针变量,非递归算法是:
若二叉树为空,则返回;否则,令p=T
【1】若p不为空,p进栈, p=p->Lchild ;
【2】否则(即p为空),退栈到p,访问p所指向的结点;
【3】p=p->Rchild ,转(1);
直到栈空为止。
#define MAX_NODE 50
void InorderTraverse(BTNode *T)
{
BTNode *Stack[MAX_NODE], *p = T;
int top=0, bool=1;
if(T==NULL)
printf(“ Binary Tree is Empty!\n”);
else
{
do
{
while (p!=NULL)
{
stack[++top] = p;
p=p->Lchild;
}
if(top == 0)
bool = 0;
else
{
p = stack[top];
top-- ;
visit(p->data);
p = p->Rchild;
}
}while(bool!=0);
}
}
2.3 LRD——后(根)序遍历
2.3.1 递归算法
算法的递归定义是:
若二叉树为空,则遍历结束;否则
⑴ 后序遍历左子树(递归调用本算法);
⑵ 后序遍历右子树(递归调用本算法) ;
⑶ 访问根结点 。
void PostorderTraverse(BTNode *T)
{
if (T!=NULL)
{
PostorderTraverse(T->Lchild) ;
PostorderTraverse(T->Rchild) ;
visit(T->data) ; //访问根结点
}
}
2.3.2 非递归算法
在后序遍历中,根结点是最后被访问的。因此,在遍历过程中,当搜索指针指向某一根结点时,不能立即访问,而要先遍历其左子树,此时根结点进栈。当其左子树遍历完后再搜索到该根结点时,还是不能访问,还需遍历其右子树。所以,此根结点还需再次进栈,当其右子树遍历完后再退栈到到该根结点时,才能被访问。
因此,设立一个状态标志变量tag :0 = 结点暂不能访问,1 = 结点可以被访问。
其次,设两个堆栈S1、S2 ,S1保存结点,S2保存结点的状态标志变量tag 。S1和S2共用一个栈顶指针。
设T是指向根结点的指针变量,非递归算法是:
若二叉树为空,则返回;否则,令p=T;
【1】第一次经过根结点p,不访问:
p进栈S1 , tag 赋值0,进栈S2,p=p->Lchild 。
【2】若p不为空,转(1),否则,取状态标志值tag :
【3】若tag=0:对栈S1,不访问,不出栈;修改S2栈顶元素值(tag赋值1) ,取S1栈顶元素的右子树,即p=S1[top]->Rchild ,转(1);
【4】若tag=1:S1退栈,访问该结点;
直到栈空为止。
#define MAX_NODE 50
void PostorderTraverse(BTNode *T)
{
BTNode *S1[MAX_NODE], *p=T;
int S2[MAX_NODE], top = 0, bool = 1;
if (T==NULL)
printf(“Binary Tree is Empty!\n”);
else{
do
{
while(p !=NULL)
{
S1[++top] = p;
S2[top] = 0;
p = p->Lchild;
}
if(top == 0)
bool = 0;
else if(S2[top] == 0)
{
p=S1[top]->Rchild ;
S2[top]=1;
}
else
{
p = S1[top];
top-- ;
visit(p->data);
p=NULL ; //使循环继续进行而不至于死循环
}
}while(bool != 0);
}
}
2.4 层次遍历二叉树
层次遍历二叉树,是从根结点开始遍历,按层次次序“自上而下,从左至右”访问树中的各结点。
为保证是按层次遍历,必须设置一个队列,初始化时为空。
设T是指向根结点的指针变量,层次遍历非递归算法是:
若二叉树为空,则返回;否则,令p=T,p入队;
【1】队首元素出队到p;
【2】访问p所指向的结点;
【3】将p所指向的结点的左、右子结点依次入队。直到队空为止。
#define MAX_NODE 50
void LevelorderTraverse( BTNode *T)
{
BTNode *Queue[MAX_NODE], *p = T;
int front=0, rear=0;
if(p!=NULL)
{
Queue[++rear]=p; //根结点入队
while (front<rear)
{
p=Queue[++front];
visit( p->data );
if(p->Lchild != NULL)
Queue[++rear]=p; //左结点入队
if(p->Rchild!=NULL)
Queue[++rear] = p; //左结点入队
}
}
}
三 二叉树遍历算法的应用
3.1 二叉树的二叉链表创建
3.1.1 按满二叉树方式建立
在此补充按满二叉树的方式对结点进行编号建立链式二叉树。对每个结点,输入i:结点编号,按从小到大的顺序输入、ch:结点内容,假设是字符。
在建立过程中借助一个一维数组S[n] ,编号为i的结点保存在S[i]中。
#define MAX_NODE 50
typedef struct BTNode
{
char data ;
struct BTNode *Lchild , *Rchild ;
}BTNode;
/* 建立链式二叉树,返回指向根结点的指针变量*/
BTNode *Create_BTree(void)
{
BTNode *T, *p, *s[MAX_NODE];
char ch;
int i, j;
while(1)
{
scanf(“%d”, &i) ;
if(i==0)
break ; //以编号0作为输入结束
else
{
ch = getchar();
p=(BTNode *)malloc(sizeof(BTNode));
p–>data=ch ;
p–>Lchild=p–>Rchild=NULL ;
s[i]=p;
if(i == 1)
T=p ;
else
{
j=i/2 ; //j是i的双亲结点编号
if (i%2==0)
s[j]->Lchild=p ;
else
s[j]->Rchild=p ;
}
}
}
return T;
}
3.1.2 按先序遍历方式建立
二叉树的扩充方法是:
在二叉树中结点的每一个空链域处增加一个扩充的结点(总是叶子结点,用方框“□”表示)。
对于二叉树的结点值:
- 是char类型:扩充结点值为“?”;
- 是int类型:扩充结点值为0或-1;
下面的算法是二叉树的前序创建的递归算法,读入一棵二叉树对应的扩充二叉树的前序遍历的结点值序列。每读入一个结点值就进行分析:
- 若是扩充结点值:令根指针为NULL;
- 若是(正常)结点值:动态地为根指针分配一个结点,将该值赋给根结点,然后递归地创建根的左子树和右子树。
#define NULLKY ‘?’
#define MAX_NODE 50
typedef struct BTNode
{
char data;
struct BTNode *Lchild, *Rchild ;
}BTNode;
/* 建立链式二叉树,返回指向根结点的指针变量 */
BTNode *Preorder_Create_BTree(BTNode *T)
{
char ch;
ch=getchar();
getchar();
if(ch==NULLKY)
{
T=NULL;
return(T);
}
else
{
T=(BTNode *)malloc(sizeof(BTNode));
T–>data=ch;
Preorder_Create_BTree(T->Lchild);
Preorder_Create_BTree(T->Rchild);
return(T);
}
}
3.2 求二叉树的叶子结点数
可以直接利用先序遍历二叉树算法求二叉树的叶子结点数。只要将先序遍历二叉树算法中vist()函数简单地进行修改就可以。
#define MAX_NODE 50
int search_leaves( BTNode *T)
{
BTNode *Stack[MAX_NODE], *p = T;
int top=0, num=0;
if(T!=NULL)
{
stack[++top]=p;
while (top>0)
{
p=stack[top--];
if(p->Lchild==NULL&&p->Rchild==NULL)
num++;
if(p->Rchild!=NULL )
stack[++top] = p->Rchild;
if(p->Lchild != NULL)
stack[++top]=p->Lchild;
}
}
return(num);
}
3.3 求二叉树的深度
利用层次遍历算法可以直接求得二叉树的深度。
#define MAX_NODE 50
int search_depth(BTNode *T)
{
BTNode *Stack[MAX_NODE] ,*p=T;
int front=0, rear=0, depth=0, level;//level总是指向访问层的最后一个结点在队列的位置
if(T!=NULL)
{
Queue[++rear]=p; //根结点入队
level=rear ; //根是第1层的最后一个节点
while(front<rear)
{
p=Queue[++front];
if(p->Lchild!=NULL)
Queue[++rear]=p; //左结点入队
if(p->Rchild!=NULL)
Queue[++rear]=p; //左结点入队
if(front == level) //正访问的是当前层的最后一个结点
{
depth++;
level=rear;
}
}
}
}
四 线索树
Ltag:
-
0:Lchild域指示结点的左孩子
-
1:Lchild域指示结点的前驱
Rtag:
- 0:Rchild域指示结点的右孩子
- 1:Rchild域指示结点的后继
typedef struct BiThrNode{
ElemType data;
struct BiTreeNode *Lchild, *Rchild;
int Ltag, Rtag;
}BiThrNode;
4.1 线索化二叉树
仿照线性表的存储结构,在二叉树的线索链表上也添加一个头结点head,头结点的指针域的安排是:
- Lchild域:指向二叉树的根结点;
- Rchild域:指向中序遍历时的最后一个结点;
- 二叉树中序序列中的第一个结点Lchild指针域和最后一个结点Rchild指针域均指向头结点head。
#define MAX_NODE 50
/* Link=0表示指针, Thread=1表示线索 */
typedef enmu{
Link,
Thread
}PointerTag;
typedef struct BiThrNode{
ElemType data;
struct BiTreeNode *Lchild, *Rchild;
PointerTag Ltag, Rtag;
}BiThrNode;
4.1.1 先序线索化二叉树
void preorder_Threading(BiThrNode *T)
{
BiThrNode *stack[MAX_NODE];
BiThrNode *last=NULL, *p ;
int top=0 ;
if (T!=NULL)
{
stack[++top]=T;
while (top>0)
{
p=stack[top--] ;
if(p->Lchild!=NULL) p->Ltag=0 ;
else
{
p->Ltag=1;
p->Lchild!=last;
}
if(last != NULL)
if(last->Rchild != NULL)
last->Rtag = 0;
else
{
last->Rtag = 1;
last->Rchild = p;
}
last = p;
if(p->Rchild!=NULL)
stack[++top]=p->Rchild;
if (p->Lchild!=NULL)
stack[++top]=p->Lchild;
}
Last->Rtag = 1;//最后一个结点是叶子结点
}
}
4.1.2 中序线索化二叉树
void inorder_Threading(BiThrNode *T)
{
BiThrNode *stack[MAX_NODE];
BiThrNode *last = NULL, *p=T;
int top=0 ;
while(p!=NULL||top>0)
if(p!=NULL)
{
stack[++top]=p;
p=p->Lchild;
}
else
{
p=stack[top--] ;
if(p->Lchild!=NULL)
p->Ltag=0 ;
else
{
p->Ltag=1;
p->Lchild=last;
}
if (last!=NULL)
if (last->Rchild!=NULL)
last->Rtag=0 ;
else
{
last->Rtag=1;
last->Rchild=p;
}
last=p ;
P = p->Rchild;
}
last->Rtag=1; //最后一个结点是叶子结点
}
4.2 线索二叉树的遍历
4.2.1 先序线索二叉树的先序遍历
void preorder_Thread_bt(BiThrNode *T)
{
BiThrNode *p=T ;
while (p!=NULL)
{
visit(p->data);
if (p->Ltag == 0)
p=p->Lchild;
else
p=p->Rchild;
}
}
4.2.2 中序线索二叉树的中序遍历
void inorder_Thread_bt(BiThrNode *T)
{
BiThrNode *p;
if(T!=NULL)
{
p = T;
while(p->Ltag == 0)
p=p->Lchild; //寻找最左的结点
while (p!=NULL)
{
visit(p->data) ;
if(p->Rtag == 1)
p=p->Rchild; //通过右线索找到后继
else //否则,右子树的最左结点为后继
{
p=p->Rchild;
while(p->Ltag==0)
p=p->Lchild;
}
}
}
}