二叉树

 

 

 

 

 二叉树的特点:1每个结点至多只有两棵子树(即二叉树中不存在度大于2的结点);2二叉树的子树有左右之分,其次序不能任意颠倒。

    二叉树的递归定义

  二叉树(BinaryTree)n(n≥0)个结点的有限集。它或者是空集(n=0),或者同时满足以下两个条件:

  (1) 有且仅有一个根结点;

  (2) 其余的结点分成两棵互不相交的左子树和右子树。

二叉树(Binary Tree)是另一种树型结构。

    二叉树的特点:1每个结点至多只有两棵子树(即二叉树中不存在度大于2的结点);2二叉树的子树有左右之分,其次序不能任意颠倒。

    二叉树的递归定义

  二叉树(BinaryTree)n(n≥0)个结点的有限集。它或者是空集(n=0),或者同时满足以下两个条件:

  (1) 有且仅有一个根结点;

  (2) 其余的结点分成两棵互不相交的左子树和右子树。

二叉树的五种基本形态:

                 二叉树_第1张图片

       (a) 空二叉树 (b)  只有根节点的二叉树   (c)右子树为空的二叉树 (d) 左子树为空的二叉树 (e) 满二叉树

满二叉树:第i层节点的个数位2^(i-1)     节点的总个数位:2^k-1(k位深度)

完全二叉树:每个节点i的左子树深度减去右子树深度结果为零或1

二叉树的性质:

性质1  若二叉树的层次从1开始, 则在二叉树的第

i 层最多有 2^(i-1) 个结点。(i >=1)

性质2深度为k的二叉树最多有 2k-1个结点。(k ³ 1)

性质3:对任何一颗二叉树,如果其叶子节点的个数为n0,度为2的非叶子节点个数为n2,则有n0=n2+1

性质4:具有n个节点的完全二叉树的高度为log2(n)+1(log以2为底n的对数+1)

性质5:如果对一颗有n个节点的完全二叉树(深度log2n+1)的节点按连续层序编号,则对任一节点i有

(1)如果i=1,则节点i为二叉树的根节点,无双亲,如果i>1,则其双亲是编号为i/2的节点

(2)如果2i>n.则编号为i的节点无左孩子,为叶子节点;否则其左孩子为编号是2i的节点

(3)如果2i+1>n,则编号为i的节点无右孩子,否则其右孩子是编号为2i+1的节点。

 

(前面的都是基本概念)

现在来讲一下二叉树的存储结构,他的存储结构也有两种,顺序和链表

1.顺序存储结构:用这种存储结构是我们不仅要知道二叉树要存储的数据信息,还要存储他的结构信息,比如双亲和孩子的关系

关键是如何反应二叉树中的非线性关系(完全 二叉树中节点i的左孩子为2i,右孩子为2i+1,双亲为i/2),在此约定把一颗具有n个节点的完全二叉树存储在具有N+1个存储单元的一维数组中,并且,为了使节点的位置与其在数组中的下标一致,下标为0的存储单元不存放二叉树的节点信息,可以用于存放二叉树节点的总数等。这种存储方式适合完全二叉树,既不浪费存储空间,又能很快确定节点的存放位置,节点的双亲和左右孩子的存放位置,但对一般二叉树,可能造成存储空间的大量浪费。

例如:深度为K,且只有k个节点de左单支,需2^k-1.个单元,既有2^k-1-K个单元被浪费

二叉树_第2张图片

2.链式存储结构

设计不同的节点结构,可以构成不同的链式存储结构,常用的有:

二叉树_第3张图片

 

(1)二叉链表 用于二叉树每个节点之多只有连个孩子,分别是左孩子和右孩子,因此可以把每个节点分成三个域,一个域存放节点本身信息,另外另个域是指针,分别指向左右孩子的地址,每个节点的结构表示为:

lchile data rchild

二叉链表的类型定义如下:

typedef int ElemType

typedef struct BiTreeNode

{

ElemType data;

struct BitreeNode *lchild

struct BitTreeNode *rchild

}BitTreeNode,*BiTree;

二叉树_第4张图片

一个二叉链表由根指针root唯一确定,若二叉树为空,则root=Null,若节点的某个孩子不存在,则相应的指针为空

具有n个节点的二叉链表中,共有2n个指针域,其中有n-1个用来指示节点的左右孩子,其余的n+1个指针域为空

遍历二叉树

DLR------------先序(先遍历根节点,再遍历左子树,再遍历右子树)

LDR------------中序 (先遍历左子树,在遍历根节点,再遍历右子树)

LRD------------后序 (先遍历左子树,再遍历右子树,再遍历根节点)

二叉树的先序遍历递归算法

void  PreOrder(BiTree bt)  /* bt为指向根结点的指针*/

{

               if (bt)  /*如果bt为空,结束*/

               {

                          visit (bt );     /*访问根结点*/

                          PreOrder (bt -> lchild);  /*先序遍历左子树*/

                          PreOrder (bt -> rchild);  /*先序遍历右子树*/

               }

}

代码实现:

typedef char ElemType;
typedef struct BtNode // binarytreenode
{
    BtNode *leftchild;
    //BtNode *parent; // BST val rb b+-
    BtNode *rightchild;
    ElemType data;
}BtNode,*BinaryTree;

//先序

void PreOrder(BtNode *p)
{
    if(p != NULL)
    {
        cout<data<<" ";
        PreOrder(p->leftchild);
        PreOrder(p->rightchild);
    }
}

//中序
void InOrder(BtNode *p)
{
    if(p != NULL)
    {
        InOrder(p->leftchild);
        cout<data<<" ";
        InOrder(p->rightchild);
    }
}

//后序

void PastOrder(BtNode *p)
{
    if(p != NULL)
    {
        PastOrder(p->leftchild);
        PastOrder(p->rightchild);
        cout<data<<" ";
    }
}

写出图示二叉树的前序,中序,后序序列

二叉树_第5张图片

层次遍历:二叉树的层次遍历是指从二叉树的第一层(根节点)开始从上至下逐层遍历,在同一层中,按从左至右的顺序对节点逐个进行访问

树的层次遍历利用队列来实现

算法思想:遍历从二叉树的根节点开始,首先将根节点入队列,然后执行下面的操作

   (1)取出队头的元素;

(2)访问该元素所指节点

(3)若该元素所指节点的左右孩子节点非空,则将该元素所指节点的左孩子指针和右孩子指针入队

(4)若队列非空,重覅1,3,当队列为空时,二叉树的层次遍历结束

void LevelOrder( BiTree bt) /*层次遍历二叉树bt算法*/

初始化队列Q

  if ( bt == NULL )    return;

  bt入队列Q;

  while( 队列Q不空){ 

  pß出队元素;

  Visit( p); /*访问出队结点*/

  if ( p->lchild) /*队首结点左孩子不空,入队*/

         {   p->lchild入队Q  }

  if (p->rchild/*队首结点右孩子不空,入队*/

          {   p->rchild入队Q  }

  }

}

 

代码实现:

层次遍历:

void NiceLevelOrder(BtNode *ptr)
{
    if(NULL == ptr) return ;
    queue qu;
    qu.push(ptr);
    while(!qu.empty())
    {
        BtNode *p = qu.front(); qu.pop();
        cout<data<<" ";
        if(p->leftchild != NULL)
        {
            qu.push(p->leftchild);
        }
        if(p->rightchild != NULL)
        {
            qu.push(p->rightchild);
        }
    }
    cout<     
}

 

构造二叉树:

(根据序列构造二叉树)

已知二叉树的先序序列和中序序列构造二叉树:

int  FindPos(ElemType *is,ElemType x,int n)

//这个函数的作用是在中序遍历中找到先序遍历根的位置
{
    int pos = -1;
    for(int i = 0;i     {
        if(x == is[i])
        {
            pos = i;
            break;
        }
    }
    return pos;
}

BtNode * CreatePI(ElemType *ps,ElemType *is,int n)//ps是先序序列,is是中序序列,数组大小
{
    BtNode *s = NULL;                      
    if(n > 0)
    {
        s = Buynode();
        s->data = ps[0];// s->data = *ps;
        int pos = FindPos(is,ps[0],n);//找到跟节点在中序中的位置
        if(pos == -1) exit(1);
        s->leftchild = CreatePI(ps+1,is,pos);
        s->rightchild = CreatePI(ps+1+pos,is+1+pos,n - pos - 1);
    }
    return s;
}
BtNode * CreateTreePI(ElemType *ps,ElemType *is,int n)
{
    if(NULL == ps || NULL == is || n < 1)
        return NULL;
    else
        return CreatePI(ps,is,n);
}

 

已知二叉树的中序遍历和后序遍历序列创建一颗二叉树

BtNode * CreateTreePI(ElemType *ps,ElemType *is,int n)
{
    if(NULL == ps || NULL == is || n < 1)
        return NULL;
    else
        return CreatePI(ps,is,n);
}
BtNode * CreateIL(ElemType *is,ElemType *ls,int n)
{
    BtNode *s = NULL;
    if(n > 0)
    {
        s = Buynode();
        s->data = ls[n-1];
        int pos = FindPos(is,ls[n-1],n);//同上面的findpos 相同,是在中序遍历中找后序遍历序列得到的根节点
        if(pos == -1) exit(1);
        s->leftchild = CreateIL(is,ls,pos);
        s->rightchild = CreateIL(is+pos+1,ls+pos,n - pos - 1);
    }
    return s;
}

 

******这样有一个缺点,如果二叉树的节点中有重复的,那末这样构造就不准确

二叉树的非递归调用:

二叉树_第6张图片

             非递归的遍历

以二叉树的中序遍历为例说明采用这种深入个返回策咯的遍历方法

沿左子树深入时,深入一个节点入栈一个节点,沿左分治无法继续深入时,返回,即出栈,出此栈的同是访问节点,然后从此节点的右子树继续深入,这样一直下去,直到从根节点的右子树返回(即空栈)时结束。

中序遍历的非递归算法思想:

Void InorderBiTree bt

{  p=bt;       /*根结点为当前结点*/

S=Initial( );  /*初始化栈*/

While(p||EmptyS)) 

{

Whilep/*当前结点不空*/

{

PushSp); /*当前结点入栈*/

p=p->Lchild; /*左孩子作为当前点;

 

}

If(!Empty(S))  /*栈不空*/

{

p=pop(s);   /*出栈*/

Visit(p);   /*访问结点*/

p=p-Rchild; /*右孩子作为当前结点*/

}

}

}

先序遍历的非递归算法思想:

Void FirstorderBiTree bt

{  p=bt;       /*根结点为当前结点*/

S=Initial( );  /*初始化栈*/

While(p||EmptyS)) 

{

Whilep/*当前结点不空*/

visit(p);  /*访问结点*/

PushSp); /*当前结点入栈*/

p=p->Lchild; /*左孩子作为当前结点*/

 

}

If(!Empty(S))  /*栈不空*/

{

p=pop(s);   /*出栈*/

p=p->Rchild; /*右孩子作为当前结点*/

}

}

}

后序遍历的非递归算法思想:

typedef enum{L,R} tagtype;     /*定义枚举类型*/

typedef struct
{

Bitree ptr;
tagtype tag;
}
stacknode;                  /*定义栈结点类型*/

  typedef struct
{

stacknode Elem[maxsize];
int top;
}
SqStack;                     /*定义顺序栈*/

SqStack s;

stacknode x;

void PostOrderUnrec(Bitree t)   /*后序遍历算法*/

 {   p=t;

     If(!p) return;
     do

     {    while (p)        /*遍历左子树*/      

         {
               x.ptr = p;
               x.tag = L;           /*标记为左子树*/
               push(s,x);         /*入栈*/

               p=p->lchild;      /*左孩子作为当前结点*/

            }
           while (!StackEmpty(s) && s.Elem[s.top].tag==R)  
           {     x = pop(s);
                p = x.ptr;
                visite(p);//tagR,表示右子树访问完毕,故访问根结点   
           }

                 if (!StackEmpty(s))
                 {
                   
s.Elem[s.top].tag =R;             /*遍历右子树*/
                    p=
s.Elem[s.top].ptr->rchild;   /*右孩子作为当前结点*/    
                  }

      }while (!StackEmpty(s));

结合“扩展先序遍历序列”,创建二叉树

扩展先序遍历序列:就是先对原有二叉树用空子树进行扩展,使每个结点的左右子树(包括空子树)都存在,然后再对扩展后的二叉树进行先序遍历。遍历序列中用特定的符号表示空子树扩展先序遍历序列:就是先对原有二叉树用空子树进行扩展,使每个结点的左右子树(包括空子树)都存在,然后再对扩展后的二叉树进行先序遍历。遍历序列中用特定的符号表示空子树

二叉树_第7张图片二叉树_第8张图片

其扩展先序遍历序列为:

5 8 9 0 0 7 0 0 6 0 3 4 0 0 0

其中“0”表示空子树。

 

代码如下:

BtNode *Buynode()
{
    BtNode *s = (BtNode*)malloc(sizeof(BtNode));
    if(NULL == s) exit(1);
    memset(s,0,sizeof(BtNode));
    return s;
}
void Freenode(BtNode *p)
{
    free(p);
}

BtNode * CreateTree1()
{
    BtNode *s = NULL;
    ElemType item;
    cin>>item;
    if(item != '#')
    {
        s = Buynode();
        s->data = item;
        s->leftchild = CreateTree1();
        s->rightchild = CreateTree1();
    }
    return s;
}
BtNode * CreateTree2(ElemType *&str)
{
    BtNode *s = NULL;
    if(*str != '#')
    {
        s = Buynode();
        s->data = *str;
        s->leftchild = CreateTree2(++str);
        s->rightchild = CreateTree2(++str);
    }
    return s;
}

 

例题(二叉树遍历算法的应用)

        设计算法输出二叉树的所有叶子结点的个数。

        设计算法求二叉树的深度。

        设计算法先序输出二叉树中所有结点值及其对应层次/序号。

        设计算法输出从根结点到每个叶子结点的路径(利用栈)。

 

1.设计算法输出二叉树所有叶子节点的个数

思想:若二叉树为空,则叶子数目为0,

           对于一颗非空二叉树,如果他的左子树和右子树都为空,那末此二叉树只有一个节点,就是叶子,此时叶子数目为1,否则,二叉树的叶子数目为左子树叶子数目和右子树叶子数目总和

算法如下:

int BitreeLeaf ( BiTree bt )   //bt是根节点

{

  if ( bt == NULL ) return 0 ;  /* 空树,叶子数为0 */

  if ( bt->lchild ==NULL&& bt->rchild == NULL) 

  return 1 ; /*只有一个根结点,叶子数为1*/

  return ( BitreeLeaf ( bt -> lchild ) + BitreeLeaf ( bt -> rchild )) ;

}

代码如下:

int GetSize(BtNode *ptr) //计算二叉树节点的个数
{
    if(ptr == NULL) return 0;
    else return GetSize(ptr->leftchild) + GetSize(ptr->rightchild) + 1;
}
 

2.设计算法求二叉树的深度

基本思想:若二叉树为空,约定二叉树的深度为0;对于一颗二叉树,如果他的左子树和右子树都为空,那末此二叉树只有一个根节点,此时二叉树的深度为1,否则先求其左右子树的深度为depthl和depthr,那末整颗二叉树的深度为1+max(depthl,delptr).

算法如下:

int BitreeDepth ( BiTree bt )

{  int d = 0,depthL, depthR/*depthLdepthR分别为左、右子树的深度*/

  if ( bt == NULL ) return 0 ;  /*空树,深度为0 */

  if ( bt -> lchild ==NULL && bt -> rchild == NULL)  return 1;  /*叶子结点,深度为1 */

  depthL = BitreeDepth ( bt -> lchild ) ;  /*左子树深度 */

  depthR = BitreeDepth ( bt -> rchild ) ;  /*右子树深度 */

  d = max (depthL , depthR/*d为左右子树中较深者的深度*/

  return d+1 ;   /* 整棵二叉树的深度为左、右子树中较深者的深度+1 */

}

代码如下:


int Depth(BtNode *ptr)
{
    if(ptr == NULL) return 0;
    else return max(Depth(ptr->leftchid),Depth(ptr->rightchild)) + 1;
}

 

 

 

 

 

 

 

 

 

 

 

            

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(二叉树)