一、树
1.4 二叉树的实现
1.4.1 BinNode模板类
节点基本组成元素:
- 数据域:data
- 引用域:parent,lChild,rChild
- 指标:height,color,npl等
(npl:空节点通路长度(null path length))
下图摘自清华大学《数据结构(C++语言版)》
代码摘自清华大学《数据结构(C++语言版)》
template <typename T> struct BinNode { //二叉树节点模板类
// 成员(为简化描述起见统一开放,读者可根据需要进一步封装)
T data; //数值
BinNodePosi(T) parent; BinNodePosi(T) lc; BinNodePosi(T) rc; //父节点及左、右孩子
int height; //高度(通用)
int npl; //Null Path Length(左式堆,也可直接用height代替)
RBColor color; //颜色(红黑树)
// 构造函数
BinNode() :
parent(NULL), lc(NULL), rc(NULL), height(0), npl(1), color(RB_RED) { }
BinNode(T e, BinNodePosi(T) p = NULL, BinNodePosi(T) lc = NULL, BinNodePosi(T) rc = NULL,
int h = 0, int l = 1, RBColor c = RB_RED) :
data(e), parent(p), lc(lc), rc(rc), height(h), npl(l), color(c) { }
// 操作接口
int size(); //统计当前节点后代总数,亦即以其为根的子树的规模
BinNodePosi(T) insertAsLC(T const&); //作为当前节点的左孩子插入新节点
BinNodePosi(T) insertAsRC(T const&); //作为当前节点的右孩子插入新节点
BinNodePosi(T) succ(); //取当前节点的直接后继
template <typename VST> void travLevel(VST&); //子树层次遍历
template <typename VST> void travPre(VST&); //子树先序遍历
template <typename VST> void travIn(VST&); //子树中序遍历
template <typename VST> void travPost(VST&); //子树后序遍历
// 比较器、判等器(各列其一,其余自行补充)
bool operator< (BinNode const& bn) { return data < bn.data; } //小于
bool operator== (BinNode const& bn) { return data == bn.data; } //等于
BinNodePosi(T) zig(); //顺时针旋转
BinNodePosi(T) zag(); //逆时针旋转
BinNodePosi(T) balance(); //完全平衡化
BinNodePosi(T) imitate(const BinNodePosi(T));
};
注意,代码中的宏定义:
#define stature(p) ((p) ? (p)->height : -1) //节点高度(与“空树高度为-1”的约定相统一)
通过宏定义,重新命名一个新的等价意义上的“高度”,是为了将常规情况下的高度和**退化的情况(节点数为1)及极其退化的情况(空树)**统一起来。
1.4.2 BinNode接口
插入接口
代码摘自清华大学《数据结构(C++语言版)》
template <typename T> BinNodePosi(T) BinNode<T>::insertAsLC(T const& e)
{
return lc = new BinNode(e, this);
} //将e作为当前节点的左孩子插入二叉树
template <typename T> BinNodePosi(T) BinNode<T>::insertAsRC(T const& e)
{
return rc = new BinNode(e, this);
} //将e作为当前节点的右孩子插入二叉树
- 通过BinNode构造方法将元素e封装为一个新的BinNode节点(通过new),并将其parent引用指向当前节点(自下而上的连接)
- 将当前节点的左(右)孩子引用指向新创建的节点
运行时间:O(1)
size()接口
返回当前节点在内的所有后代的总数,亦以其为根的子树的规模
代码摘自清华大学《数据结构(C++语言版)》
template <typename T> int BinNode<T>::size() { //统计当前节点后代总数,即以其为根的子树规模
int s = 1; //计入本身
if (lc) s += lc->size(); //递归计入左子树规模
if (rc) s += rc->size(); //递归计入右子树规模
return s;
}
递归地统计当前节点的左孩子(左子树)的size和右孩子(右子树)的size,二者相加即为当前子树的规模
运行时间:O(n=|size|)
1.4.3 BinTree类
通过内部变量记录当前树的节点总数(规模)_size,以及根节点位置_root
针对节点高度,提供两个更新的接口。注意updateHeight()定义为了虚方法,因为二叉树尤其是二叉搜索树是个庞大的家族,其中的每个成员对于高度的定义即更新的方法都不尽相同,因此定义为虚方法便于派生类对其进行改写。
代码摘自清华大学《数据结构(C++语言版)》
#include "BinNode.h" //引入二叉树节点类
template <typename T> class BinTree { //二叉树模板类
protected:
int _size; BinNodePosi(T) _root; //规模、根节点
virtual int updateHeight(BinNodePosi(T) x); //更新节点x的高度
void updateHeightAbove(BinNodePosi(T) x); //更新节点x及其祖先的高度
public:
BinTree() : _size(0), _root(NULL) { } //构造函数
~BinTree() { if (0 < _size) remove(_root); } //析构函数
int size() const { return _size; } //规模
bool empty() const { return !_root; } //判空
BinNodePosi(T) root() const { return _root; } //树根
BinNodePosi(T) insertAsRoot(T const& e); //插入根节点
BinNodePosi(T) insertAsLC(BinNodePosi(T) x, T const& e); //e作为x的左孩子(原无)插入
BinNodePosi(T) insertAsRC(BinNodePosi(T) x, T const& e); //e作为x的右孩子(原无)插入
BinNodePosi(T) attachAsLC(BinNodePosi(T) x, BinTree<T>* &T); //T作为x左子树接入
BinNodePosi(T) attachAsRC(BinNodePosi(T) x, BinTree<T>* &T); //T作为x右子树接入
int remove(BinNodePosi(T) x); //删除以位置x处节点为根的子树,返回该子树原先的规模
BinTree<T>* secede(BinNodePosi(T) x); //将子树x从当前树中摘除,并将其转换为一棵独立子树
template <typename VST> //操作器
void travLevel(VST& visit) { if (_root) _root->travLevel(visit); } //层次遍历
template <typename VST> //操作器
void travPre(VST& visit) { if (_root) _root->travPre(visit); } //先序遍历
template <typename VST> //操作器
void travIn(VST& visit) { if (_root) _root->travIn(visit); } //中序遍历
template <typename VST> //操作器
void travPost(VST& visit) { if (_root) _root->travPost(visit); } //后序遍历
bool operator< (BinTree<T> const& t) //比较器(其余自行补充)
{
return _root && t._root && lt(_root, t._root);
}
bool operator== (BinTree<T> const& t) //判等器
{
return _root && t._root && (_root == t._root);
}
}; //BinTree
1.4.4 高度更新
节点的高度=其左孩子和右孩子高度的最大者加1
h
e
i
g
h
t
(
v
)
=
1
+
m
a
x
(
h
e
i
g
h
t
(
v
−
>
l
c
)
,
h
e
i
g
h
t
(
v
−
>
r
c
)
)
height(v)=1+max(height(v->lc),height(v->rc))
height(v)=1+max(height(v−>lc),height(v−>rc))
单个节点高度更新(具体规则,因树而异):
代码摘自清华大学《数据结构(C++语言版)》
template <typename T> int BinTree<T>::updateHeight(BinNodePosi(T) x) //更新节点x高度
{
return x->height = 1 + __max(stature(x->lc), stature(x->rc));
} //具体规则,因树而异
高度更新会体现出层层递进的连锁形式,因为当x的高度发生变化,则其所有祖先的高度均有可能发生变化。
因此如果要对全树高度进行更新,需要从某节点出发,向上追溯(遍历)历代祖先,直到抵达其根节点(根节点的父节点为NULL),算法终止(可优化:一旦高度未变,即可终止)。
代码摘自清华大学《数据结构(C++语言版)》
template <typename T> void BinTree<T>::updateHeightAbove(BinNodePosi(T) x) //更新高度
{
while (x) //可优化:一旦高度未变,即可终止
{
updateHeight(x); x = x->parent;
}
} //从x出发,覆盖历代祖先。可优化
算法复杂度:
O
(
n
=
d
e
p
t
h
(
x
)
)
O(n=depth(x))
O(n=depth(x))
该算法可优化:一旦高度未变,即可终止
小小感悟
注意到数据结构的很多接口都对局部和整体进行了各自的封装,如前面的vector、list的remove等先定义范围方法,再将范围缩为一个元素去调用以对单个元素操作。而这儿二叉树的更新单个节点高度updateHeight()和更新该节点及其以上节点高度updateHeightAbove()的两个操作也各自进行了封装,先定义了单个节点高度更新的方法,再通过封装一个循环,将单个节点高度更新的方法向上遍历该节点及其所有祖先,以定义了对该节点及其以上所有节点封装的一个方法。
1.4.5 节点插入
图摘自清华大学《数据结构(C++语言版)》
- 增加树的规模
- 调用BinNode类接口,将目标e封装为一个新的节点x,并在创建x时就以当前节点p作为其父节点。接着,该BinNode类接口通过赋值,将新生成节点x的位置赋给当前节点p此前为空的左(右)孩子引用
- 调用updateHeightAbove()对当前节点p及其所有祖先的高度进行更新
- 返回当前节点p左(右)孩子的位置(即新插入节点x的位置)
代码摘自清华大学《数据结构(C++语言版)》
template <typename T>
BinNodePosi(T) BinTree<T>::insertAsRoot(T const& e)
{
_size = 1; return _root = new BinNode<T>(e);
} //将e当作根节点插入空的二叉树
template <typename T>
BinNodePosi(T) BinTree<T>::insertAsLC(BinNodePosi(T) x, T const& e)
{
_size++; x->insertAsLC(e); updateHeightAbove(x); return x->lc;
} //e插入为x的左孩子
template <typename T>
BinNodePosi(T) BinTree<T>::insertAsRC(BinNodePosi(T) x, T const& e)
{
_size++; x->insertAsRC(e); updateHeightAbove(x); return x->rc;
} //e插入为x的右孩子
//insertAsLC()完全对称,在此省略
题
设二叉树有n个节点,高度为h.在其中插入一个新的节点,高度发生改变的节点个数为:
O
(
d
e
p
t
h
(
x
)
=
O
(
h
)
)
O(depth(x)=O(h))
O(depth(x)=O(h))
新插入节点到根节点的路径上所有节点(即新节点的祖先)高度都有可能变化.