51、树的定义(非线性)(工厂模式)

树是一种非线性的数据结构。

树是由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 ...");
}
}

小结:树的结点没有固定的编号方式,可以按照层次关系对树中的结点进行遍历,通过游标的思想设计遍历成员函数,遍历成员函数是相互依赖,相互配合的关系,遍历算法的核心为队列的使用。

猜你喜欢

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