邓俊辉《数据结构》 学习笔记-第五章 二叉树 (自用)
1.树的基础概念(特殊的图)
有根树:
有序树:
路径+环路:
连通+无环:
层次+深度:根节点是所有节点的公共祖先,深度为0,没有后的节点称作叶子(想想真实的树,很形象)
特别的:空树的高度为-1
2.树的表示方法
还是从接口开始:
这里有4种表示法:
1.父节点表示法(找父亲和根节点很快,但是孩子和兄弟的访问就得遍历整颗树)
2.孩子节点表示法(同样的,找孩子很快)
3.父节点+孩子节点表示法(结合了以上两种表示法的优点)
4.长子+兄弟表示法(这种表示方法很重要,是树转换成二叉树的关键)
3.二叉树的概述
基数:
真二叉树:就是不存在出度为1的节点(注意与满二叉树区别)
描述多叉树:这个很妙
使用上面树的表示方法的第四种【长子+兄弟】表示法,表示出来之后,整体顺势旋转45°即可!
4.二叉树的实现
首先定义BinNode模板类
#define BinNodePosi(T) BinNode<T>*//节点位置
template<class T> struct BinNode{
BinNodePosi(T) parent ,lChild,rChild;//父亲,孩子
T data;int height;int size();//高度,子树规模
//插入接口
BinNodePosi(T) insertAsLC(T const &);
BinNodePosi(T) insertAsRC(T const &);
//(中序遍历意义下)返回当前节点的直接后继
BinNodePosi(T) succ();
//遍历接口
template<class VST> void travLevel(VST &);
template<class VST> void travPre(VST &);
template<class VST> void travIn(VST &);
template<class VST> void travPost(VST &);
};
//BinNode接口实现
template<class T>
BinNodePosi(T) BinNode<T>::insertAsLC(T const & e)
{return lChild=new BinNode(e,this);}//Data(e),parent(this)
template<class T>
BinNodePosi(T) BinNode<T>::insertAsRC(T const & e)
{return rChild=new BinNode(e,this);}
template<class T>
int BinNode<T>::size(){
int s=1;//计入本身
if(lChild) s+=lChild->size();
if(rChild) s+=rChild->size();
return s;
}//O(n=|size|)
然后定义BinTree模板类
//定义BinTree模板类
template<class T> class BinTree{
protected:
int _size;
BinNodePosi(T) _root;//根节点
virtual int updataHeight(BinNodePosi(T) x);//更新节点x的高度
void updateHeightAbove(BinNodePosi(T) x);//更新x及祖先的高度
void release(BinTree<T>* &t);
static int removeAt(BinNodePosi(T) x);
public:
int size() const {return _size;}
bool empty() const {return !_root;}
BinNodePosi(T) root() const {return _root;}
/*...子树接入、删除、分离接口...*/
BinNodePosi(T) insertAsRc(BinNodePosi(T) x,T const e);//节点接入
BinNodePosi(T) attachAsRCT(BinNodePosi(T) x,BinTree<T>* & S);
int remove(BinNodePosi(T) x);
BinNodePosi(T) secede(BinNodePosi(T) x);
/*...遍历接口...*/
};
实现各个接口
4.1 高度更新
//高度更新
#define stature(p) ((p) ? (p)->height : -1)
template <class T>
int BinTree<T>::updataHeight(BinNodePosi(T) x){
return x->height=1+
max(stature(x->lChild),stature(x->rChild));
}
template <class T>
void BinTree<T>::updateHeightAbove(BinNodePosi(T) x){
while(x){
updataHeight(x);
x=x->parent;
}
}
4.2 节点插入
//节点插入
template <class T>
BinNodePosi(T) BinTree<T>::insertAsRc(BinNodePosi(T) x,T const e){
_size++;
x->insertAsRc(e);
updateHeightAbove(x);
return x->rChild;
}
4.3 子树接入
//子树接入
template<class T>
BinNodePosi(T) BinTree<T>::attachAsRCT(BinNodePosi(T) x,BinTree<T>* & S){
if(x->rChild=S->_root) x->rChild->parent=x;//接入
_size+=S->_size;//更新规模
updateHeightAbove(x);
S->root=0;S->_size()=0;
release(S);S=0;//释放S
return x;//返回接入的位置
}
template<class T>
void BinTree<T>::release(BinTree<T>* &t){
if(!t) return;
release(t->lChild);
release(t->rChild);
delete [] t;
}
4.4 子树删除
//子树删除
template<class T>
int BinTree<T>::remove(BinNodePosi(T) x){//子树接入的逆过程
FromParentTo(*x)=0;//切断来自父节点的指针
updateHeightAbove(x->parent); //更新祖先高度(其余节点不变)
int n=removeAt(x);_size-=n;//递归删除x及其后代,更新规模
return n;
}
template<class T>
int BinTree<T>::removeAt(BinNodePosi(T) x){
if(!x) return 0;
int n=1+removeAt(x->lChild)+removeAt(x->rChild);
release(x->data);
release(x);
return n;
}
4.5 子树分离
//子树分离
template<class T>
BinNodePosi(T) BinTree<T>::secede(BinNodePosi(T) x){
FromParentTo(*x)=0;//切断来自父节点的指针
updateHeightAbove(x->parent);
//以下对分离的子树做封装
BinTree<T>* S=new BinTree<T>;//创建空树
S->_root=x;
x->parent=0;
S->_size=x->size();
_size-=S->_size;//更新原树的规模
return S;
}
5.二叉树的遍历
有以下四种遍历方式:
5.1 Preorder先序遍历(stack)
递归版本
首先从递归的方面来思考先序遍历,无非是先遍历根节点,然后分别遍历左节点、右节点
template<class T,class VST>
void traverse(BinNodePosi(T) x,VST & visit){
if(!x) return;
visit(x->data);
traverse(x->lChild,visit);
traverse(x->rChild,visit);
}
为了提高效率,改递归为迭代
迭代版本a:
先将根节点推入栈中,然后top()根节点访问的同时将右节点和左节点推入栈中,
下一次再从栈中top()出一个节点访问同时将其右节点和左节点推入栈中,
如此往复直至栈为空
实现:
template<class T,class VST>
void travPre_I1(BinNodePosi(T) x,VST & visit){
stack<BinNodePosi(T)> S;//辅助栈
if(x) S.push(x);//根节点入栈
while(!S.empty()){
x=S.pop();visit(x->data);
if(HasRChild(*x)) S.push(x->rChild);//注意!是右孩子先入栈,因为栈的特性
if(HasLChild(*x)) S.push(x->lChild);
}
}
迭代版本b:
直接访问根结点,并将右孩子推入栈中,然后权力交给根节点的左孩子
实现:
首先编写入栈和访问算法,右孩子反复入栈
template<class T,class VST>
void BinTree<T>::visitAlongLeftBranch(BinNodePosi(T) x,VST & visit,stack<BinNodePosi(T) x> &S){
while(x){
visit(x->data);
S.push(x->rChild);
x=x->lChild;//沿着左侧链下行
}
}
然后实现主函数,主要是出栈
template<class T,class VST>
void travPre_I1(BinNodePosi(T) x,VST & visit){
stack<BinNodePosi(T) S;
while(true){
visitAlongLeftBranch(x,visit,S);//访问子树x的左侧链,右子树入栈
if(S.empty()) break;
x=S.pop();
}
}
5.2 Inprder中序遍历(stack)
迭代版本:(与先序的递归版本类似)
template<class T,class VST>
void traverse(BinNodePosi(T) x,VST & visit){
if(!x) return;
traverse(x->lChild,visit);
visit(x->data);
traverse(x->rChild,visit);
}
``
迭代版本:
反复入栈,,直到到达最左侧分支,然后开始访问,访问完之后权利交给其右孩子
实现:
首先编写左孩子反复入栈的算法
template <class T>
static void goAlongLeftBranch(BinNodePosi(T) x,stack<BinNodePosi(T)> & S){
while(x) {
S.push(x);
x=x->lChild;
}
}
然后编写主算法,在主函数中访问
template <class T,class VST>
void travIn_I1(BinNodePosi(T) x,VST & visit){
stack<BinNodePosi(T)> S;
while(true){
goAlongLeftBranch(x,S);//反复入栈
if(S.empty()) break;
x=S.pop;
visit(x->data);//立即访问!
x=x->rChild;
}
}
5.3 Postorder后序遍历(stack)
递归版本:(与先序遍历类似)
template<class T,class VST>
void traverse(BinNodePosi(T) x,VST & visit){
if(!x) return;
traverse(x->lChild,visit);
traverse(x->rChild,visit);
visit(x->data);
}
``
``
迭代版本:
先编写入栈的代码
template <class T>
static void gotoHLVFL(Stack<BinNodePosi(T)> & S){
while(BinNodePosi(T) x=S.top())//自顶而下反复检查栈顶节点
if(HasLChild(*x)){//尽可能得向左,在此之前
if(HasRChild(*x))//若有右孩子,则
S.push(x->rChild);//优先入栈
S.push(x->lChild);//然后转向左孩子
}
else//实不得已
S.push(x->rChild);//才转向右孩子
S.pop();//返回之前弹出栈顶的空节点
}
再编写主函数,在主函数中访问
template <class T,class VST>
void travIn_I1(BinNodePosi(T) x,VST & visit){
stack<BinNodePosi(T)> S;
if(x) S.push(x);//根节点入栈
while(!S.empty()){
if(S.top()!=x->parent)//栈顶非x之父则必为其右兄弟
gotoHLVFL(S);//在x的右子树中找到HLVFL?这是啥
x=S.pop();
visit(x->data);//访问之
}
}
5.4 Levelorder层次遍历(queue)
理解:就是一层一层的遍历
代码实现
template <class T> template<class VST>
void BinNode<T>::travLevel(VST & visit){
Queue<BinNodePosi(T)> Q;
Q.enqueue(this);//根节点入队
while(!Q.empty())
BinNodePosi(T)x=Q.dequeue();
visit(x->data);//访问
if(HasLChild(*x)) Q.enqueue(x->lChild);//左孩子入队
if(HasLChild(*x)) Q.enqueue(x->lChild);//右孩子入队
}
}