Binary Trees

上一篇 树


基本概念

树与二叉树的区别

\二叉树
结点数至少为1可以为0
最大度数无限制为2

五种基本形态
在这里插入图片描述
特殊的二叉树
(1)斜树:所有结点都只有左子树的二叉树称为左斜树;所有结点都只有右子树的二叉树称为右斜树。

在这里插入图片描述
<左斜树>

(2)满二叉树:所有的分支结点都存在左子树和右子树,并且所有叶子都在同一层上。
在这里插入图片描述
(3)完全二叉树:按从上到下、从左到右的顺序为结点编号,与满二叉树序号一一对应的二叉树。

在这里插入图片描述

常见性质:

性质一 :一棵非空二叉树的第i层上至多有 2 i − 1 2^{i-1} 2i1个结点
证明:用数学归纳法即可

性质二:深度为h的二叉树至多有 2 h − 1 2^{h}-1 2h1个结点
证明:根据性质一,在结合等比数列前n项和公式

性质三:对于任何一棵二叉树T,如果其终端结点数为 n 0 n_{0} n0,度为2的结点数为 n 2 n_{2} n2,则 n 0 = n 2 + 1 n_{0}=n_{2}+1 n0=n2+1
证明:
结点: n = n 0 + n 1 + n 2 n=n_{0}+n_{1}+n_{2} n=n0+n1+n2
分支: n = n 1 + 2 n 2 + 1 n=n_{1}+2n_{2}+1 n=n1+2n2+1
n 0 = n 2 + 1 n_{0}=n_{2}+1 n0=n2+1

性质四:具有n个结点的完全二叉树的深度为 log ⁡ 2 n \log_{2}n log2n
证明:
2 k − 1 ≤ n < 2 k 2^{k-1}\leq n<2^{k} 2k1n<2k
取对数 k − 1 < log ⁡ 2 n ≤ k k-1<\log_{2}n\leq k k1<log2nk
推出 log ⁡ 2 n < k ≤ log ⁡ 2 n + 1 \log_{2}n<k\leq\log_{2}n+1 log2n<klog2n+1
又k为整数 k = ⌊ log ⁡ 2 n ⌋ + 1 k=\lfloor \log_{2}n\rfloor+1 k=log2n+1

性质五
对含n个结点的完全二叉树从上到下,从左至右进行1至n的编号,则任意一个编号为i的结点:
(1) 如果i=1,为根;如果i>1,则其双亲为 ⌊ i / 2 ⌋ \lfloor i/2\rfloor i/2
(2) 如果2i>n,则结点无左孩子,否则其左孩子为2i
(3) 如果2i+1>n,则无右孩子,否则其右孩子为2i+1
证明:归纳法即可

基本操作——遍历

1、前序遍历(DLR)
(1)访问根结点
(2)前序遍历左子树
(3)前序遍历右子树

2、中序遍历(LDR)
(1)中序遍历左子树
(2)访问根结点
(3)中序遍历右子树

3、后序遍历(LRD)
(1)后序遍历左子树
(2)后序遍历右子树
(3)访问根结点

4、层序遍历(也称为广度遍历)
从第一层开始,从上到下逐层遍历,同层按从左到右的顺序遍历
在这里插入图片描述
前序:ABDEFGC
中序:DBFEGAC
后序:DFGEBCA

存储结构

顺序存储结构

方法:
1、将二叉树按照完全二叉树编号
2、然后用一维数组存储该二叉树,其中无结点的位置使用NULL表示
缺点:
浪费大量空间

二叉链表

在这里插入图片描述

template <class T>
struct Node
{
 T data;
 Node<T>* lch;
 Node<T>* rch;
};
三叉链表

在这里插入图片描述

template <class T> class Node
{
public:
 T data;
 Node<T>* parent;
 Node<T>* lchild;
 Node<T>* rchild;
};

实现

声明

递归算法1 :

template<class T>
struct BiNode//这里采用的是二叉链表法
{
    T data;
    BiNode<T> *lchild;
    BiNode<T> *rchild;
}template<class T>
class BiTree
{
private:
    void Create(BiNode<T> *&R,T data[],int i,int n)//创建二叉树  
    void Release(BiNode<T> *R);//释放二叉树
public:
    BiNode<T> *root;             //根结点
    BiTree():root(NULL){}        //空构造函数
    BiTree(T data[],int n);      //构造函数
    void PreOrder(BiNode<T>*R);  //前序遍历
    void InOrder(BiNode<T>*R);   //中序遍历
    void PostOrder(BiNode<T>*R); //后序遍历
    void LevelOrder(BiNode<T>*R);//层序遍历
    ~BiTree();                   //析构函数
}

非递归算法:

template<class T>
class SNode
{
public:
BiNode <T>*ptr;
int tag;    //栈结点标记,1为左子树,2为右子树
}
关键算法
1、创建

以顺序存储结构作为建立二叉树的输入,根据二叉树的定义,分三步建树:
1、建立根结点
2、建立左子树
3、建立右子树

在这里插入图片描述

template <class T>
void BiTree<T>::Create(BiNode<T>* &R, T data[], int i, int n) //i表示位置,从1开始
{ 
 if ((i <= n) && (data[i - 1] != '0'))
 {
  R = new BiNode<T>;                     //创建根结点
  R->data = data[i - 1];
  R->lchild = NULL;
  R->rchild = NULL;
  Create(R->lchild, data, 2 * i, n);    //创建左子树
  Create(R->rchild, data, 2 * i + 1, n);//创建右子树
 }
}
template<class T>
BiTree<T>::BiTree(T data[], int n)
{
 Create(root, data, 1, int n);
}
2、前序、中序、后序遍历的实现

前序
递归算法:

template<class T>
void BiTree<T>::PreOrder(BiNode<T> *Root)
{
    if(Root != NULL)
    {
      cout<<Root->data;      //访问结点
      PreOrder(Root->lchild);//遍历左子树
      PreOrder(Root->rchild);//遍历右子树
    }
}

非递归算法1:

栈顶元素永远为当前元素的父结点,设R为当前访问的结点,则:
(1)若R!= NULL,访问R并人栈,调用R= R-> lchild(设R标记为1)返回(1)。
(2)若R= = NULL,重新设R=栈顶元素:
①若R标记=2,说明右子树返回,R出栈,重新设R=栈顶元素,返回①;
②若R标记-1,说明左子树返回,调用R=R-> rchlid(设R标记为2)返回(1)。
反复执行上述操作,直到栈空,程序结束
在这里插入图片描述

template<class T>
void BiTree<T>::PreOrder(BiNode<T> *R)//设R为当前访问的结点
{
 BiNode<T>  S[100];
 int top = -1;
 do 
 {
  while (R != NULL)//(1)若R!= NULL,访问R并人栈,调用R= R-> lchild(设R标记为1)返回(1)
  {
   S[++top].R = R;
   S[top].tag = 1;
   cout << R->data;
   R = R->lch;
  }
  //(2)若R= = NULL,重新设R=栈顶元素:
  while ((top != -1) && (S[top].tag == 2))top--;//①若R标记=2,说明右子树返回,R出栈,重新设R=栈顶元素,返回①;
  if ((top != -1) && S[top].tag == 1)//②若R标记-1,说明左子树返回,调用R=R-> rchlid(设R标记为2)返回(1)。
  {
   R = S[top].R->rch;
   S[top].tag = 2;
  }
 } while (top != 1);
}

非递归算法2(考虑到1内部过于复杂,于是有基于1的优化):

问题分析:
二叉树前序遍历非递归的关键
在前序遍历完某结点的左子树后,找到该结点的右子树的根!
这就需要栈:
1)保存当前结点;
2)访问当前结点的左子树
3)访问当前结点左子树完毕后,当前结点出栈
4)访问其右子树

在这里插入图片描述

伪代码:
设R为当前访问的结点
(1)如果当前结点非空
访问当前结点
当前结点入栈;
将当前结点的左孩子作为当前结点;
(2)如果当前结点为空
栈顶结点出栈,
将该结点的右孩子作为当前结点;
反复执行(1)(2),直到当前结点=NULL && 栈空.

template<class T>
void BiTree<T>::PreOrder(BiNode<T> *R)//设R为当前访问的结点
{
    BiNode<T>  S[100];
    int top = -1;
     while((top != -1)||(R != NULL))//反复执行(1)(2),直到当前结点=NULL && 栈空.
    {
        if(R != NULL)               //(1)如果当前结点非空
        {
            cout<<R->data;          //访问当前结点
            S[++top] = R;           //当前结点入栈
            R = R->lchild;          //将当前结点的左孩子作为当前结点
        }
        else                        //(2)如果当前结点为空
        {
            R = S[top--];           //栈顶结点出栈
            R = R->rchild;          //将该结点的右孩子作为当前结点
        }
    }
}

中序
递归算法:

template<class T>
void BiTree<T>::InOrder(BiNode<T> *Root)
{
 if (Root != NULL)
 {
  PreOrder(Root->lchild);//遍历左子树
  cout << Root->data;    //访问结点
  PreOrder(Root->rchild);//遍历右子树
 }
}

非递归算法1:

伪代码
伪代码:(栈顶元素永远为当前结点的父结点)
(1)若R != NULL,R入栈,调用 R=R->lchild(设R标记为1) 返回(1)
(2)若 R==NULL,重新设 R=栈顶元素
①若R标记为2,说明右子树返回,R出栈,重新设R=栈顶元素,返回(1)
②若R标记为1,说明左子树返回,访问R,并调用 R=R->rchild(设R标记为2),返回(1)
反复执行上述操作直到栈空
在这里插入图片描述

template<class T>
void BiTree<T>::InOrder(BiNode<T> *R)
{
    SNode<T> S[100];  //栈
    int top = -1;    //栈顶指针
    do 
    {
        while(R != NULL)   //入栈,设置遍历左子树
        {
            S[++top].R = R;
            S[top].tag = 1;
            R = R->lchild;
        }
        while((top != -1)&&(S[top].tag == 2))  top--; //出栈
        if((top != 1)&&(S[top].tag == 1))   //访问栈顶元素
        {                                   //遍历右子树
            cout<<S[top].R->data;
            R = S[top].R->rchild;
            S[top].tag = 2;
        }
    }while(top != -1);
}

非递归算法2:
在这里插入图片描述

伪代码
(1)如果当前结点非空
当前结点入栈;
将当前结点的左孩子作为当前结点;
(2)如果当前结点为空
栈顶结点出栈,
访问当前结点
将该结点的右孩子作为当前结点;
反复执行1)2),直到当前结点NULL && 栈空

template <class T>
void BiTree<T>::InOrder(Node<T> *R)
{
 Stack<Node<T>*> S;
 while (!S.IsEmpty() || (R != NULL))//反复执行1)2),直到当前结点NULL && 栈空
 {
  if (R != NULL)    //(1)如果当前结点非空
  {
   S.Push(R);       //当前结点入栈;
   R = R->lchild;   //将当前结点的左孩子作为当前结点;
  }
  else              //(2)如果当前结点为空
  {
   R = S.Pop();     //栈顶结点出栈
   cout << R->data; //访问当前结点
   R = R->rchild;   //将该结点的右孩子作为当前结点
  }
 }
}

后序
递归算法:

template<class T>
void BiTree<T>::PostOrder(BiNode<T> *Root)
{
 if (Root != NULL)
 {
  PreOrder(Root->lchild);//遍历左子树
  PreOrder(Root->rchild);//遍历右子树
  cout << Root->data;    //访问结点
 }
}

非递归算法1:

伪代码:栈顶元素永远为当前结点的父结点
(1)若R != NULL,R入栈,调用 R=R->lchild(设R标记为1) 返回(1)
(2)若 R==NULL,重新设 R=栈顶元素
①若R标记为2,说明右子树返回,R出栈,重新设R=栈顶元素,返回(1)
②若R标记为1,说明左子树返回,访问R,并调用 R=R->rchild(设R标记为2),返回(1)
反复执行上述操作直到栈空
在这里插入图片描述

template<class T>
void BiTree<T>::PostOrder(BiNode<T> *R)
{
    SNode<T> S[100];
    int top= -1;
    do 
    {
        while(R != NULL)
        {
            S[++top].R = R;
            S[top].tag = 1;
            R = R->lchild;
        }
        while((top != -1)&&(S[top].tag == 2)) 
        { 
            cout<<S[top].R->data;
            top--;
        }
        if((top != 1)&&(S[top].tag == 1))
        {
            R = S[top].R->rchild;
            S[top].tag = 2;
        }
    }while(top != -1);
}

非递归算法2:
无法实现,父结点在子结点后被访问,无法提前出栈。

3、层序遍历的实现

在这里插入图片描述

根结点非空,入队。
如果队列不空
{
队头元素出队
访问该元素
若该结点的左孩子非空,则左孩子入队;
若该结点的右孩子非空,则右孩子入队;
}

template<class T>
void BiTree<T>::LevelOrder(BiNode<T> *R)
{
  BiNode<T> *queue[MAXSIZE];
  int f=0,r=0;               //初始化空队列
  if(R != NULL)
    queue[++r] = R;          //根结点入队
  while(f != r)
  {
    BiNode<T> *p=queue[++f]; //队头元素出队
    cout<<p->data;//出队打印
    if(p->lchild != NULL)
      queue[++r] = p->lchild;//左孩子入队
    if(p->rchild != NULL)
      queue[++r] = p->rchild;//右孩子入队
  }
}
4、析构函数的实现

采用后序遍历的方式,防止内存泄漏

template < class T >
void BiTree<T>::Release(Node<T>  *Root)
{
 if (Root != NULL)
 {
  Release(Root->lchild);    // 释放左子树
  Release(Root->rchild);    // 释放右子树
  delete Root;              // 释放根结点
 }
}
template < class T >
void BiTrees<T>::~BiTree()
{
 Release(root);
}

下一篇 森林


  1. 1、所有递归函数都可以改写成效率高的非递归函数 ;2、递归调用普遍规律:(1)形参为局部变量(2)函数调用实参入栈(3)函数调用完毕,实参出栈,自动返回上一级 ↩︎

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值