二叉树的原理及二叉树的三种遍历方式,假设父节点是D,左节点是L,右节点是R:
前序遍历 D->L->R
中序遍历 L->D->R
后序遍历 L->R->D
对于下面的二叉树,遍历结果分别为
前序遍历:ABDECF
中序遍历:DBEACF
后序遍历:DEBFCA
已知二叉树的前序遍历和中序遍历,如何得到它的后序遍历
其实,只要知道其中任意两种遍历的顺序,我们就可以推断出剩下的一种遍历方式的顺序,这里我们只是以:知道前序遍历和中序遍历,推断后序遍历作为例子,其他组合方式原理是一样的。要完成这个任务,我们首先要利用以下几个特性:
特性(1),对于前序遍历,第一个肯定是根节点;
特性(2),对于后序遍历,最后一个肯定是根节点;
特性(3),利用前序或后序遍历,确定根节点,在中序遍历中,根节点的两边就可以分出左子树和右子树;
特性(4),对左子树和右子树分别做前面3点的分析和拆分,相当于做递归,我们就可以重建出完整的二叉树;
如图,例子来说明。知道中序和后序遍历,画二叉树和写出前序遍历。
后续遍历知道A是根,C是A的右子树。HDMIBJE是A的左子树部分,FKCG是右子树部分。B则是A的左子树。
接着看A的右子树部分KFGC,C是根,从中序遍历知,FK是C的左子树部分,G是右子树。再结合后续遍历知,F是C的左子树,K是F的右子树。
如图所示,此时A的右子树部分全部出来了。
再看,A的左子树部分HDMIBJE,B是A的左子树。则HDMI是B的左子树部分,JNE是B的右子树部分。后续遍历看出E是B的右子树,在结合中序遍历看出JN是E的左子树部分,E无右子树。分析出J是E的左子树,N是J的右子树,J无左子树。
紧接着就是看B的左子树部分HDMI,从后续遍历中可以看出D是B的左子树,结合前序遍历知道H是D的左子树,MI是D的右子树部分。再通过分析可知,I是B的右子树,M是I的左子树,I无右子树。
上面已经画出二叉树,然后根据二叉树的前序遍历方法写出为:ABDHIMEJNCFKG。
代码验证
#include
#include
#include
typedef char TElemType;
#define MAX_TREE_SIZE 100
typedef struct BiTNode //结点结构
{
TElemType data; //结点数据
struct BiTNode *lchild, *rchild; //左右孩子指针
} BiTNode, *BiTree;
//建树并前序遍历
void BinaryTreeFromOrderings(char *mid, char *last, int len)
{
//长度为0则返回空
if (len == 0)
{
return ;
}
BiTree node = (BiTree)malloc(sizeof(BiTNode)); //分配结点空间
node->data = last[len - 1];
//mid中寻找根结点
int rootIndex = 0;
for (; rootIndex < len; rootIndex++)
{
if (mid[rootIndex] == last[len - 1])
{
break;
}
}
//前序遍历,则在建一个节点打印一次
printf("%c", node->data);
//Left
BinaryTreeFromOrderings(mid, last, rootIndex);
//Right
BinaryTreeFromOrderings(mid + rootIndex + 1, last + rootIndex, len - (rootIndex + 1)); //后段为右子树
return ;
}
int main(int argc, const char * argv[])
{
char mid[100], last[100], len;
scanf("%s %s", mid, last);
len = (int)strlen(mid);
BinaryTreeFromOrderings(mid, last, len);
printf("\n");
return 0;
}
二叉树的前序、中序、后序遍历及互相求法
一、基本概念
性质:
1、非空二叉树的第n层上至多有2^(n-1)个元素。
2、深度为h的二叉树至多有2^h-1个结点。
满二叉树:所有终端都在同一层次,且非终端结点的度数为2。
在满二叉树中若其深度为h,则其所包含的结点数必为2^h-1。
完全二叉树:除了最大的层次即成为一颗满二叉树且层次最大那层所有的结点均向左靠齐,即集中在左面的位置上,不能有空位置。
对于完全二叉树,设一个结点为i则其父节点为i/2,2i为左子节点,2i+1为右子节点。
二、存储结构顺序存储:将数据结构存在一块固定的数组中。
#define LENGTH 100
typedef char datatype;
typedef struct node{
datatype data;
int lchild,rchild;
int parent;
}Node;
Node tree[LENGTH];
int length;
int root;
虽然在遍历速度上有一定的优势,但因所占空间比较大,是非主流二叉树。二叉树通常以链式存储。
typedef char datatype;
typedef struct BinNode{
datatype data;
struct BinNode* lchild;
struct BinNode* rchild;
}BinNode;
typedef BinNode* bintree; //bintree本身是个指向结点的指针
三、二叉树的遍历
遍历即将树的所有结点访问且仅访问一次。按照根节点位置的不同分为前序遍历,中序遍历,后序遍历。
前序遍历:根节点->左子树->右子树
中序遍历:左子树->根节点->右子树
后序遍历:左子树->右子树->根节点
例如:求下面树的三种遍历
前序遍历:abdefgc
中序遍历:debgfac
后序遍历:edgfbca
一个例子:
假设某二叉树的先序遍历序列是abdgcefh,中序遍历序列是dgbaechf,画出二叉树,并给出其后序遍历序列.
以下面的例题为例进行讲
已知一棵二叉树的先序遍历序列和中序遍历序列分别是abdgcefh、dgbaechf,求二叉树及后序遍历序列.
分析:先序遍历序列的第一个字符为根结点.对于中序遍历,根结点在中序遍历序列的中间,左边部分是根结点的左子树的中序遍历序列,右边部分是根结点的右子树的中序遍历序列.
先序:abdgcefh --> a bdg cefh
中序:dgbaechf --> dgb a echf
得出结论:a是树根,a有左子树和右子树,左子树有bdg结点,右子树有cefh结点.
先序:bdg --> b dg
中序:dgb --> dg b
得出结论:b是左子树的根结点,b无右子树,有左子树.
先序:dg --> d g
中序:dg --> d g
得出结论:d是b的左子树的根结点,d无左子树,有右子树.
先序:cefh --> c e fh
中序:echf --> e c hf
得出结论:c是右子树的根结点,c有左子树(只有e结点),有右子树(有fh结点).
先序:fh --> f h
中序:hf --> h f
得出结论:f是c的左子树的根结点,f有左子树(只有h结点),无右子树.
还原二叉树为:
a
b c
d e f
g h
后序遍历序列:gdbehfca
四、遍历的实现递归实现(以前序遍历为例,其他的只是输出的位置稍有不同)
void preorder(bintree t){
if(t){
printf("%c ",t->data);
preorder(t->lchild);
preorder(t->rchild);
}
}
非递归的实现
因为当遍历过根节点之后还要回来,所以必须将其存起来。考虑到后进先出的特点,选用栈存储。数量确定,以顺序栈存储。
#define SIZE 100
typedef struct seqstack{
bintree data[SIZE];
int tag[SIZE]; //为后续遍历准备的
int top; //top为数组的下标
}seqstack;
void push(seqstack *s,bintree t){
if(s->top == SIZE){
printf("the stack is full\n");
}else{
s->top++;
s->data[s->top]=t;
}
}
bintree pop(seqstack *s){
if(s->top == -1){
return NULL;
}else{
s->top--;
return s->data[s->top+1];
}
}
1、前序遍历
void preorder_dev(bintree t){
seqstack s;
s.top = -1; //因为top在这里表示了数组中的位置,所以空为-1
if(!t){
printf("the tree is empty\n");
}else{
while(t || s.stop != -1){
while(t){ //只要结点不为空就应该入栈保存,与其左右结点无关
printf("%c ",t->data);
push(&s,t);
t= t->lchild;
}
t=pop(&s);
t=t->rchild;
}
}
}
2、中序遍历
void midorder(bintree t){
seqstack s;
s.top = -1;
if(!t){
printf("the tree is empty!\n");
}else{
while(t ||s.top != -1){
while(t){
push(&s,t);
t= t->lchild;
}
t=pop(&s);
printf("%c ",t->data);
t=t->rchild;
}
}
}
3、后序遍历
因为后序遍历最后还要要访问根结点一次,所以要访问根结点两次。采取夹标志位的方法解决这个问题。
这段代码非常纠结,对自己有信心的朋友可以尝试独立写一下。反正我是写了很长时间。逻辑不难,我画了一张逻辑图:
void postorder_dev(bintree t){
seqstack s;
s.top = -1;
if(!t){
printf("the tree is empty!\n");
}else{
while(t || s.top != -1){ //栈空了的同时t也为空。
while(t){
push(&s,t);
s.tag[s.top] = 0; //设置访问标记,0为第一次访问,1为第二次访问
t= t->lchild;
}
if(s.tag[s.top] == 0){ //第一次访问时,转向同层右结点
t= s.data[s.top]; //左走到底时t是为空的,必须有这步!
s.tag[s.top]=1;
t=t->rchild;
}else {
while (s.tag[s.top] == 1){ //找到栈中下一个第一次访问的结点,退出循环时并没有pop所以为其左子结点
t = pop(&s);
printf("%c ",t->data);
}
t = NULL; //必须将t置空。跳过向左走,直接向右走
}
}
}
}
4、层次遍历:即每一层从左向右输出
元素需要储存有先进先出的特性,所以选用队列存储。
队列的定义:
#define MAX 1000
typedef struct seqqueue{
bintree data[MAX];
int front;
int rear;
}seqqueue;
void enter(seqqueue *q,bintree t){
if(q->rear == MAX){
printf("the queue is full!\n");
}else{
q->data[q->rear] = t;
q->rear++;
}
}
bintree del(seqqueue *q){
if(q->front == q->rear){
return NULL;
}else{
q->front++;
return q->data[q->front-1];
}
}
遍历实现
void level_tree(bintree t){
seqqueue q;
bintree temp;
q.front = q.rear = 0;
if(!t){
printf("the tree is empty\n");
return ;
}
enter(&q,t);
while(q.front != q.rear){
t=del(&q);
printf("%c ",t->data);
if(t->lchild){
enter(&q,t->lchild);
}
if(t->rchild){
enter(&q,t->rchild);
}
}
}
5、利用前序遍历的结果生成二叉树
//递归调用,不存点,想的时候只关注于一个点,因为还会回来的,不要跟踪程序运行,否则容易多加循环
void createtree(bintree *t){
datatype c;
if((c=getchar()) == '#')
*t = NULL;
else{
*t = (bintree)malloc(sizeof(BinNode));
(*t)->data = c;
createtree(&(*t)->lchild);
createtree(&(*t)->rchild);
}
}
6、二叉树的查找
bintree search_tree(bintree t,datatype x){
if(!t){
return NULL;
}
if(t->data == x){
return t;
}else{
if(!search_tree(t->lchild,x)){
return search_tree(t->rchild,x);
}
return t;
}
}
7、统计结点个数
int count_tree(bintree t){
if(t){
return (count_tree(t->lchild)+count_tree(t->rchild)+1);
}
return 0;
}
8、比较两个树是否相同
int is_equal(bintree t1,bintree t2){
if(!t1 && !t2){ //都为空就相等
return 1;
}
if(t1 && t2 && t1->data == t2->data){ //有一个为空或数据不同就不判断了
if(is_equal(t1->lchild,t2->lchild))
if(is_equal(t1->rchild,t2->rchild)){
return 1;
}
}
return 0;
}
9、求二叉树的深度
int hight_tree(bintree t){
int h,left,right;
if(!t){
return 0;
}
left = hight_tree(t->lchild);
right = hight_tree(t->rchild);
h = (left>right?left:right)+1;
return h;
}
大家要注意,如果是知道前序遍历和后续遍历,是无法确定唯一的二叉树的!
牛刀小试
已知中序遍历:ACQVLCOJYPRKSXG
后序遍历:AVQCOCJLRSKPGXY
画出二叉树,并写出前序遍历。