树是一种非线性的数据结构。
树是由n(n>=0)个结点组成的有限集合。
如果n=0,称为空树;
如果n>0,则:
有一个特定的称之为根(root)的结点。
根结点只有直接后继,但没有直接前驱。
除根以外的其它结点划分为m(m>=0)个互不相交的有限集合T0,T1,...,Tm-1,每个集合又是一棵树,并且称之为根的子树(sub tree)。
树中度的概念:
树的结点包含一个数据及若干指向子树的分支的指针。
结点拥有的子树数目称为结点的度:度为0的结点称为叶结点,度不为0的结点称为分支结点。
树的度定义为所有结点中度的最大值。
树中的前驱和后继:
结点的直接后继称为该结点的孩子:相应的,该结点称为孩子的双亲。
结点的孩子的孩子的。。。称为该结点的子孙:相应的,该结点称为子孙的祖先。
同一个双亲的孩子之间互称兄弟。
树中结点的层次:跟为第一层,跟的孩子为第二层。。。
树中结点的最大层次称为树的深度或高度。
树的有序性:如果树中结点的各子树从左到右是有次序的,子树间不能互换位置,则称该树为有序树,否则为无序树。
森林的概念:由n(n>=0)棵互不相交的树组成的集合。
树的操作:插入,删除,获取结点数,获取高度,获取度,清空,查找。
树在程序中为一种特殊的数据类型。树中的结点也表现为一种特殊的数据类型。
小结:树是一种非线性的数据结构。结点拥有唯一前驱(父结点)和若干后继(子结点)
树的结点包含一个数据及若干指向其他结点的指针,树与结点在程序中表现为特殊的数据类型。
52、树的存储结构与实现
设计要点:Gtree为通用树结构,每个结点可以存在多个后继节点。
GTreeNode能够包含任意多指向后继结点的指针。
实现树结构的所有操作(增,删,查等)
问题:每个树节点中为什么包含指向前驱结点的指针?
根节点->叶节点:非线性数据机构。
叶节点->根节点:线性数据结构(链表)。
向上是单链表很有用。
53、树的查找操作
基于数据元素值的查找
GTreeNode<T>* find(const T& value) const
基于结点的查找:
GTreeNode<T>* find(TreeeNode<T>* node) const
基于数据元素值的查找
定义功能:find(node,value)
在node为根结点的树中查找value所在的结点。
find(node,value)=
return node; node->value==value
find(node->child,value); node->value!=value
GTreeNode<T>* find(GTreeNode<T>* node,const T& value) const//功能函数
{
GTreeNode<T>* ret=NULL;
if(node!=NULL)
{
if(node->value == value)
{
return node;
}
else
{
for(node->child.move(0),!node->child.end()&&(ret==NULL);node->child.next()) //遍历当前结点里边的每一棵子树
{
ret=find(node->child.current(),value);
}
}
}
return ret;
}
基于结点的查找:
定义功能:find(node,obj)
在node为根节点的树中查找是否存在obj结点
find(node,obj)=
return node; node==obj
find(node->child,obj); node!=obj
GTreeNode<T>* find(GTreeNode<T>* node,GTreeNode<T>*obj) const
{
GTreeNode<T>* ret=NULL;
if(node==obj)
{
return node;
}
else
{
if(node!=NULL)
{
for(node->child.move(0);!node->child.end() &&(ret==NULL);node->child.next())
{
ret=find(node->child.current(), obj);
}
}
}
return ret;
}
小结:查找操作是树的关键操作之一,基于数据元素的查找可判断值是否存在于树中,基于结点的查找可判断树中是否存在指定结点,插入操作和删除操作都依赖于查找操作。
54、插入操作
插入新结点:
bool insert(TreeNode<T>* node)
插入数据元素:
bool insert(const T& value,TreeNode<T>*parent)
问题?如何指定新结点在树中的位置?
分析:树是非线性的,无法采用下标的形式定位数据元素。
每一个树节点都有唯一的前驱结点(父节点)
因此,必须先找到前驱结点,才能完成新结点的插入。
bool insert(TreeNode<T>* node)
{
bool ret=true;
if(node!=NULL)
{
if(this->root()==NULL)
{
node->parent=NULL;
this->m_root=node;
}
else
{
GTreeNode<T>* np=find(node->parent);
if(np!=NULL)
{
GTreeNode<T>* n=dynamic_cast<GTreeNode<T>*>(node);//强制类型转换
if(np->child.find(n)<0) //为了高效,不查找整棵树
{
np->child.insert(n);
}
}
else
{
THROW_EXCEPTION(InvalidParameterException,"parent INvalid");
}
}
}
else
{
THROW_EXCEPTION(InvalidParameterException,"node cannot be NULL");
}return ret;
}
bool insert(const T& value, TreeNode<T>* parent)
{
bool ret=true;
GTreeNode<T>* node=new GTreeNode<T>();
if(node!=NULL)
{
node->value=value;
node->parent=parent;
insert(node);//调用上边的函数
}
else
{
THROW_EXCEPTION(NoEnoughMemoryException,"no memory");
}
return ret;}
验证:
GTree<char> t;
GTreeNode<char>* node=NULL;
t.insert('A',NULL);
node=t.find('A');
t.insert('B',node);
t.insert('C',node);
t.insert('D',node);
node=t.find('B');
char* s="KLFGMIJ";
for(int i=0;i<7;i++)
{
TreeNode<char>* node=t.find(s[i]);
node=t.find(s[i]);
while(node!=NULL)
{
cout<<node->value<<" ";
node=node->parent;
}
小结:插入操作是构建树的唯一操作,执行插入操作时必须指明结点间的父子关系,插入操作必须正确处理指向父节点的指针,插入数据元素时需要从堆空间中创建结点。
55、清除操作
void clear() 将树中的所有结点清除(释放堆中的结点)
定义:
free(node):
清除node为根结点的树,释放树中的每一个结点
free(node)=
return; node==NULL
free(node->child); delete node; node!=NULL
问题:树中的结点可能来源于不同的存储空间,如何判断堆空间中的结点并释放?
分析:单凭内存地址很难准确判断具体的存储区域,只有堆空间的内存需要主动释放(delete),清除操作时只需要对堆空间中的结点进行释放。
解决方案:工厂模式:提供一个工厂方法
1、在GTreeNode中增加保护成员变量 m_flag
2、将GTreeNode中的operator new重载为保护成员函数
3、提供工厂方法GTreeNode<T>* NewNode() (静态成员函数,外部可以使用)
4、在工厂方法中new新结点并将m_flag设置为true。
template <typename T>
class GTreeNode:public TreeNode<T>
{
protected:
bool m_flag;
void* operator new(unsigned int size) throw()
{
return Wobject::operator new(size);
}
public:
LinkList<GTreeNode<T>*> child;
GTreeNode()
{
m_flag=false;
}
bool flag()
{
return m_flag;
}
static GTreeNode<T>* NewNode() //工厂方法
{
GTreeNode<T>* ret=new GTreeNode<T>();
if(ret !=NULL)
{
ret->m_flag=true;
}
return ret;
}
void free(GTreeNode<T>* node)
{
if(node!=NULL)
{
for(node->child.move(0);!node->child.end();node->child.next())
{
free(node->child.current());
}
if(node->flag())
{
delete node;
}
}
}
小结:清除操作用于销毁树中的每个结点,销毁结点时需要决定是否释放对应的内存空间,工厂模式可用于“定制”堆空间中的结点,只有销毁定制结点的时候需要进行释放。
56、删除个别结点
方式:基于数据元素值的删除:
SharedPointer<Tree<T>>remove(const T& value)
基于结点的删除:
SharedPointer<Tree<T> >remove(TreeNode<T>*node)
删除操作成员函数的设计要点:
将被删除结点所代表的子树进行删除,删除函数返回一颗堆空间中的树,具体返回值为指向树的智能指针对象(能够管理这颗树 的生命周期)。
实用得设计原则:当需要从函数中返回堆中的对象时,实用智能指针(SharedPointer)作为函数的返回值。
删除操作功能的定义:
void remove(GTree<T>* node,GTree<T>*& ret) //GTree<T>*&, ret一个引用,这个引用是一个指针的别名
将node为根结点的子树从原来的树中删除,ret作为子树返回(ret指向堆空间中的树对象)
void remove(GTreeNode<T>* node,GTree<T>*& ret)
{
ret=new GTree<T>();
if(ret==NULL)
{
THROW_EXCEPTION(NoEnoughMemoryException,"no memory");
}
else
{
if(root()==node)
{
this->m_root=NULL;
}
else
{
LinkList<GTreeNode<T>*>& child=dynamic_cast<GTreeNode<T>*>(node->parent)->child;
child.remove(child.find(node));
node->parent=NULL;//断开
}
ret->m_root=node;
}
}
SharedPointer< Tree<T> > remove(const T& value)
{
GTree<T>* ret=NULL;
GTreeNode<T>* node=find(value);
if(node==NULL)
{
THROW_EXCEPTION(InvalidParameterException,"can not find node via parameter value..");
}
else
{
remove(node,ret);
}
return ret;
}
SharedPointer<Tree<T> > remove(TreeNode<T>* node)
{
GTree<T>* ret=NULL;
node=find(node);
if(node==NULL)
{
THROW_EXCEPTION(InvalidParameterException,"Parameter node is invalid...");
}
else
{
remove(dynamic_cast<GTreeNode<T>*>(node),ret);
}
return ret;
}
SharedPointer<Tree<char> > p=t.remove(t.find('D')); //测试
const char* s="KLFGMIJ";
for(int i=0;i<7;i++)
{
TreeNode<char>* node=p->find(s[i]);
while(node!=NULL)
{
cout<<node->value<<" ";
node=node->parent;
}
cout<<endl;
}
小结:删除操作将目标结点所代表的子树移除,删除操作必须完善处理父结点和子结点的关系,删除操作的返回值为指向树的智能指针对象。函数中返回堆中的对象时,使用智能指针作为返回值。
57、 属性操作的实现
树中结点的数目:
定义功能:count(node)
在node为根结点的树中统计结点数目。
count(node)=
return 0; node==NULL
return 1; node->child.length==0;
coutnt(node->child)+1; node->child.length>0
count(A)=count(B)+count(C)+count(D)+1.
int count(GTreeNode<T>* node) const
{
int ret=0;
if(node!=NULL)
{
ret=1;
for(node->child.move(0); !node->child.end();node->child.next())
{
ret+=count(node->child.current());
}
}
return ret;
}
树的高度:
定义高度:定义功能:height(node)
获取node为根结点的树的高度。
height=
return 0; node==NULL
return 1; node->child.length==0
MAX{height(node->child)}+1; node->child.height>0
height(A)=MAX{height(B),height(C),height(D)}+1
int height(GTreeNode<T>* node) const
{
int ret=0;
if(node!=NULL)
{
for(node->child.move(0);!node->child.end();node->child.next())
{
int h=height(node->child.current());
if(ret<h)
{
ret=h;
}
}
ret=ret+1;
}
return ret;
}
树的度数:定义功能:degree(node)
获取node为根结点的树的度数
degree(node)=
return 0; node==NULL
MAX{degree(node->child), node->child.length}; node!=NULL
degree(A)=MAX{degree(B),degree(C),degree(D),3}
int degree(GTreeNode<T>* node)const
{
int ret=0;
if(node!=NULL)
{
ret=node->child.length();
for(node->child.move(0);!node->child.end();node->child.next())
{
int d=degree(node->child.current());
if(ret<d)
{
ret=d;
}
}
}
return ret;
}
58、层次遍历
如何按层次遍历通用树结构中的每一个数据元素?
设计思路:(游标)
在树中定义一个游标(GTreeNode<T>*),遍历开始前将游标指向根结点(root()),获取游标指向的数据元素,通过结点中的child成员移动游标。
设计思路(游标):提供一组遍历相关的函数,按层次访问树中的数据元素。
begin():初始化,准备进行遍历访问。
next():移动游标,指向下一个结点。
current():获取游标所指向的数据元素。
end():判断游标是否到达尾部。
层次遍历算法:
原料:class LinkQueue<T>;
游标:LinkQueue<T>::front();
思想:
begin()->将根结点压入队列中、游标front()指向根节点
current()->访问队头元素指向的数据元素
next()->队头元素弹出,将队头元素的孩子压入队列中(核心)
end()->判断队列是否为空
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)
{
GTreeNode<T>* node=m_queue.front();
m_queue.remove();
for(node->child.move(0); !node->child.end(); node->child.next()) //遍历链表
{
m_queue.add(node->child.current());
}
}
return ret;
}
T current()
{
if(!end())
{
return m_queue.front()->value;
}
else
{
THROW_EXCEPTION(InvalidOperationException,"No value at current position ...");
}
}
小结:树的结点没有固定的编号方式,可以按照层次关系对树中的结点进行遍历,通过游标的思想设计遍历成员函数,遍历成员函数是相互依赖,相互配合的关系,遍历算法的核心为队列的使用。