二叉树

概念

树是按照层次关系组织数据项,可以把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);//右孩子入队
	}
}

重构
[先序|后序]+中序 还原得到树的拓扑结构
先和后,当某一子树为空得到的序列 无法得出是哪个子树为空;当树是真二叉树时可以确认拓扑结构,前序遍历左子树的第一个元素在后序左子树的末后

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值