概念
树是按照层次关系组织数据项,可以把Vector和List的静动态优点结合起来,List
树是特殊的图,图指定一个节点为根后就是有根树,就出现了以下定义:父亲孩子兄弟,dgree 边数= 所有顶点的度数和=顶点数-1 定义次序即有序树
路径(连通性)+环路(无环)
路径:k+1个节点通过n条边依次相连 路径长度:边数
连通图:节点均有路径 无环图:不含环路
树 :无环,所以边数不能太大 ;连通,所以边数不能太小 所以会有几个特殊的图:无环连通图,极小连通图,极大无环图
任一节点与根之间存在唯一路径 ,
深度+层次
不致歧义时,路径 节点和子树可相互指代 path(v)~ v~subtree(v);depth(v)=|path(v)|
path(v)以上节点(祖先)保持前继的唯一性,后继不唯一
根节点为公共祖先,没有后代的节点成为叶子
叶子节点深度的最大者成为树的高度
特别的,空树高度取作-1
depth(v)+height(v)<=height(T)
树的表示
接口 :静态的获取查找根节点某个孩子 遍历,动态的插入和移除
父节点
任一节点有且仅有一个父节点(前继唯一),我们可以定义一个序列包含 所在顺序秩 数据 其父节点的秩
空间性能O(n)
时间性能 O(1)~O(n)
孩子节点
既然性能都浪费在了寻找孩子上,那能不能对其改进呢?
对孩子节点以vector或者列表的方式构造一个数据集(children)替代原指向父节点的方法
却失去了找寻父节点的优势
父节点+孩子节点 :rank data parent children,兼顾两方面
children的数据集大小怎么设置呢?
长子+兄弟
每个节点只记录 纵:firstChild() 横:nextSibling()
二叉树概述
一般二叉树
binary tree:节点度数不超过二的树,同一节点的孩子和子树以左右区分(隐含有序)
基数: 深度为k的节点,至多2^k个 由此可以推出n个节点高度为h的二叉树中节点个数为
真二叉树
节点出度为0或2,当某一个节点的出度为1时添加一个假象节点
通过二叉树描述多叉树
将一个一般的有根有序树通过长子兄弟表示法转成二叉树
二叉树
BinTree实现
数据域:data 引用域:lChild parent rChild 指标:height npl clolor
BinNode模板类
#define BinNodePosi(T) BinNode<T>* //通过类型参数T生成一个类模板BinNode<T>,BinNode<T> *是指向该类型的指针类型 BinNode<T>* insert()函数返回类型是指向类的指针对象
template <typename 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 <typename VST> void travLevel(VST &);//子树层次遍历
template <typename VST> void travPre(VST &);
template <typename VST> void travIn(VST &);
template <typename VST> void travPost(VST &);
}
**BinNode接口实现**
template <typename T> BinNodePosi(T) BinNode<T>::insertAsLC(T const&e)
{
return lChild=new BinNode(e,this);//this使新结点的parent指向this 将新结点赋给this.lChild 实现了双向链接
template <typename T>
int BinNode <T>::size()
{
int s=1;//计入根节点
if(lChild) s+=lChild->size();
if(lChild) s+=rChild->size();//递归计入右子树规模
return s;
}
BinTree模板类
template <typename T> class BinTree
{
protected:
int _size;
BinNodePosi(T) _root;//根节点
virtural int updateHeight( BinNodeposi(T) x);//使用虚函数便于派生修改 //更新节点x的高度
void updateHeightAbove( BinNodePosi(T) x);//更新x及祖先的高度
public:
int size() const {return _size;}
bool empty () const{ return !_root;}//判空
BinNodePosi(T) root()const { return _size;}//返回树根
/****子树接入 删除 分离接口***/
/***** 遍历接口****/
}
高度更新
#define stature(p) ((p)?(p)->height:-1) //节点高度约定空树为-1
template <typename T>//更新节点x高度,具体规则因树不同
int BinTree <T>::updateHeight ( BinNodePosi (T) x)
{
return x->height=1+max(stature(x->lChild),stature(x->rChild));
}
template <typename T>//更新v及历代祖先高度
void BinTree<T>::updateHeightAbove(BinNodePosi (T)x)
{
while(x)//可优化,一旦高度未变即可停止
{ updateHeight(x); x=x->parent;}
}//O(n=depth(x))
节点插入
template <typename T> BinNodePosi(T)
BinTree::insertAsRC(BinNodePosi(T) x,T const &e)
{
_size++;x->insertAsRC(e);//x祖先高度可能增加,其余节点必然不变
updateHeightAbove(x);
return x_>rChild;
}
求解策略,借用之前的方法(线性) 通过遍历把半线性转化为线性结构, 遍历是把各节点均访问且只访问一次,有先序中后序 层次(广度)
递归实现
temolate <typename T,typename VST>
void traverse(BinNodePosi(T)x, VST & visit)
{
if(!x) return;
visit (x->data);
traverse(x->lChild,visit);
traverse(x->rChild,visit);
}//渐进意义上的O(n),因为递归是采用固定的格式 不如采用迭代,量体裁衣设计出栈中更小的帧
迭代1
template <typename T,typename 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(x0->lChild);///左孩子后入先出
}
} //右顾左看,左看且弹出
显然可以看出,从根节点开始不断访问左孩子并生成一个包含对应右孩子的栈,再上溯到顶(即栈底)
迭代2
template <typename T,typename VST>
static void visitAlongLeftBranch(BinNodePosi(T) x,VST &visit, Stack<BinNodePosi(T)>&s)
{
while(x)
{
visit(x->data);//访问当前节点
S.push(x->rChild);//右孩子入栈将来逆序出栈
x=x->lChild;//沿左侧连下行
}
}
void travPre_I2(BinNodePosi(T) x,VST & visit)
{
Stack<BinNodePosi(T)>S;//辅助栈
while(true)
{
visitAlongLeftBranch(x,visit,S);//访问子树x的左侧连,右子树入栈缓冲
if(S.empty()) break;//栈空退出
x=S.pop();//弹出下一子树的根
}//#pop=#puch=#visit=O(n)=分摊O(1)
}
中序遍历
temolate <typename T,typename VST>
void traverse(BinNodePosi(T)x, VST & visit)
{
if(!x) return;
traverse(x->lChild,visit);
visit (x->data);
traverse(x->rChild,visit);
}
观察递归实例,访问左侧链末端 然后是右子树 访问左侧链倒数第二个然后是右子树
template <typename T,typename VST>
static void goAlongLeftBranch(BinNodePosi(T) x,VST &visit, Stack<BinNodePosi(T)>&s)
{
while(x) {S.push(x);x=x->lChild;}//左侧不断入栈
}
template <typename T,typename V> void ttravIn_I1( BinNodePosi(T) x,V& visit)
{
Stack <BinNodePosi (T)> S;//辅助栈
while(true)
{
goAlongLeftBranch(x,s);//从当前节点出发,逐批入栈
if(S.empty()) break;//直到所有节点处理完毕
x= S.pop();//x的左子树为空或已遍历
visit(x->data);
x=x->rChild;//再转向右子树
}
}
分摊分析:外部循环O(n), 内部循环总共要push n次 所以分摊仍是O(n);性能优于递归
层次分析
借助队列,规则 按深度从上向下,水平从左到右
template <typename T> template <typename VST>
viod 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(HasRChild(*x)) Q.enqueue(x->rChild);//右孩子入队
}
}
重构
[先序|后序]+中序 还原得到树的拓扑结构
先和后,当某一子树为空得到的序列 无法得出是哪个子树为空;当树是真二叉树时可以确认拓扑结构,前序遍历左子树的第一个元素在后序左子树的末后