二叉树及其遍历(递归和非递归实现)

1.基本概念

二叉树:一种特殊的树形结构,它的特点是每个结点至多只有两棵子树(即二叉树中不存在度大于2的结点),并且,二叉树的子树有左右之分,其次序不能任意颠倒。

二叉树的五种形态:空二叉树、只有一个根结点、根节点只有左子树、根节点只有右子树、根节点既有左子树又有右子树。

特殊二叉树

  • 斜树(所有结点只有左子树的二叉树叫左斜树,所有结点只有右子树的二叉树叫右斜树,与线性表结构类似)
  • 满二叉树(所有分支结点都存在左子树和右子树,且所有叶子结点都在同一层,即拥有最大结点数)
  • 完全二叉树(从上到下,从左到右依次填满树结构,若一层没填满,则不能跳到一层。满二叉树为特殊的完全二叉树)

二叉树的性质:

  • 性质1 在二叉树的第i层上至多有 2i1 个结点
  • 性质2 深度为k的二叉树至多有 2k1 个结点
  • 性质3 对于任何一棵二叉树,如果其叶子结点数为n0,度为2的结点数为n2,则n0=n2+1
  • 性质4 具有n个结点的二叉树,其深度为 log2n+1 x 不大于x的最大整数)
  • 性质5 如果对于一棵有n个结点的完全二叉树的结点按层序编号(从第1层到 log2n+1 层,每层从左至右),对任 意一结点 i( 1in )有:
    1. 如果i=1,则i是根节点,无双亲。如果i>1,则 i/2 是其双亲结点
    2. 如果2i>n,则i结点的无左孩子,否则i结点左孩子为2i
    3. 如果2i+1>n,则i结点无右孩子,否则i结点右孩子为2i+1

2.二叉树的存储结构

顺序存储结构

按照完全二叉树的结构,从1开始依次对各结点编号,并根据编号将其存入对应下标的数组中。不存在的结点用“0”或其他符号表示。此类方法对于非完全二叉树,可能会造成大量存储空间的浪费。

链式存储结构(二叉链表)

该存储方法在每个结点中设置两个指针域,分别指向该结点的左孩子和右孩子。

 //二叉树的链式存储结构定义  
typedef char TElemType;         //元素的数据类型根据实际情况而定,这里假设为char

typedef struct TNode          //结点结构 
{  
    TElemType      data;           //数据域,用于存储结点数据   
    struct TNode  *lchild;     //指针,指向该结点的左孩子 
    struct TNode  *rchild;     //指针,指向该结点的右孩子             
}BiTNode,*BiTree;

如有需要,还可以增加一个指针域指向双亲,形成三叉链表。

3.二叉树的遍历

二叉树的遍历是从根节点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次。
二叉树的遍历方式有很多,如果限制从左到右的方向,则主要包括前序遍历、中序遍历、后序遍历和层序遍历
每次遍历时,对各结点的操作,此处定义为最简单的输出操作:

void printelement(TElemType e)  //定义visit函数,最简单的就是print直接输出 
{
    printf("%c",e);
}

前序、中序和后序遍历有递归和非递归两种实现方法。非递归遍历方法需要运用栈这种数据结构,在此定义栈的数据结构以及建栈、压栈、出栈和验证栈是否为空操作:

typedef BiTNode* ElemType;    //定义数据类型为指向树结点的指针
typedef struct Node
{
    ElemType data;
    struct Node*next;
}Node,*LinkStack;

int InitStack(LinkStack*S)
{
    (*S)=(Node*)malloc(sizeof(Node));      //头结点 
    if (!(*S)) return ERROR;
    (*S)->next=NULL;                        
    (*S)->data=0;                         //头结点数据域用来存储栈长度 
}

int StackEmpty(LinkStack S)   //验证栈是否为空 
{
    if (S->data==0) return TRUE;
    return FALSE; 
}

int Push(LinkStack *S,ElemType e)   //将元素e压栈 
{
    Node*p;
    p=(Node*)malloc(sizeof(Node));
    if (!p) return ERROR;
    p->data=e;
    p->next=(*S)->next;
    (*S)->next=p;
    (*S)->data++;
    return OK;
}

int POP(LinkStack *S,ElemType*e)  //将栈顶元素出栈,并将值返回给e 
{
    Node*p;
    if (StackEmpty(*S)) return ERROR;
    p=(*S)->next;
    *e=p->data;
    (*S)->next=p->next;
    free(p);
    (*S)->data--;
    return OK;
}

前序遍历

若二叉树不为空,先访问根结点,然后前序遍历左子树,再前序遍历右子树。

void PreOrderTraverse(BiTree T,void(*visit)(TElemType))  //递归
{
    if (T==NULL) return;
    visit(T->data);
    PreOrderTraverse(T->lchild,visit);
    PreOrderTraverse(T->rchild,visit);
} 
void PreOrderTraverse(BiTree T,void(*visit)(TElemType))  //非递归
{
    BiTNode*p;
    LinkStack S;
    InitStack(&S);
    p=T;
    while (p||!StackEmpty(S))     //当树非空或栈不为空时进行遍历操作 
    {
        if (p)                     //当前结点存在时
        {
            Push(&S,p);            //将结点压栈
            visit(p->data);       //访问当前结点
            p=p->lchild;           //指向当前结点的左孩子
        }else
        {
            POP(&S,&p);            //当前结点不存在时,则弹出栈顶结点,使其成为当前结点 
            p=p->rchild;           //指向当前结点的右孩子 
        }
    }
}

中序遍历

若二叉树不为空,先中序遍历左子树,然后访问根结点,再中序遍历右子树。

void InOrderTraverse(BiTree T,void(*visit)(TElemType))  //递归
{
    if (T==NULL) return;
    InOrderTraverse(T->lchild,visit);
    visit(T->data);
    InOrderTraverse(T->rchild,visit);
}
void InOrderTraverse(BiTree T,void(*visit)(TElemType))  //非递归
{
    BiTNode*p;
    LinkStack S;
    InitStack(&S);
    p=T;
    while (p||!StackEmpty(S))      //当树非空或栈不为空时进行遍历操作 
    {
        if (p)                  //当前结点存在时 
        { 
            Push(&S,p);         //将结点压栈 
            p=p->lchild;        //指向当前结点的左孩子 
        }else
        {
            POP(&S,&p);        //当前结点不存在时,则弹出栈顶结点,使其成为当前结点 
            visit(p->data);    //访问当前结点 
            p=p->rchild;       //指向当前结点的右孩子 
        }
    }
}  

后序遍历

若二叉树不为空,先后序遍历左子树,然后后序遍历右子树,再访问根结点。

void PostOrderTraverse(BiTree T,void(*visit)(TElemType))  //递归
{
    if (T==NULL) return;
    PostOrderTraverse(T->lchild,visit);
    PostOrderTraverse(T->rchild,visit);
    visit(T->data);
}
void PostOrderTraverse(BiTree T,void(*visit)(TElemType)) //非递归
{
    BiTNode*pcur,*plastvisit;    //pcur为当前结点,plastvisit为上一个访问的结点 
    LinkStack S;
    InitStack(&S);               //建栈 
    pcur=T;                     //使根结点成为当前结点 
    plastvisit=NULL;             
    while(pcur)                 //寻找后序遍历的第一个结点,并将沿路结点依次压栈 
    {
        Push(&S,pcur);
        pcur=pcur->lchild;
    }
    while (!StackEmpty(S))    //栈为空时,所有结点输出完毕,退出循坏 
    {
        POP(&S,&pcur);        //将栈顶结点弹出,成为当前结点 
        if (pcur->rchild==NULL || pcur->rchild==plastvisit) //如果当前结点的右孩子不存在或右孩子已经被访问过,则可以输出该结点 
        {
            visit(pcur->data);
            plastvisit=pcur;   //使当前结点成为上一个访问的结点,为下一轮做准备 
        }else                //否则说明当前结点的右子树还没有遍历,不能输出当前结点 
        {
            Push(&S,pcur);  //将当前结点重新压栈 
            pcur=pcur->rchild;  //进入当前结点的右子树 
            while(pcur)    
            {
                Push(&S,pcur);
                pcur=pcur->lchild;
            }
        }    
    }
}

层序遍历

若二叉树不为空,从树的第一层开始,从左至右,从上至下逐层访问各结点。
和前三种遍历方法相比,层序遍历较为不同,在此借助队列结构来实现遍历。
首先定义队列的数据结构以及建队、入队、出队和验证队列是否为空的操作:

typedef BiTNode* ElemType; //定义数据类型为指向树结点的指针

typedef struct Node   
{  
    ElemType data;            //数据域,用于存储结点数据   
    struct Node *next;        //指针域,存储下一个结点的地址信息                
}QNode,*Queueptr;  

typedef struct  
{  
    Queueptr front,rear;     //定义头指针和尾指针   
}LinkQueue;

int InitQueue(LinkQueue*Q)    //建立队列   
{  
    Q->front=(QNode*)malloc(sizeof(QNode));   //建立头结点   
    if (!Q->front) return ERROR;  
    Q->front->next=NULL;     
    Q->rear=Q->front;           //头尾指针同时指向头结点,队列为空   
    return OK;  
}

int QueueEmpty(LinkQueue*Q)  //验证队列是否为空   
{  
    if (Q->front==Q->rear) return TRUE;  
    return FALSE;   
}

int EnQueue(LinkQueue*Q,ElemType e)  //插入元素e队列   
{  
    QNode*p;  
    if (!Q->front) return ERROR;  
    p=(QNode*)malloc(sizeof(QNode));  
    p->data=e;  
    p->next=NULL;                  //使新节点next指针指向空   
    Q->rear->next=p;               //使前一结点指向新节点   
    Q->rear=p;                     //使尾指针指向新节点   
}  

int DeQueue(LinkQueue*Q,ElemType*e)  //删除队头元素,返回值给e   
{  
    QNode*p;  
    if (!Q->front) return ERROR;  
    p=Q->front->next;                //记录队头结点   
    *e=p->data;  
    Q->front->next=p->next;          //将头结点指向队头结点的下一个结点   
    if (Q->rear==p) Q->rear=Q->front;  //若队头结点为最后一个结点,则使尾指针指向头结点   
    free(p);                          //释放队头结点   
    return OK;  
} 

层序遍历:

void LevelOrderTraverse(BiTree T,void(*visit)(TElemType))
{
    BiTNode*p;
    LinkQueue Q;        //定义队列类型 
    InitQueue(&Q);     //创建队列  
    if (T!=NULL) EnQueue(&Q,T);   //如果树非空,把根结点入队列 
    while(!QueueEmpty(&Q))     //如果队列非空,则继续遍历操作 
    {
        DeQueue(&Q,&p);      //将队列头的树结点弹出 
        visit(p->data);      //对出队列的结点进行访问 
        if (p->lchild)       //如果该出队列的结点左孩子存在,则将其左孩子入队列 
        EnQueue(&Q,p->lchild);
        if (p->rchild)      //如果该出队列的结点右孩子存在,则将其右孩子入队列 
        EnQueue(&Q,p->rchild);
    }

}

4.二叉树的建立

前面我们了解了几种基本的二叉树遍历方法,现在我们就可以运用这些方法来建立二叉树。只需把前面遍历过程中对每个结点的输出操作改为输入操作即可。例如用前序遍历的方法建立二叉树:

//二叉树的建立
void CreateBiTree(BiTree * T)
{
    char ch;
    scanf("%c",&ch);
    if (ch=='#') *T=NULL;    //如果结点数据为空,则将指针指向空 
    else
    {
        *T=(BiTree)malloc(sizeof(BiTNode));  //申请结点空间 
        if (!*T) exit(OVERFLOW);
        (*T)->data=ch;                       //输入数据,生成根节点 
        CreateBiTree(&((*T)->lchild));      //构造左子树 
        CreateBiTree(&((*T)->rchild));      //构造右子树 
    }
}

5.代码验证

int main()
{
    BiTree T;
    printf("按先序遍历建立二叉树:\n");
    CreateBiTree(&T);
    printf("先序遍历:\n");
    PreOrderTraverse(T,printelement);
    printf("\n中序遍历:\n");
    InOrderTraverse(T,printelement);
    printf("\n后序遍历:\n");
    PostOrderTraverse(T,printelement);
    printf("\n层序遍历:\n");
    LevelOrderTraverse(T,printelement);
    return 0;
}

这里写图片描述

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值