二叉树 线索二叉树 以及遍历算法

来源:互联网 发布:宠物商城源码 编辑:程序博客网 时间:2024/06/08 12:00
树的概念
    树是由n个结点构成的有限集合(n>=0),当n=0时,称为空树,当n>0时:(1)有且仅有一个称为根(root)的特定节点,该节点没有前驱结点,但有0个或多个直接后驱结点。(2)除根结点以外的n-1个结点可以划分为m个互不相交的有限集(m>=0),每个有限集又是一颗子树,每颗子树的根结点有且仅有一个前驱结点,该前驱结点就是树的前驱结点,但有0个或多个后继结点。
    结点:包含一个数据域及若干个指向其他子树的分支
    结点的度:拥有子树的个数称为该结点的度。
    树的度:树中所有结点的度的最大值
    内部结点:度不为0的结点
    孩子结点:结点的子树的根(直接后驱结点)
    双亲结点:结点是其子树的根(直接前期结点),即结点是其双亲结点
    堂兄弟结点:双亲是兄弟或堂兄弟的结点
    祖先结点:从根结点到该结点的路径的所有结点   子孙节点:该结点的根结点的所有结点

     
二叉树
    概念:二叉树是由n个结点构成的有限集合。当n大于0时:该集合由一个根节点以及两个互不相交的称为左右孩子的子树构成。且满足以下条件:(1)每个结点的度的最大值不超过2个,(2)每颗子树是严格区分左右位置的
     性质:(1)在第i层上最多由2^(i-1)(i>=1)1个结点构成(二倍关系);
               (2)深度为k的二叉树最多由2^k-1个结点构成(k>=1);
               (3)具有n个节点的完全二叉树的深度为[log2N]+1(同(2));
               (4)对于任意一颗二叉树,若终端结点个数为n0,度为2的结点个数为n2,则n0=n2+1(多一个度为2的结点多一个叶子结点);
               (5)对于具有n个结点的满二叉树,所有结点从1开始编号,则对于任意为i的结点:1.若i==1,则为根无双亲;若i>1,则双亲结点序号为[i/2]。2.如果2*i<n,则i的左孩子为2*i,若2*i+1<n,则i的右孩子为2*i+1。

存储
     1.顺序存储结构
       将各结点的数据存储到一组连续的一维数组中去,这种存储方式对于满二叉树和完全二叉树非常方便,但无发体现各结点的逻辑关系,导致无法找到结点的双亲
#define max 100
typedef struct
{
    datatype SqBitree[max+1];// 从1开始存储
    int nodemax; 记录最后一个结点的位置
}Bitree;
    0        1        2       3        4          5            6        7         8        9        10       11     12      13     14 

A

B


C







D


       2.链式存储结构
         对于任意的二叉树,每一个结点包含数据域,两个指针域(Lchild,Rchild),一颗二叉树含有n个结点必有n-1个指针域指向孩子,n+1个指针域为空
Lchid左孩子
data
Rchild右孩子

#define max 100
typedef struct node
{
    datatype data;
    struct node *Rchild,*Lchild;//指向左右孩子指针
}Bitree;


遍历
                           
   1.递归实现
          
      (1)先序遍历:先访问根节点数据,再先序遍历左子树,再先序遍历右子树。由图遍历就是A,B,D,E,C,F;
      (2)中序遍历:先中序遍历左子树,再访问根结点数据,再中序遍历右子树。由图遍历就是D,B,E,A,F,C;
      (3)后序遍历:先后序遍历左子树,再后序遍历右子树,再访问根结点数据。由图遍历就是D,E,B,F,C,A;
       递归形式可读性强,易于理解算法简洁,易于证明,但算法效率太低,消耗的时间太多,占用空间资源较多
       先序遍历实现:
void preorder(Bitree *root)
{
    if(root!=NULL)
    {
        visit(root->data); //先访问根结点的数据,再通过递归调用,先一直访问根结点左子树,直到根结点左子树为空,再继续访问根结点的右子树。若右子树也为空则函数返回前驱结点,接着按函数递归访问,直到全部结点访问完成
        preorder(root->Lchild);                                                                                                                                                                                                                     
        preorder(root->Rchild);
    }
}

       中序遍历实现:
void inorder(Bitree *root)
{
    if(root==NULL)
    {
        inorder(root->Lchild); //先递归访问根结点的左子树,直到根结点左子树为空,访问根结点数据,再继续根结点访问右子树。若根结点右子树为空则函数返回前驱结点,访问前驱结点的数据,接着函数按递归继续访问,直到所有结点全部访问完成
        visit(root->data);
        inorder(root->Rchild);
    }
}

       后序遍历实现:
void postorder(Bitree *root)
{
    if(root!=NULL)
    {
        postorder(root->Lchild); //先递归根结点的左子树,直到左子树为空继续访问右子树,若右子树为空,访问根结点数据,然后返回根结点的前驱结点,按递归继续访问前驱结点的右子树,若右子树为空或是左右子树都访问完成则访问前驱节点数据,直到所有数据访问完成 
        postorder(root->Rchild);
        visit(root->data);
    }
}


     2.非递归实现:

      (1)非递归实现先序遍历:访问根结点,根结点入栈,并进入其左子树,访问左子树的根结点并入栈,再进入下一层左子树,直到当前结点为空;若栈不为空,退出栈顶结点并进入其右子树,若右子树不为空,继续先序遍历,若为空继续退栈
      (2)非递归实现中序遍历:树的当前根结点入栈,并进入其左子树,左子树根结点入栈,进入下一层左子树,直到当前结点为空;若栈非空,取出栈顶元素,访问栈顶元素,并进入其右子树,若右子树不为空,继续中序遍历,若为空继续退栈
      (3)后序遍历比先序和中序要麻烦,因为只有把根结点的左右子树都被访问完成后,从右子树返回时才访问根结点,因此还要判断是从哪个子树返回的,所以要添加一个指针p
              非递归实现后序遍历:树的当前根结点入栈,左子树根结点入栈,进入下一层左子树,直到当前根结点为空;若栈非空,判断栈顶结点右子树是否为空或者是否是刚刚访问过的右子树(上一个访问过的后继右结点),若是的话则出栈,继续判断条件,若不是的话进入其右子树


         遍历所需的辅助函数
typedef struct node
{
    datatype data;
    struct node *Rchild,*Lchild;
}Bitree;
typedef struct node
{
    Bitree *s;
    struct node *next;
}Seqstack;
Seqstack *initstack(Seqstack *s) //设置栈
{
    s=(Seqstack*)malloc(sizeof(Seqstack));
    s->next=NULL;
    return s;
 }
void push(Seqstack *s,Bitree *p) 入栈
{
    Seqstack *q;
    q=(Seqstack*)malloc(sizeof(Seqstack));
    q->s=p;
    q->next=s->next;
    s->next=q;
    return 1;
}
Bitree *pop(Seqstack *s) 出栈
{
    Bitree *root;
    Seqstack *q;
    q=s->next;
    s->next=q->next;
    root=q->s;free(q);
    return root;
}
Bitree *top(Seqstack *s)
{
    return s->next->s;//返回栈顶元素的根结点
}
int empty(Seqstack *s) 判断空栈
{
    if(s->next==NULL)
    return 0;
    else return 1;
}

        非递归先序遍历实现
void preorder(Bitree *root)
{
    SeqStack *s=initstack(s);//创建一个栈
    Bitree *p=root;
    while(p||empty(s)) //如果树空并且栈空则循环结束
    {
        while(p!=NULL) //判断根结点是否为空
        {visit(p->data);push(s,p);p=p->Lchild;} //访问根结点并且入栈,然后进入根结点的左子树,到循环最后p=NULL,即该根结点左子树为空,接下来访问其右子树
        if(empty(s)) //判断栈空,栈中是否有根结点          
        {p=pop(s);p=p->Lchild;}出栈,取出栈顶元素根结点,并进入其右子树
    }
}

       非递归中序遍历实现
void inorder(Bitree *root)
{
    Seqstack *s=initstack();//创建一个栈
  Bitree *p=root;
    while(p||empty(s))如果树空并且栈空则循环结束
    /*{
        while(p!=NULL) 判断根结点是否为空
        {push(s,p);p=p->Lchild;} 根结点若不为空,入栈,直到当前根结点的左子树为空
        if(empty(s))
        {p=pop(s);visit(p->data);p=p->Rchild;} 出栈,取出栈顶根结点元素,访问栈顶根结点元素,并进入其右子树,继续访问
    }*/
        或者
       /*{
    if(p!=NULL)
    {push(s,p);p=p->Lchild;}
     else{p=pop(s);visit(s);p=p->Rchild;}
     }*/
      
         非递归后序遍历实现
void postorder(Bitree *root)
{
    Seqstack *s=initstack();//创建一个栈
    Bitree *p,*q; //p用来判断刚访问过的结点是不是当前根结点的右子树,如果缺少p来判断,有可能会                                                                                                        
    p=root;
    while(p!=NULL||empty(s)) 如果树空并且栈空则循环结束
    {
        while(p!=NULL) //判断根结点是否为空
        {push(s,p);p=p->Lchild;} //若不为空,入栈,进入其左子树,直到最后p=NULL
        if(empty(s))
        {
            p=top(s); //取栈顶元素   因为是后序遍历所以最后才能访问根结点
            if(p->Rchild==NULL||p->Rchild==q) //判断根结点的右子树是否为空或者右子树是否是之前访问过的根结点
            {p=pop(s);visit(s);q=p;p=NULL;} //出栈,访问当前根结点,将当前根结点赋给q,因为编译器不知道是从左子树退出访问根结点还是从右子树退出访问根结点,所以设立q,来判断与右子树相同则为访问完成,所以可以访问根结点   p=NULL则不执行第二个while循环(避免重复访问左子树,避免死循环),继续取栈顶元素,并判断栈顶元素的右子树是否执行                                
            else
            {p=p->Rchild;}
        }
    }
 }

          
      3.层次遍历:
         二叉树的层次遍历指从第一层开始从上而下逐层遍历,同层自左而右遍历                            
         访问特点:先访问的结点其孩子也会先被访问,后访问的结点其孩子也会后被访问符合队列特点   层次性很强
         实现:(1)队头结点出队,访问队头结点。(2)队头的非空左右孩子依次入队。
void levelorder(Bitree *root)
{
    Queue *q=init_Queue(); //创建队列
    Bitree *p;
    In_Queue(q,root); //根结点入队
    while(empty(q)) //队列为空则树遍历结束
    {
        p=Out_Queue(q);visit(p->data); //根结点出队访问根结点
        if(p->Lchild!=NULL)
        In_Queue(q,p->Lchild); //左孩子非空,左孩子入队
        if(p->Rchild!=NULL)
        In_Queue(q,p->Rchild); //右孩子非空,右孩子入队
    }
}


               
    遍历算法的应用
    1.统计二叉树中的结点数
      由于统计树中的结点数与访问的次序无关,所以采用何种遍历都行,统计的方法使用全局变量count
int count=0;
void printpreorder(Bitree *root)
{
    if(root!=NULL)
    {
        count++;
        printpreorder(root->Lchild);
        printpreorder(root->Rchild);
    }
}

     2.输出二叉树中的叶子结点
       输出叶子结点信息与访问次序无关
void printinorder(Bitree *root)
{
    if(root)
    {
        printinorder(root->Lchild);
        if(root->Lchild==NULL&&root->Rchild==NULL)
        printf(root->data);
        printinorder(root->Rchild);
    }
}
    3.输出叶子结点数目
      法1:采用全局变量count。 法2:用函数的递归,如果root==NULL则返回0,如果root的左右结点都为空则返回1,这个方法只有在左子树和右子树叶子都计算完成后才可以返回叶子的数目,所以采用后序遍历
递归法:
void countleafnumbers(Bitree *root)
{
    int m,n;
    if(root==NULL) return 0;
    if(root->Lchild==NULL&&root->Rchild==NULL) return 1;
    m=countleafnumbers(root->Lchild);
    n=countleafnumbers(root->Rchild);
    return (m+n);
}
     4.求二叉树的高度
        1.采用全局变量的方法,用depth记录当前结点的高度(初始值为0),h表示结点所在层次
int depth=0;
void deeptree(Bitree *root,int h) //h表示结点root所在层数
{
    if(root)
    {
        if(h>depth) depth=h; //depth记录当前结点的层次
        deeptree(root->Lchild,h+1); //下一个结点则层次加1
        deeptree(root->Rchild,h+1);
    }
}
       2.递归方法,如果root==NULL则返回0,否则否则返回1,最后比较左右子树的高度,因为比较左右子树高度所以采用后序遍历
void deeptree(Bitree *root)
{
    int m,n; //分别计算左右子树的高度
    if(root==NULL) return 0;
    else
    {
        m=deeptree(root->Lchild);
        n=deeptree(root->Rchild);
        return (m>n?m:n)+1; //比较左右子树的高度大小
    }
}
      5.求结点的双亲结点
         在遍历过程中若结点非空并且如果左孩子或者右孩子等于目标结点则返回结点
Bitree *parent(Bitree *root,Bitree *current)//current为目标结点
{
    Bitree *p;
    if(root==NULL) return NULL;
    if(root->Lchild==current||root->Rchild==current)
    return root;
    p=parent(root->Lchild);//求左子树中是否有目标结点的双亲结点
    if(p!=NULL)
    return p;
    else return parent(root->Rchild);
}
        6.比较两棵树是否相等
          递归求解的思想,依次比较左右子树
int like(Bitree *T1,Bitree *T2)
{
    if(T1==NULL&&T2==NULL) return 1;//如果都为空则树相等,返回1
    else if(T1==NULL||T2==NULL) return 0;//两棵树不等返回0
    else
    {
    like(T1->Lchild,T2->Lchild);//分别比较左子树和右子树
    like(T1->Rchild,T2->Rchild);
    }
}
         
        7.创建二叉树
          先序创建二叉树
Bitree *CreatBitree()
{
    char ch=getchar();
    Bitree *root;
    if(ch=='^')    root=NULL; //用^来表示空结点
    else
    {
        root=(Bitree*)malloc(sizeof(Bitree));
        root->data=ch;
        root->Lchild=CreatBitree(); //从左子树依次创建
        root->Rchild=CreatBitree();
    }
    return root;
}

        

线索二叉树

概念:用二叉链表存储的二叉树中有大量的指针域浪费(n个结点只有n-1个指针域指向结点,其余为空),并且树中的结点遍历中只能找到孩子信息不能直接找到前驱后继结点的信息。所以设定两个标志域来判断左右指针域的信息
ltag=1,则左指针域lchild指向前驱结点,否则指向其左孩子;rtag=1,则右指针域rchild指向后继结点,否则指向其右孩子;

遍历需要的辅助函数
typedef struct node
{
    char data;
    int Ltag,Rtag;
    struct node *Lchild,*Rchild;
}Bitree;

先序遍历创建线索二叉树
void prethread(Bitree *root)//先序遍历最后一个指向为空的指针域指向空指针,pre初始值为NULL
{
    if(root!=NULL)//root==NULL直接跳过整个函数
    {
        if(root->Lchild==NULL) root->Ltag=1;
        if(root->Rchild==NULL) root->Rtag=1;
        if(pre!=NULL)//pre指向前驱结点
        {
            if(pre->Rtag==1) pre->Rchild=root;//pre指向的前驱结点负责找到Rtag=1并指向后继root
            if(root->Ltag==1) root->Lchil=pre;//root指向的后继结点负责找到Ltag=1并指向前驱pre   
        }
        pre=root;//继续把root赋给pre,pre继续为上一个前驱结点,root下面变为后继孩子结点
        prethread(root->Lchild);
        prethread(root->Rchild);
    }

}

中序遍历创建线索二叉树
Bitree *pre=NULL
void inthread(Bitree *root)//pre为初始化为NULL,因为是中序序遍历,即如果第一个左指针域为空的结点,其指向空指针
{
    if(root!=NULL)
    {
        inthread(root->Lchild); //线索化左子树
        if(root->Lchild==NULL)//因为pre初始值为空所以注意第一次改变指针域
        {root->Lchild==pre;root->Ltag=1;}//找到左孩子为空的结点并将其改为指向前驱结点
        if(pre!=NULL&&pre->Rchild==NULL)//pre到这里就变成了之前访问过的结点,并且当lchild指向下一个结点
        {pre->Rchild=root;pre->Rtag=1;}
        pre=root;//中序遍历所以pre改为当前访问结点,记录当前结点成为后继结点的前驱
        inthread(root->Rchild); //线索化右子树
    }
}



在二叉线索树中查找特定结点的前驱,后继结点
中序线索树中查找
前驱
Bitree *Inorderprenode(Bitree *root) //注意中序查找结合中序查找特点,当前结点是左子树回来后再访问的,所以查找左子树的访问最后一个结点
{
    Bitree *q,*pre;
    pre=root->Lchild;//防止出现root只有左孩子或者root所有左孩子结点都只有左孩子
    if(root->Ltag==1) pre=root->Lchild; //Ltag==1直接输出
    else
    for(q=root->Lchild;q->Rtag==0;q=q->Rchild) //因为是中序遍历所以找到左子树中最右的右结点,但如果左子树没有右结点就直接使pre=root->Lchild;
    pre=q;
    return pre;
}
后继
Bitree *inorder_nextnode(Bitree *root) //注意中序查找特点,中序查找的直接后继是右子树访问的第一个结点
{
    Bitree *q,*next;
    next=root->Rchild; //为了防止root只有右孩子或者root所有的右孩子都只有右孩子
    if(root->Rtag==1) next=root->Rchild; //右后继结点直接指向其后继结点
    else
    for(q=root->Rchild;q->Ltag==0;q=q->Lchild) //因为中序遍历的特点,直接后继就是右子树的最左边的孩子,但如果没有直接使pre=root->Rchild;
    pre=q;
    return pre;
}




遍历线索二叉树


遍历中序线索二叉树
Bitree *firstvisit(Bitree *root)//该函数查找中序线索树中第一个被访问的结点
{
    if(root==NULL) return NULL;
    while(root->Ltag!=1)//即有左孩子
    root=root->Lchild;
    return root;
}
void visit_inorder(Bitree *root)
{
    Bitree *p;
    p=firstvisit(root);//此函数找到中序线索二叉树的第一个访问结点
    while(p!=NULL)
    {visit(p);inorder_nextnode(p);}//当p!=NULL时访问它,并且找到它的后继结点(inorder_nextnode)接着访问
}








0 0
原创粉丝点击