52、二叉树

树到二叉树的转换:

通用树结构:双亲孩子表示法:

每个结点都有一个指向其双亲的指针,每个结点都有若干个指向其孩子的指针。

另一种树结构模型:孩子兄弟表示法:

每个结点都有一个指向其第一个孩子的指针,每个结点都有一个指向其第一个右兄弟的指针。

孩子兄弟表示法的特点:

1、能够表示任意的树形结构

2、每个结点包含一个数据成员和两个指针成员

3、孩子结点指针和兄弟结点指针构成了“树杈”

二叉树的定义:

二叉树是由n个结点组成的有限集合,该集合或者为空,或者是由一个根节点加上两棵分别称为左子树和右子树的,互不相交的二叉树组成。

特殊的二叉树:

满二叉树(Full Binary Tree):如果二叉树中所有分支结点的度数都为2,且叶子结点都在同一层次上,则称这类二叉树为满二叉树。

完全二叉树(Complete Binary Tree):如果一课具有n个结点的高度为k的二叉树,它的每一个结点都与高度为k的满二叉树中编号为1-n的结点--对应,则称这棵二叉树为完全二叉树。(从上到下从左到右编号)

完全二叉树的特性:

同样结点数的二叉树,完全二叉树的高度最小。

完全二叉树的叶节点仅出现在最下面两层:最底层的叶结点一定出现在左边,倒数第二层的叶结点一定出现在右边,完全二叉树中度为1的结点只有左孩子。

小结:通用树结构采用了双亲结点表示法进行描述,孩子兄弟表示法有能力描述任意类型的树结构,孩子兄弟表示法能够将通用树转化为二叉树,二叉树是最多只有两个孩子的树。

61、二叉树的存储结构设计

完成二叉树和二叉树结点的存储结构设计。

设计要点:BTree为二叉树结构,每个结点最多只有两个后继结点。GTreeNode只包含4个固定的共有成员(哪4个?)。

实现树结构的所有操作(增,删,查,等)。

GTree(二叉树结构)的实现架构:

62、查找

基于数据元素值的查找:

BTreeNode<T>*find(const T& value)const

基于结点的查找:

BTreeNode<T>*find(TreeNode<T>* node)const

基于数据元素值的查找

定义功能:find(node,value)

在node为根结点的树中查找value所在的结点。

find(node,value)=

return node;    node->value==value

find(node->left,value);or find(node->reght,value);  node->value!=value

virtual BTreeNode<T>* find(BTreeNode<T>* node,const T& value) const
{
BTreeNode<T>* ret=NULL;
if(node != NULL)
{
if(node->value==value)
{
ret=node;
}
else
{
if(ret==NULL)
{
ret=find(node->left,value);
}
if(ret == NULL)
{
ret=find(node->right,value);
}
}
}
return ret;

}

基于结点的查找:

定义功能:find(node,obj)

在node为根节点的树中查找是否存在obj结点

find(node,obj)=

return node; node==obj

find(node->left,obj);or find(node->right,obj); node!=obj

virtual BTreeNode<T>* find(BTreeNode<T>* node,BTreeNode<T>* obj) const
{
BTreeNode<T>* ret=NULL;
if(node == obj)
{
ret=node;
}
else
{
if(node!=NULL)
{
if(ret==NULL)
{
ret=find(node->left,obj);
}
if(ret==NULL)
{
ret=find(node->right,obj);
}
}
}
return ret;

}

63、插入操作

是否能够在任意结点处插入子节点?是否需要指定新数据元素(新结点)的插入位置。

二叉树结点的位置枚举类型:

enum BTNodePos

{ ANY , LEFT , RIGHT }

插入方式:插入新结点:

bool insert(TreeNdoe<T>* node)

bool insert(TreeNode<T>* node,BTNodePos pos)

插入数据元素:

bool insert(const T& valude,TreeNode<T>* parent)

bool insert(const T& value,TreeNode<t>* parent,BTnodePos pos)

virtual bool insert(BTreeNode<T>* n,BTreeNode<T>* np,BTNodePos pos)
{
bool ret=true;
if(pos==ANY)
{
if(np->left==NULL)
{
np->left=n;
}
else if(np->right==NULL)
{
np->right=n;
}
else
{
ret=false;
}
}
else if(pos==LEFT)
{
if(np->left==NULL)
{
np->left=n;
}
else
{
ret=false;
}
}
else if(pos==RIGHT)
{
if(np->right==NULL)
{
np->right=n;
}
else
{
ret=false;
}
}
else
{
ret=false;
}
return ret;

}

bool insert(TreeNode<T>* node)
{
return insert(node,ANY);
}
virtual bool insert(TreeNode<T>* node,BTNodePos pos)//首先要实现父类的虚函数,所以此处还是TreeNode,下边还得转换一下
{
bool ret=true;
if(node!=NULL)
{
if(this->m_root==NULL)
{
node->parent=NULL;
this->m_root=node;
}
else
{
BTreeNode<T>* np=find(node->parent);
if(np != NULL)
{
ret=insert(dynamic_cast<BTreeNode<T>* >(node),np,pos); //将指向父类的指针强制转化为子类
}
/*else
{
THROW_EXCEPTION(InvalidParameterException,"Invalid parent tree node ...");
}*/

} }

else
{
THROW_EXCEPTION(InvalidParameterException,"is NULL");
}
return ret;

}

bool insert(const T& value, TreeNode<T>* parent) 

{
return insert(value,parent,ANY);
}
virtual bool insert(const T& value,TreeNode<T>* parent, BTNodePos pos) 
{
bool ret=true;
BTreeNode<T>* node=BTreeNode<T>::NewNode();
if( node == NULL)
{
THROW_EXCEPTION(NoEnoughMemoryException,"no memory");
}
else
{
node->value= value;
node->parent=parent;
ret=insert(node,pos);
if( !ret)
{
delete node;
}
}
return ret;
}

小结:二叉树的插入操作需要指明插入的位置,插入操作必须正确处理指向父节点的指针,插入数据元素时需要从堆空间中创建结点,当数据元素插入失败时需要释放结点空间。

64、删除和清除

删除的方式:基于数据元素值的删除

SharedPointer<Tree<T>>remove(const T& value)

基于结点的删除

SharedPointer<Tree<T> >remove(TreeNode<T>* node)

删除操作功能的定义:

void remove(BTreeNode<T>* node,BTree<T>*& ret)

将node为根结点的子树从原来的二叉树中删除,ret作为子树返回(ret指向堆空间中的二叉树对象)

virtual void remove(BTreeNode<T>* node,BTree<T>*& ret)
{
ret=new BTree<T>();
if(ret==NULL)
{
THROW_EXCEPTION(NoEnoughMemoryException,"No memory to create new true..");
}
else
{
if(root()==node)
{
this->m_root=NULL;
}
else
{
BTreeNode<T>* parent=dynamic_cast<BTreeNode<T>*>(node->parent);//parent是TreeNode?
if(parent->left==node)
{
parent->left=NULL;
}
else if(parent->right==node)
{
parent->right=NULL;
}
node->parent=NULL;
}
ret->m_root=node;
}

}

SharedPointer<Tree<T> > remove(const T& value)  //删除结点,不让后续结点也全部删除,返回sharepointer指向的一颗树
{
BTree<T>* ret=NULL;
BTreeNode<T>* node=find(value);
if(node==NULL)
{
THROW_EXCEPTION(InvalidParameterException,"can not find the node via value..");
}
else
{
remove(node,ret);
}
return ret;
}
SharedPointer<Tree<T> > remove(TreeNode<T>* node)
{
BTree<T>* ret=NULL;
node=find(node);
if(node==NULL)
{
THROW_EXCEPTION(InvalidParameterException,"Paratermeter node is invalid...");
}
else
{
remove(dynamic_cast<BTreeNode<T>*>(node),ret);
}
return ret;
}

清除操作的定义:

void clear():

将二叉树中的所有结点清除(释放堆中的结点)。

清除功能的定义:

free(node):

清除node为根节点的二叉树,释放二叉树中的每一个结点

free(node)=

return; node==NULL

free(node->left); free(node->right); delete node; node!=NULL

virtual void free(BTreeNode<T>* node)//功能函数
{
if(node!=NULL)
{
free(node->left);
free(node->right);
if(node->flag())
{
delete node;
}
}

}

void clear()
{
free(root());
this->m_root=NULL;
}

小结:删除操作将目标结点所代表的子树移除,删除操作必须完善处理父结点和子结点的关系,清除操作作用于销毁树中的每个结点,销毁结点时判断是否释放对应的内存空间(工厂模式)。

65、属性操作

1 二叉树中结点的数目

定义功能:count(node)

在node为根结点的二叉树中统计结点数目。

count(node)=

return 0; node==NULL

count(node->left)+count(node->right)+1; node!=NULL

int count(BTreeNode<T>* node) const//功能函数
{
/*int ret=0;
if(node!=NULL)
{
ret=count(node->left)+count(node->right)+1;
}*/
return (node!=NULL)? (count(node->left)+count(node->right)+1):0;

}

int count() const
{
return count(root());

}

2 二叉树的高度

定义功能:height(node)

获取node为根结点的二叉树的高度

height(node)=

return 0; node==NULL

MAX{height(node->left),height(node->right) }+1; node!=NULL

int height(BTreeNode<T>* node)const//功能函数
{
int ret=0;
if(node!=NULL)
{
int lh=height(node->left);
int rh=height(node->right);
ret=((lh>rh)? lh:rh)+1;
}
return ret;

}

int height() const
{
return height(root());

}

3 树的度数

定义功能:degree(node)

获取node为根结点的二叉树的度数

degree(node)=

return 0; node==NULL

MAX{degree(node->left),degree(node->right), !!node->left+!!node->right}; node!=NULL

!! 编程技巧,不为空为1,为空0.如果都不为空,值为2

int degree(BTreeNode<T>* node)const
{
int ret=0;
if(node!=NULL)
{
BTreeNode<T>* child[]={node->left,node->right};
ret=(!!node->left + !!node->right);
for(int i=0;(i<2)&&(ret<2);i++)//提高效率并且没有代码冗余
{
int d=degree(child[i]);
if(ret<d)
{
ret=d;
}
}
/*if(ret<2)
{
int d=degree(node->left);
if(ret<d)
{
ret=d;
}
if(ret<2)
{
int d=degree(node->right);
if(ret<d)
{
ret=d;
}
}

}*/

/*

int dl=degree(node->left);
int dr=degree(node->right);
ret=(!!node->left+!!node->right);
if(ret<dl)
{
ret=dl;
}
if(ret<dr)
{
ret=dr;
}
*/
}
return ret;

}

66、二叉树结构的层次遍历

二叉树的遍历:

二叉树的遍历(Traversing Binay Tree)是指从根结点出发,按照某种次序依次访问二叉树中的所有结点,使得每个结点被访问一次,且仅被访问一次。

需要考虑的问题:通用树结构的层次遍历算法是否可以用在二叉树结构上?

如果可以,代码需要做怎样的改动?

设计思路(游标):与通用树一样

层次遍历算法:

原料:class LinkQueue<T>;

游标:LinkQueue<T>::front();

思想:begin() current() next() end()

next压入的不是链表而是左右孩子。

bool begin()
{
bool ret(root() != NULL);
if(ret)
{
m_queue.clear();//万一上一次遍历没结束,先clear,保证begin调用之后,队列中只有根节点
m_queue.add(root());
}
return ret;
}
bool end()
{
return (m_queue.length()==0);
}
bool next()
{
bool ret=(m_queue.length()>0);
if(ret)
{
BTreeNode<T>* node=m_queue.front();
m_queue.remove();
if(node->left!=NULL)
{
m_queue.add(node->left);
}
if(node->right!=NULL)
{
m_queue.add(node->right);
}
}
return ret;
}
T current()
{
if(!end())
{
return m_queue.front()->value;
}
else
{
THROW_EXCEPTION(InvalidOperationException,"No value at current position ...");

}}

for(bt.begin();!bt.end();bt.next())//测试
{
cout<<bt.current()<<" ";
}

cout<<endl;

67、二叉树的典型遍历方式

典型的二叉树遍历方式:

先序遍历(Pre-order Trabersal)<最先访问根结点

中序遍历(In--order Trabersal) 中间访问根结点

后序遍历(Post-order Trabersal) 最后访问根结点

先序遍历:二叉树为空:无操作,直接返回 

二叉树不为空:1、访问根结点中的数据元素。2、先序遍历左子树。3、先序遍历右子树。

1-2-3-4-5-6-7-8-9-10->1-2-4-8-9-5-10-3-6-7 

中序遍历:二叉树为空:无操作,直接返回

二叉树不为空:1、中序遍历左子树。2.访问根结点中的数据元素。3.中序遍历右子树。

1-2-3-4-5-6-7-8-9-10-> 8-4-9-2-10-5-1-6-3-7

后序遍历:二叉树为空:无操作,直接返回

二叉树不为空:1.后序遍历左子树。2、后序遍历右子树。3、访问根结点中的数据元素。

1-2-3-4-5-6-7-8-9-10->8-9-4-10-5-2-6-7-3-1

考虑的问题:是否可以将二叉树的典型遍历算法集成到BTree中?如果可以,代码怎么改?

设计要点:

不能与层次遍历函数冲突,必须设计新的函数接口。

算法执行完成后,能够方便的获得遍历结果。

遍历结果能够反映结点访问的先后顺序。

函数接口设计:

SharedPointer<Array<T> >traversal(BTTraversal order)

根据参数order选择执行遍历算法(先序,中序,后序),返回值为堆中的数据对象(生命期由智能指针管理),数组元素的次序反映遍历的先后次序。

void preOrderTraversal(BTreeNode<T>* node,LinkQueue<BTreeNode<T>*>& queue)
{
if(node!=NULL)
{
queue.add(node);
preOrderTraversal(node->left,queue);
preOrderTraversal(node->right,queue);
}
}
void inOrderTraversal(BTreeNode<T>* node,LinkQueue<BTreeNode<T>*>& queue)
{
if(node!=NULL)
{
inOrderTraversal(node->left,queue);
queue.add(node);
inOrderTraversal(node->right,queue);
}
}
void postOrderTraversal(BTreeNode<T>* node,LinkQueue<BTreeNode<T>*>& queue)
{
if(node!=NULL)
{
postOrderTraversal(node->left,queue);
postOrderTraversal(node->right,queue);
queue.add(node);
}

}

SharedPointer<Array<T> >traversal(BTTraversal order)
{
DynamicArray<T>* ret=NULL;
LinkQueue<BTreeNode<T>*>queue; //保存遍历结点次序
switch(order)
{
case PreOrder:
preOrderTraversal(root(),queue);
break;
case InOrder:
        inOrderTraversal(root(),queue);
break;
case PostOrder:
postOrderTraversal(root(),queue);
break;
default:
THROW_EXCEPTION(InvalidParameterException,"parameter order is invalid ...");
break;
}
ret=new DynamicArray<T>(queue.length());
if(ret!=NULL)
{
for(int i=0;i<ret->length();i++,queue.remove())
{
ret->set(i,queue.front()->value);
}
}
else
{THROW_EXCEPTION(NoEnoughMemoryException,"No memory to create return array...");
}
return ret;

小结:二叉树的典型遍历都是以递归方式执行的,BTree以不同的函数接口支持典型遍历,层次遍历与典型遍历互不冲突,遍历结果能够反映树结点访问的先后次序。

68、二叉树的比较与相加

克隆:定义功能:clone(node)

拷贝node为根结点的二叉树(数据元素在对应位置相等)

clone=

return NULL; node=NULL

n=NewNode(); n->value=node->value; n->left=clone(node->left); n->right=clone(node->right); node!=NULL

BTreeNode<T>* clone(BTreeNode<T>* node)const
{
BTreeNode<T>* ret=NULL;
if(node!=NULL)
{
ret=BTreeNode<T>::NewNode();
if(ret!=NULL)
{
ret->value=node->value;
ret->left=clone(node->left);
ret->right=clone(node->right);
if(ret->left!=NULL)
{
ret->left->parent=ret;
}
if(ret->right!=NULL)
{
ret->right->parent=ret;
}
}
else
{
THROW_EXCEPTION(NoEnoughMemoryException,"no memory");
}
}
return ret;

}

SharedPointer<BTree<T> > clone() const
{
BTree<T>* ret=new BTree<T>();
if(ret!=NULL)
{
ret->m_root = clone(root()); //BTreeNode赋值给TreeNode 子类对象能当父类对象使用
}
else
{
THROW_EXCEPTION(NoEnoughMemoryException,"No memeory to create new tree...");
}
return ret;

}

2 二叉树比较操作的定义

判断两棵二叉树中的数据元素是否对应相等:

bool operator==(const BTree<T>& btree)

bool operator !=(const BTree<T>& BTree)

定义功能:equal(lh,rh)

判断lh为根结点的二叉树与rh为根结点的二叉树是否相等。

equal(lh,rh)=

return true; lh==rh

return false; lh==0&& rh!=0

return false; lh!=0 && rh==0

lh->value==rh->value&&equal(lh->left,rh->left)&&equal(lh-right,rh->right); lh!=0&& rh!=0

bool equal(BTreeNode<T>* lh,BTreeNode<T>* rh) const //功能函数
{
if(lh==rh)
{
return true;
}
else if((lh!=NULL)&&(rh!=NULL))
{
return (lh->value==rh->value)&&(equal(lh->left,rh->left))&&(equal(lh->right,rh->right));
}
else
{
return false;
}

}

bool operator ==(const BTree<T>& btree)
{
return equal(root(),btree.root());
}
bool operator !=(const BTree<T>& btree)
{
return !(*this==btree);

}

3 相加操作

SharedPointer<BTree<T> >add(const BTree<T>& btree)const

将当前二叉树与参数btree中的数据元素在对应位置处相加。

返回值(相加的结果)为堆空间中的一颗新的二叉树。

定义功能:add(lh,rh)

将lh为为根结点的二叉树与rh为根结点的二叉树相加

add(lh,rh)=

clone(rh); lh==0&& rh!=0

clone(lh); lh!=0&& rh==0

n=NewNode();n->value=lh->value+rh->value;n->left=add(lh->left,rh->left);n->right=add(lh->right,rh->right); lh!=0 && rh!=0

BTreeNode<T>* add(BTreeNode<T>* lh,BTreeNode<T>* rh) const
{
BTreeNode<T>* ret=NULL;
if((lh == NULL)&&( rh != NULL))
{
ret=clone(rh);
}
else if((lh!=NULL)&&(rh==NULL))
{
ret=clone(lh);
}
else if((lh!=NULL)&&(rh!=NULL))
{
ret=BTreeNode<T>::NewNode();
if(ret!=NULL)
{
ret->value=lh->value+rh->value;
ret->left=add(lh->left,rh->left);
ret->right=add(lh->right,rh->right);
if(ret->left !=NULL)
{
ret->left->parent=ret;
}
if(ret->right!=NULL)
{
ret->right->parent=ret;
}
}
else
{
THROW_EXCEPTION(NoEnoughMemoryException,"NO MEMORY");
}
}
return ret;

}

SharedPointer<BTree<T> > add(const BTree<T>& btree)const
{
BTree<T>* ret=new BTree<T>();
if(ret!=NULL)
{
ret->m_root=add(root(),btree.root());
}
else
{
THROW_EXCEPTION(NoEnoughMemoryException,"no memory");
}
return ret;
}

小结:比较操作判断两颗二叉树中的数据元素是否对应相等,克隆操作将当前二叉树在堆空间中进行复制,相加操作将两颗二叉树中的数据元素在对应位置处相加,相加操作的结果保存在堆空间的一颗二叉树中。

69、二叉树的线索化实现

线索化是将二叉树转换为双向链表的过程(非线性->线性)

能够反映某种二叉树的遍历次序(节点的先后访问次序):

利用结点的right指针指向遍历中的后继结点,利用结点的left指针指向遍历中的前驱结点。

思维过程:

线索化能够反映遍历次序->线索化算法必然与遍历算法相关->如何在遍历时记录结点间的访问次序->使用队列(先进先出)->遍历结束后队列记录了访问次序->循环访问队列,连接队列中的结点。

课程目标:新增功能函数traversal(order,queue)

新增遍历方式BTTraversal::LevelOrder 层次遍历

新增共有函数 BTreeNode<T>*thread(BTraversal order ) 线索化

消除遍历和线索化的代码冗余(代码重构)

函数接口设计:

BTreeNode<T>* thread(BTTraversal order)

根据参数order选择线索化的次序(先序,中序,后序,层次),返回值线索化之后指向链表首结点的指针,线索化执行结束之后对应的二叉树为空树。

连接算法:

void levelOrderTraversal(BTreeNode<T>* node,LinkQueue<BTreeNode<T>*>& queue)
{
if(node!=NULL)
{
LinkQueue<BTreeNode<T>*> tmp;
tmp.add(node);
while(tmp.length()>0)
{
BTreeNode<T>* n=tmp.front();
if(n->left!=NULL)
{
tmp.add(n->left);
}
if(n->right!=NULL)
{
tmp.add(n->right);
}
tmp.remove();
queue.add(n);
}
}

}

BTreeNode<T>* connect(LinkQueue<BTreeNode<T>*>& queue) //连接
{
BTreeNode<T>* ret=NULL;
if(queue.length()>0)
{
ret=queue.front(); //返回首元素
BTreeNode<T>* slider=queue.front();
queue.remove();
slider->left=NULL;
while(queue.length()>0)
{
slider->right=queue.front();
queue.front()->left=slider;
slider=queue.front();
queue.remove();
}
slider->right=NULL;
}
return ret;

}

SharedPointer<Array<T> >traversal(BTTraversal order)
{
DynamicArray<T>* ret=NULL;
LinkQueue<BTreeNode<T>*>queue; //保存遍历结点次序
traversal(order,queue);//调用重构之后的
ret=new DynamicArray<T>(queue.length());
if(ret!=NULL)
{
for(int i=0;i<ret->length();i++,queue.remove())
{
ret->set(i,queue.front()->value);
}
}
else
{
THROW_EXCEPTION(NoEnoughMemoryException,"No memory to create return array...");
}
return ret;

}

BTreeNode<T>* thread(BTTraversal order) //线索化
{
BTreeNode<T>* ret=NULL;
LinkQueue<BTreeNode<T>*> queue;
traversal(order,queue); //遍历 结果放入queue
ret=connect(queue);    //把队列连接成双向链表
this->m_root=NULL;  //置为空树
m_queue.clear();  //清空队列,否则四个组合函数有问题
return ret;
}

小结:线索化是将二叉树转换为双向链表的过程,结点间的先后次序符合某种遍历次序,线索化操作将破坏原二叉树结点间的父子关系,线索化之后二叉树将不再管理结点的生命期。

猜你喜欢

转载自blog.csdn.net/ws857707645/article/details/80621534