数据结构 | 第5章 二叉树(中)

98 阅读10分钟

5.2 编码树

5.2.1 二进制编码

  • 编码(encoding):在加载到信道上之前,信息被转换为二进制形式的过程
  • 解码(decoding):经信道抵达目标后再由二进制编码恢复原始信息的过程

    image-20220702213814687.png

生成编码表:编码与解码即映射与逆向映射

二进制编码

二进制解码

问题:解码歧义;解决方法:前缀无歧义编码 (prefix-free code),简称PFC编码

5.2.2 二叉编码树

  • 跟通路与节点编码:

    从根节点到每个节点的通路是唯一的,故可以为各节点v赋予一个互异的二进制串,称为根通路串(root path string),记作rps(v)。|rps(v)| = depth(v)。

  • PFC编码树:

image-20220703203547244.pngimage-20220703203720678.png

image-20220703170459876.png

左5.1右5.3

5.3中编码方案导致解码歧义的根源在于:在其编码树中字符'M'是'S'的父亲。

实现PFC编码的简明策略:只要所有字符都对应于叶节点,歧义现象即自然消除。

  • 基于PFC编码树的解码:

    从根出发,不断深入下一层,直至抵达叶节点;然后重新返回到树根,并继续扫描剩余部分。

5.3 二叉树的实现

作为图的特殊形式,二叉树的基本组成单元是节点与边;作为数据结构,其基本的组成实体是二叉树节点(binary tree node),而边则对应于节点之间的相互引用。

5.3.1 二叉树节点

  • 二叉树节点BinNode模板类:
 0001 #if defined( DSA_REDBLACK )
 0002 #define stature(p) ((p) ? (p)->height : 0) //红黑树节点的黑高度(NULL视作外部节点,对应于0)
 0003 #else
 0004 #define stature(p) ((p) ? (p)->height : -1) //其余BST中节点的高度(NUll视作空树,对应于-1)
 0005 #endif
 0006 typedef enum { RB_RED, RB_BLACK} RBColor; //节点颜色
 0007 template <typename T> struct BinNode;
 0008 template <typename T> using BinNodePosi = BinNode<T>*; //节点位置
 0009 template <typename T> struct BinNode { //二叉树节点模板类
 0010 // 成员(为简化描述起见统一开放,读者可根据需要进一步封装)
 0011    T data; //数值
 0012    BinNodePosi<T> parent, lc, rc; //父节点及左、右孩子
 0013    int height; //高度(通用)
 0014    int npl; //Null Path Length(左式堆,也可直接用height代替)
 0015    RBColor color; //颜色(红黑树)
 0016 // 构造函数
 0017    BinNode() :
 0018       parent ( NULL ), lc ( NULL ), rc ( NULL ), height ( 0 ), npl ( 1 ), color ( RB_RED ) { }
 0019    BinNode ( T e, BinNodePosi<T> p = NULL, BinNodePosi<T> lc = NULL, BinNodePosi<T> rc = NULL,
 0020              int h = 0, int l = 1, RBColor c = RB_RED ) :
 0021       data ( e ), parent ( p ), lc ( lc ), rc ( rc ), height ( h ), npl ( l ), color ( c ) { }
 0022 // 操作接口
 0023    int size(); //统计当前节点后代总数,亦即以其为根的子树的规模
 0024    BinNodePosi<T> insertAsLC ( T const & ); //作为当前节点的左孩子插入新节点
 0025    BinNodePosi<T> insertAsRC ( T const & ); //作为当前节点的右孩子插入新节点
 0026    BinNodePosi<T> succ(); //取当前节点的直接后继
 0027    template <typename VST> void travLevel ( VST& ); //子树层次遍历
 0028    template <typename VST> void travPre ( VST& ); //子树先序遍历
 0029    template <typename VST> void travIn ( VST& ); //子树中序遍历
 0030    template <typename VST> void travPost ( VST& ); //子树后序遍历
 0031 // 比较器、判等器(各列其一,其余自行补充)
 0032    bool operator< ( BinNode const& bn ) { return data < bn.data; } //小于
 0033    bool operator== ( BinNode const& bn ) { return data == bn.data; } //等于
 0034 };
  • 成员变量

BinNode节点由多个成员变量组成,它们分别记录了当前节点的父亲和孩子的位置、节点内存放的数据以及节点的高度等指标,这些都是二叉树相关算法赖以实现的基础。

image-20220703210859829.png

比如,v.parent = NULL当且仅当v是根节点,而v.lChild = v.rChlid = NULL当且仅当v是叶节点。

  • 快捷方式

在BinNode模板类各接口以及后续相关算法的实现中,将频繁检查和判断二叉树节点的状态与性质,有时还需要定位与之相关的(兄弟、叔叔等)特定节点,为简化算法描述同时增强可读性,不妨如下所示将其中常用功能以宏的形式加以整理归纳

 0001 /******************************************************************************************
 0002  * BinNode状态与性质的判断
 0003  ******************************************************************************************/
 0004 #define IsRoot(x) ( ! ( (x).parent ) )
 0005 #define IsLChild(x) ( ! IsRoot(x) && ( & (x) == (x).parent->lc ) )
 0006 #define IsRChild(x) ( ! IsRoot(x) && ( & (x) == (x).parent->rc ) )
 0007 #define HasParent(x) ( ! IsRoot(x) )
 0008 #define HasLChild(x) ( (x).lc )
 0009 #define HasRChild(x) ( (x).rc )
 0010 #define HasChild(x) ( HasLChild(x) || HasRChild(x) ) //至少拥有一个孩子
 0011 #define HasBothChild(x) ( HasLChild(x) && HasRChild(x) ) //同时拥有两个孩子
 0012 #define IsLeaf(x) ( ! HasChild(x) )
 0013 
 0014 /******************************************************************************************
 0015  * 与BinNode具有特定关系的节点及指针
 0016  ******************************************************************************************/
 0017 #define sibling( p ) ( IsLChild( * (p) ) ? (p)->parent->rc : (p)->parent->lc ) /*兄弟*/
 0018 #define uncle( x ) ( sibling( (x)->parent ) ) /*叔叔*/
 0019 #define FromParentTo( x ) /*来自父亲的引用*/ \
 0020    ( IsRoot(x) ? _root : ( IsLChild(x) ? (x).parent->lc : (x).parent->rc ) )

5.3.2 二叉树节点操作接口

插入孩子节点:

 0001 template <typename T> BinNodePosi<T> BinNode<T>::insertAsLC ( T const& e )
 0002 { return lc = new BinNode ( e, this ); } //将e作为当前节点的左孩子插入二叉树
 0003 
 0004 template <typename T> BinNodePosi<T> BinNode<T>::insertAsRC ( T const& e )
 0005 { return rc = new BinNode ( e, this ); } //将e作为当前节点的右孩子插入二叉树

image-20220704100431138.png

二叉树中序遍历算法的统一入口:

 0001 template <typename T> template <typename VST> //元素类型、操作器
 0002 void BinNode<T>::travIn ( VST& visit ) { //二叉树中序遍历算法统一入口
 0003    switch ( rand() % 5 ) { //此处暂随机选择以做测试,共五种选择
 0004       case 1: travIn_I1 ( this, visit ); break; //迭代版#1
 0005       case 2: travIn_I2 ( this, visit ); break; //迭代版#2
 0006       case 3: travIn_I3 ( this, visit ); break; //迭代版#3
 0007       case 4: travIn_I4 ( this, visit ); break; //迭代版#4
 0008       default: travIn_R ( this, visit ); break; //递归版
 0009    }
 0010 }

5.3.3 二叉树

在BinNode模板类的基础之上,可如代码5.5所示定义二叉树BinTree模板类:

 0001 #include "BinNode.h" //引入二叉树节点类(代码5.5)
 0002 template <typename T> class BinTree { //二叉树模板类
 0003 protected:
 0004    int _size; BinNodePosi<T> _root; //规模、根节点
 0005    virtual int updateHeight ( BinNodePosi<T> x ); //更新节点x的高度
 0006    void updateHeightAbove ( BinNodePosi<T> x ); //更新节点x及其祖先的高度
 0007 public:
 0008    BinTree() : _size ( 0 ), _root ( NULL ) { } //构造函数
 0009    ~BinTree() { if ( 0 < _size ) remove ( _root ); } //析构函数
 0010    int size() const { return _size; } //规模
 0011    bool empty() const { return !_root; } //判空
 0012    BinNodePosi<T> root() const { return _root; } //树根
 0013    BinNodePosi<T> insert ( T const & ); //插入根节点
 0014    BinNodePosi<T> insert ( T const &, BinNodePosi<T> ); //插入左孩子
 0015    BinNodePosi<T> insert ( BinNodePosi<T>, T const & ); //插入右孩子
 0016    BinNodePosi<T> attach ( BinTree<T> * &, BinNodePosi<T> ); //接入左子树
 0017    BinNodePosi<T> attach ( BinNodePosi<T>, BinTree<T> * & ); //接入右子树
 0018    int remove ( BinNodePosi<T> ); //子树删除
 0019    BinTree<T>* secede ( BinNodePosi<T> ); //子树分离
 0020    template <typename VST> //操作器
 0021    void travLevel ( VST& visit ) { if ( _root ) _root->travLevel ( visit ); } //层次遍历
 0022    template <typename VST> //操作器
 0023    void travPre ( VST& visit ) { if ( _root ) _root->travPre ( visit ); } //先序遍历
 0024    template <typename VST> //操作器
 0025    void travIn ( VST& visit ) { if ( _root ) _root->travIn ( visit ); } //中序遍历
 0026    template <typename VST> //操作器
 0027    void travPost ( VST& visit ) { if ( _root ) _root->travPost ( visit ); } //后序遍历
 0028    bool operator< ( BinTree<T> const& t ) //比较器(其余自行补充)
 0029    { return _root && t._root && lt ( _root, t._root ); }
 0030    bool operator== ( BinTree<T> const& t ) //判等器
 0031    { return _root && t._root && ( _root == t._root ); }
 0032 }; //BinTree
  • 更新高度:
 0001 template <typename T> int BinTree<T>::updateHeight ( BinNodePosi<T> x ) //更新节点x高度
 0002 { return x->height = 1 + max ( stature ( x->lc ), stature ( x->rc ) ); } //具体规则,因树而异
 0003 
 0004 template <typename T> void BinTree<T>::updateHeightAbove ( BinNodePosi<T> x ) //更新高度
 0005 { while ( x ) { updateHeight ( x ); x = x->parent; } } //从x出发,覆盖历代祖先。可优化

更新每一节点本身的高度,只需执行两次getHeight()操作、两次加法以及两次取最大操作,不过常数时间,故updateHeight()算法总体运行时间也是O(depth(v) + 1),其中depth(v)为节点v的深度。

  • 节点插入:
 0001 template <typename T> BinNodePosi<T> BinTree<T>::insert ( T const& e )
 0002 { _size = 1; return _root = new BinNode<T> ( e ); } //将e当作根节点插入空的二叉树
 0003 
 0004 template <typename T> BinNodePosi<T> BinTree<T>::insert ( T const& e, BinNodePosi<T> x )
 0005 { _size++; x->insertAsLC ( e ); updateHeightAbove ( x ); return x->lc; } //e插入为x的左孩子
 0006 
 0007 template <typename T> BinNodePosi<T> BinTree<T>::insert ( BinNodePosi<T> x, T const& e )
 0008 { _size++; x->insertAsRC ( e ); updateHeightAbove ( x ); return x->rc; } //e插入为x的右孩子

image-20220704104552897.png

  • 子树接入
 0001 template <typename T> //将S当作节点x的左子树接入二叉树,S本身置空
 0002 BinNodePosi<T> BinTree<T>::attach ( BinTree<T>* &S, BinNodePosi<T> x ) { //x->lc == NULL
 0003    if ( x->lc = S->_root ) x->lc->parent = x; //接入
 0004    _size += S->_size; updateHeightAbove ( x ); //更新全树规模与x所有祖先的高度
 0005    S->_root = NULL; S->_size = 0; release ( S ); S = NULL; return x; //释放原树,返回接入位置
 0006 }
 0007 
 0008 template <typename T> //将S当作节点x的右子树接入二叉树,S本身置空
 0009 BinNodePosi<T> BinTree<T>::attach ( BinNodePosi<T> x, BinTree<T>* &S ) { //x->rc == NULL
 0010    if ( x->rc = S->_root ) x->rc->parent = x; //接入
 0011    _size += S->_size; updateHeightAbove ( x ); //更新全树规模与x所有祖先的高度
 0012    S->_root = NULL; S->_size = 0; release ( S ); S = NULL; return x; //释放原树,返回接入位置
 0013 }

image-20230214100631278.png

若二叉树T中节点x的右孩子为空,则attachAsRC()接口首先将待植入的二叉树S的根节点作为x的右孩子,同时令x作为该根节点的父亲;然后,更新全树规模以及节点x所有祖先的高度;最后,将树S中除已接入的各节点之外的其余部分归还系统。(即最后会舍弃树 S自己内部的那一套逻辑,而融入整个大系统(树 T)

  • 子树删除

子树删除的过程与子树接入的过程恰好相反。这里的恰好相反是指每一步执行的性质一样,但执行的内容完全相反。

 0001 template <typename T> //删除二叉树中位置x处的节点及其后代,返回被删除节点的数值
 0002 int BinTree<T>::remove ( BinNodePosi<T> x ) { //assert: x为二叉树中的合法位置
 0003    FromParentTo ( *x ) = NULL; //切断来自父节点的指针
 0004    updateHeightAbove ( x->parent ); //更新祖先高度
 0005    int n = removeAt ( x ); _size -= n; return n; //删除子树x,更新规模,返回删除节点总数
 0006 }
 0007 template <typename T> //删除二叉树中位置x处的节点及其后代,返回被删除节点的数值
 0008 static int removeAt ( BinNodePosi<T> x ) { //assert: x为二叉树中的合法位置
 0009    if ( !x ) return 0; //递归基:空树
 0010    int n = 1 + removeAt ( x->lc ) + removeAt ( x->rc ); //递归释放左、右子树
 0011    release ( x->data ); release ( x ); return n; //释放被摘除节点,并返回删除节点总数
 0012 } //release()负责释放复杂结构,与算法无直接关系,具体实现详见代码包
  • 子树分离

子树分离的过程与以上的子树删除过程基本一致,不同之处在于,需要对分离出来的子树重新封装,并返回给上层调用者。

 0001 template <typename T> //二叉树子树分离算法:将子树x从当前树中摘除,将其封装为一棵独立子树返回
 0002 BinTree<T>* BinTree<T>::secede ( BinNodePosi<T> x ) { //assert: x为二叉树中的合法位置
 0003    FromParentTo ( *x ) = NULL; //切断来自父节点的指针
 0004    updateHeightAbove ( x->parent ); //更新原树中所有祖先的高度
 0005    BinTree<T>* S = new BinTree<T>; S->_root = x; x->parent = NULL; //新树以x为根
 0006    S->_size = x->size(); _size -= S->_size; return S; //更新规模,返回分离出来的子树
 0007 }
  • 复杂度

就二叉树拓扑结构的变化范围而言,以上算法均只涉及局部的常数个节点。因此,除了更新祖先高度和释放节点等操作,只需常数时间。

PS:计算机底层:函数调用栈,不断递归调用

“开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 8 天,点击查看活动详情