数据结构实验八 二叉树

时间:2021-05-20 17:31:13

这篇blog 前面是基础实验,后面二叉树详解,由红色字体加粗显示

包括各种遍历(递归与非递归)

以及已知两种序列求第三种序列


头文件

/********************************/
/*** 建立头文件bintree.h **/
/*******************************/

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<algorithm>
using namespace std;

#define N 100
extern char *a; /*存放扩充二叉树的前序序列*/
typedef struct node /*二叉树结构定义*/
{
char data;
struct node *lchild,*rchild;
}binnode;
typedef binnode *bintree;

/*根据扩充二叉树的前序序列(字符串a)建立二叉树t的存储结构*/
bintree creatbintree()
{
char ch=*a++;
bintree t;
if(ch=='#')
t=NULL;
else
{
t=(bintree)malloc(sizeof(binnode));
t->data=ch;
t->lchild=creatbintree();
t->rchild=creatbintree();
}
return t;
}

void preorder(bintree t) /*前序递归遍历二叉树*/
{
if(t)
{
printf("%c",t->data);
preorder(t->lchild);
preorder(t->rchild);
}
}
void postorder(bintree t) /*后序递归遍历二叉树*/
{
if(t)
{
postorder(t->lchild);
postorder(t->rchild);
printf("%c",t->data);
}
}

/*顺序栈定义*/
typedef struct
{
bintree data[N];
int top;
}seqstack;

void init(seqstack *s) /*初始化空栈*/
{
s->top=-1;
}

int empty(seqstack *s) /*判断栈是否为空*/
{
if(s->top!=-1)
return 0;
else
return 1;
}

int full(seqstack *s) /*判断栈是否为满*/
{
if(s->top==N-1)
return 1;
else
return 0;
}

void push(seqstack *s,bintree x) /*进栈*/
{
if(!full(s))
s->data[++s->top]=x;
}

bintree pop(seqstack *s)/*出栈*/
{
if(!empty(s))
return s->data[s->top--];
}


非递归前序遍历

/*
实现二叉树t的非递归前序遍历
*/

#include"bintree.h"
char *a="ABC##D#E##F##"; /*扩充二叉树序树t的前序序列*/

void preorder1(bintree t)
{
seqstack BiTree;
init(&BiTree);
bintree p=t;

while(p||!empty(&BiTree))
{
while(p)
{
push(&BiTree,p);
printf("%c ",p->data);
p=p->lchild;
}

p=pop(&BiTree);
p=p->rchild;
}
}

int main()
{
bintree t;
t=creatbintree(); /*建立二叉树t的存储结构*/
printf("二叉树的前序序列为:\n");
preorder1(t); /*前序非递归遍历二叉树*/
return 0;
}



层次遍历递归算法

/*
实现二叉树的层次遍历
*/

#include"bintree.h"
char *a="ABC##D#E##F##"; /*扩充二叉树序树t的前序序列*/
void levelbintree(bintree t,int level)
{
if(!t||level<0)
return ;
if(level==1){
printf("%c ",t->data);
return;
}

levelbintree(t->lchild,level-1);
levelbintree(t->rchild,level-1);
}
int main()
{
bintree t;
t=creatbintree(); /*建立二叉树t的存储结构*/
printf("二叉树的层次序列为:\n");
for(int i=1;i<=4;i++){
levelbintree(t,i); /*层次遍历二叉树*/
puts("");
}
return 0;
}


分别返回二叉树t在前序遍历的最后一个结点地址和后序遍历下的第一个结点地址

/*
分别返回二叉树t在前序遍历的最后一个结点地址
和后序遍历下的第一个结点地址
*/

#include"bintree.h"
char *a="ABC##D##EF#G###";  /*扩充二叉树序树t的前序序列*/

bintree prelast(bintree t)
{
    seqstack BiTree;
    init(&BiTree);
    bintree p=t;
    bintree ans;

    while(p||!empty(&BiTree))
    {
        while(p)
        {
            push(&BiTree,p);
            ans=p;
            p=p->lchild;
        }

        p=pop(&BiTree);
        p=p->rchild;
    }
    return ans;
}
bintree postfirst(bintree t)
{
    bintree p=t;
    bintree ans;
    while(p)
    {
        ans=p;
        p=p->lchild;
    }
    return ans;
}

int main()
{
    bintree t,p,q;
    t=creatbintree();       /*建立二叉树t的存储结构*/
    p=prelast(t);
    q=postfirst(t);
    if(t!=NULL)
    {
        printf("前序遍历最后一个结点为:%c\n",p->data);
        printf("后序遍历第一个结点为:%c\n",q->data);
    }
    else
        printf("二叉树为空!");
    return 0;
}



求某节点在二叉树中的层次

/*
二叉树采用链式方式存储,t为其根结点,求x的结点在二叉树中的层次
*/
#include"bintree.h"
char *a="ABC##D##EF#G###"; /*扩充二叉树序树t的前序序列*/

int visit[100];
int Depth(bintree t,char x)
{
seqstack seq;
init(&seq);
bintree p=t;

int ans=1;

while(p||!empty(&seq))
{
while(p)
{
visit[p->data-'A']=ans;
if(p->data==x)
return visit[p->data-'A'];
push(&seq,p);
p=p->lchild;

ans++;
}


p=pop(&seq);
ans=visit[p->data-'A'];
p=p->rchild;
if(p)
ans++;
}
return ans;
}

int main()
{
int k;
char x;
bintree root;
root=creatbintree();
printf("请输入树中的1个结点值:\n");
scanf("%c",&x);
printf("%c结点的层次为%d\n",x,Depth(root,x));
return 0;
}

将一棵给定二叉树中所有结点的左、右子女互换

/*
将一棵给定二叉树中所有结点的左、右子女互换
*/
#include"bintree.h"
char *a="ABC##D##EF#G###"; /*扩充二叉树序树t的前序序列*/

void change(bintree t)
{
bintree p=t;
seqstack seq;
init(&seq);

while(p||!empty(&seq))
{
while(p)
{
bintree temp=p->lchild;
p->lchild=p->rchild;
p->rchild=temp;

push(&seq,p);
p=p->lchild;
}
p=pop(&seq);
p=p->rchild;
}
}
int main()
{
bintree root;
root=creatbintree();
change(root);
preorder(root);
}


根据前序和中序求后序

(见本blog尾,有对此的解析)

/*
根据二叉树的前序序列pre、中序序列mid和序列长度length
构造二叉树的二叉链表存储结构
*/

#include"bintree.h"
#include<string.h>
char *a="";
char pre[100],mid[100];

/*
* i: 子树的前序序列字符串的首字符在pre[]中的下标
* j: 子树的中序序列字符串的首字符在mid[]中的下标
* len: 子树的字符串序列的长度
*/

void PreMidCreateTree(bintree &pn,int i,int j,int len)
{
if(len<=0){
pn=NULL;
return;
}

pn=(bintree)malloc(sizeof(binnode));
pn->data=pre[i];
int temp=strchr(mid,pre[i])-mid;///字符第一次出现在mid中的位置
PreMidCreateTree(pn->lchild,i+1,j,temp-j); ///temp-j为左子树字符串长度
PreMidCreateTree(pn->rchild,i+(temp-j)+1,temp+1,len-1-(temp-j)); ///len-1-(temp-j)为右子树字符串长度
}

int main()
{
freopen("in.txt","r",stdin);
puts("请输入前序序列:");
gets(pre);
puts("请输入中序序列:");
gets(mid);

bintree root;
PreMidCreateTree(root,0,0,strlen(pre));
puts("后序序列是:");
postorder(root);
}





二叉树详解:

下面部分借鉴别人而总结出比较好理解,个人觉得比较好运用的代码


遍历即将树的所有结点访问且仅访问一次。按照根节点位置的不同分为前序遍历,中序遍历,后序遍历。

前序遍历:根节点->左子树->右子树

中序遍历:左子树->根节点->右子树

后序遍历:左子树->右子树->根节点

例如:求下面树的三种遍历

 数据结构实验八 二叉树

前序遍历:abdefgc

中序遍历:debgfac

后序遍历:edgfbca




储存结构:


顺序存储:


typedef char datatype;  
typedef struct node{
datatype data;
int lchild,rchild;
int parent;
}Node;


虽然在遍历速度上有一定的优势,但因所占空间比较大,是非主流二叉树。二叉树通常以链式存储。


链式存储:

typedef char datatype;  

typedef struct BinNode{
datatype data;
struct BinNode* lchild;
struct BinNode* rchild;
}BinNode;

typedef BinNode* bintree; //bintree本身是个指向结点的指针


遍历:

递归遍历:

前序遍历

void PreOrder(BiTree T){  
    if(T){  
        //访问父亲  
        printf("%c",T->data);
        //访问左子儿子
        PreOrder(T->lchild);
        //访问右儿子
        PreOrder(T->rchild);
    }
}


中序遍历


  void InOrder(BiTree T){  
    if(T){
        //访问左子结点  
        InOrder(T->lchild);  
        //访问父亲
        printf("%c",T->data);
        //访问友儿子 
        InOrder(T->rchild);
    }
}



后序遍历

 void PostOrder(BiTree T){
    if(T){
        //访问左儿子
        PostOrder(T->lchild);
        //访问右儿子
        PostOrder(T->rchild);
        //访问父亲
        printf("%c",T->data);
    }
}



非递归遍历:



前序遍历

思路:
访问T->data后,将T入栈
遍历左子树;
遍历完左子树返回时,栈顶元素应为T,
出栈,再先序遍历T的右子树

void PreOrder2(BiTree T){
stack<BiTree>stack;
BiTree p=T;

while(p||!stack.empty()){
while(p)
stack.push(p);
printf("%c ",p->data);
p=p->lchild;
}

p=stack.top();
stack.pop();
p=p->rchild;
}
}


思路:
中序遍历要求在遍历完左子树后
访问根,再遍历右子树
先将T入栈,遍历左子树;
遍历完左子树返回时,栈顶元素应为T,出栈,
访问T->data,再中序遍历T的右子树中序遍历

void InOrder2(BiTree T){
stack<BiTree> stack;
BiTree p=T;
while(p||!stack.empty()){
while(p){
stack.push(p);
p = p->lchild;
}
p=stack.top();
printf("%c ",p->data)
stack.pop();
p=p->rchild;
}
}



后序遍历


数据结构实验八 二叉树

对于任一结点P,将其入栈,然后沿其左子树一直往下搜索,直到搜索到没有左孩子的结点,此时该结点出现在栈顶,但是此时不能将其出栈并访问,因此其右孩子还为被访问。所以接下来按照相同的规则对其右子树进行相同的处理,当访问完其右孩子时,该结点又出现在栈顶,此时可以将其出栈并访问。这样就保证了正确的访问顺序。可以看出,在这个过程中,每个结点都两次出现在栈顶,只有在第二次出现在栈顶时,才能访问它。因此需要多设置一个变量标识该结点是否是第一次出现在栈顶


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置空。跳过向左走,直接向右走  
            }  
        }  
    }  
}  

另外有一个详细的版本
typedef struct
{
Node * p;
int rvisited;
}SNode; //Node 是二叉树的结点结构,rvisited==1代表p所指向的结点的右结点已被访问过。

lastOrderTraverse(BiTree bt){
  //首先,从根节点开始,往左下方走,一直走到头,将路径上的每一个结点入栈。
  p = bt;
  while(bt){
    push(bt, 0); //push到栈中两个信息,一是结点指针,一是其右结点是否被访问过
    bt = bt.lchild;
  }

  //然后进入循环体
  while(!Stack.empty()){ //只要栈非空
    sn = Stack.getTop(); // sn是栈顶结点

    //注意,任意一个结点N,只要他有左孩子,则在N入栈之后,N的左孩子必然也跟着入栈了(这个体现在算法的后半部分),所以当我们拿到栈顶元素的时候,可以确信这个元素要么没有左孩子,要么其左孩子已经被访问过,所以此时我们就不关心它的左孩子了,我们只关心其右孩子。

    //若其右孩子已经被访问过,或是该元素没有右孩子,则由后序遍历的定义,此时可以visit这个结点了。
    if(!sn.p.rchild || sn.rvisited){
      p = pop();
      visit(p);
    }
    else //若它的右孩子存在且rvisited为0,说明以前还没有动过它的右孩子,于是就去处理一下其右孩子。
    {
      //此时我们要从其右孩子结点开始一直往左下方走,直至走到尽头,将这条路径上的所有结点都入栈。

      //当然,入栈之前要先将该结点的rvisited设成1,因为其右孩子的入栈意味着它的右孩子必将先于它被访问(这很好理解,因为我们总是从栈顶取出元素来进行visit)。由此可知,下一次该元素再处于栈顶时,其右孩子必然已被visit过了,所以此处可以将rvisited设置为1。
      sn.rvisited = 1;

      //往左下方走到尽头,将路径上所有元素入栈
      p = sn.p.rchild;
      while(p != 0){
        push(p, 0);
        p = p.lchild;
      }
    }//这一轮循环已结束,刚刚入栈的那些结点我们不必管它了,下一轮循环会将这些结点照顾的很好。
  }
}



已知两种序列求解第三种序列


注意:

已知先序和后序,不能求得中序,故不讨论


(一):已知二叉树中序序列和后序序列,求先序序列

例:(白书P38613题)二叉树中序序列为abcdefg,后序序列为bdcafge,求先序序列

1.找根结点:

后序序列顺序为左右头,它最后一个定为根结点,根结点就为后序序列的最后一个------e

2.分左右孩子:

e为根结点,根据中序序列是左头右推出abcd是根结点的左孩子,fg为右孩子;

3.排左孩子:

中序序列为abcd,后序序列为bdca(递归到第一步)

1):找分支结点:

        后序序列最后一个为分支结点,分支结点为a

2):分左右孩子:

        a为分支结点,根据中序序列是左头右推出没有左孩子,bcd为右孩子;

3):排左孩子:

        无;

4):排右孩子:

        (此处省略过程,还是递归)排序为bc的左孩子,dc的右孩子,ca的右孩子;

4.排右孩子:

中序序列为fg,后序序列为fg(也是递归)

1):找分支结点:

        后序序列最后一个为分支结点,分支结点为g

2):分左右孩子:

        g为分支节点,根据中序序列是左头右推出

f为左孩子,无右孩子;

    (3):排左孩子:

            fg的左孩子;

4):排右孩子:

        无;

5.画二叉树:

                       e

                     /     \

                    a       g

                     \      /

                      c    f

                    /   \

                   b     d

6.写先序序列:为eacbdgf

(二):已知二叉树先序序列和中序序列,求后序序列

例:(白书P38618题)二叉树先序序列为EFHIGJK,中序序列为HFIEJKG,求后序序列

1.找根结点:

先序序列顺序为头左右,它第一个定为根结点,根结点就为先序序列的第一个------E

2.分左右孩子:

E为根结点,根据中序序列是左头右推出HFI是根结点的左孩子,JKG为右孩子;

3.排左孩子:

中序序列为HFI,先序序列为FHI(递归到第一步)

1):找分支结点:

        先序序列第一个为分支结点,分支结点为F

2):分左右孩子:

        F为分支结点,根据中序序列是左头右推出H为左孩子,I为右孩子;

3):排左孩子:

        HF的左孩子;

4):排右孩子:

        IF的右孩子;

4.排右孩子:

中序序列为JKG,先序序列为GJK(也是递归)

1):找分支结点:

        先序序列第一个为分支结点,分支结点为G

2):分左右孩子:

        G为分支节点,根据中序序列是左头右推出

JK为左孩子,无右孩子;

    (3):排左孩子:

            (此处省略过程,还是递归)KJ的右孩子,JG的左孩子;

4):排右孩子:

        无;

5.画二叉树:

                       E

                     /     \

                    F       G

                   /  \      /

                  H   I    J

                            \

                              K

6.写后序序列:为HIFKJGE



前序  ABDGHCEFI
中序  GDHBAECIF
后序  GHDBEIFCA


学习这个查找函数:

int temp=strchr(mid,pre[i])-mid;///字符第一次出现在mid中的位置


/*
根据二叉树的前序序列(或者后序)、中序序列和序列长度length
求后序或者前序的二叉树
注:不能由前序和后序得到中序序列
*/

#include"bintree.h"
#include<string.h>
char *a="";
char pre[100],mid[100],post[100];

/*
* i: 子树的前序序列字符串的首字符在pre[]中的下标
* j: 子树的中序序列字符串的首字符在mid[]中的下标
* len: 子树的字符串序列的长度
*/

void PreMidCreateTree(bintree &pn,int i,int j,int len)
{
if(len<=0){
pn=NULL;
return;
}

pn=(bintree)malloc(sizeof(binnode));
pn->data=pre[i];
int temp=strchr(mid,pre[i])-mid;///字符第一次出现在mid中的位置
PreMidCreateTree(pn->lchild,i+1,j,temp-j); ///temp-j为左子树字符串长度
PreMidCreateTree(pn->rchild,i+(temp-j)+1,temp+1,len-1-(temp-j)); ///len-1-(temp-j)为右子树字符串长度
}



/* 利用后序中序序列创建树
* i: 子树的后序序列字符串的尾字符在post[]中的下标
* j: 子树的中序序列字符串的首字符在mid[]中的下标
* len: 子树的字符串序列的长度
*/
void PostMidCreateTree(bintree &pn,int i,int j,int len)
{
if(len<=0){
pn=NULL;
return;
}

pn=(bintree)malloc(sizeof(binnode));
pn->data=post[i];
int m=strchr(mid,post[i])-mid;
PostMidCreateTree(pn->lchild,i-1-(len-1-(m-j)),j,m-j);
PostMidCreateTree(pn->rchild,i-1,m+1,len-1-(m-j));
}



int main()
{
freopen("in.txt","r",stdin);
puts("请输入前序序列:");
gets(pre);
puts("请输入中序序列:");
gets(mid);
puts("请输入后序序列:");
gets(post);

puts("**********华丽的分割线*********");
bintree root1;
PreMidCreateTree(root1,0,0,strlen(pre));
puts("后序序列是:");
postorder(root1);
puts("");

puts("**********华丽的分割线*********");
bintree root2;
PreMidCreateTree(root2,0,0,strlen(post));
puts("前序序列是:");
preorder(root2);
puts("");
}