5.2 编码树
5.2.1 二进制编码
- 编码(encoding):在加载到信道上之前,信息被转换为二进制形式的过程
- 解码(decoding):经信道抵达目标后再由二进制编码恢复原始信息的过程
生成编码表:编码与解码即映射与逆向映射
二进制编码
二进制解码
问题:解码歧义;解决方法:前缀无歧义编码 (prefix-free code),简称PFC编码。
5.2.2 二叉编码树
-
跟通路与节点编码:
从根节点到每个节点的通路是唯一的,故可以为各节点v赋予一个互异的二进制串,称为根通路串(root path string),记作rps(v)。|rps(v)| = depth(v)。
-
PFC编码树:
左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节点由多个成员变量组成,它们分别记录了当前节点的父亲和孩子的位置、节点内存放的数据以及节点的高度等指标,这些都是二叉树相关算法赖以实现的基础。
比如,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作为当前节点的右孩子插入二叉树
二叉树中序遍历算法的统一入口:
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的右孩子
- 子树接入
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 }
若二叉树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 天,点击查看活动详情”