从现在开始,我们研究算法理论中最重要的树的实现问题,其中包括抽象树、N叉树及二叉树,这里重点研究二叉树。树状图是一种数据结构,它是由n(n>=1)个有限结点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:每个结点有零个或多个子结点;每一个子结点只有一个父结点;没有前驱的结点为根结点;除了根结点外,每个子结点可以分为m个不相交的子树。
树的总体架构如图所示:
1 二叉树
二叉树是树的数据结构中,最重要,运用场合最多的技术,其总体架构如下:
首先来看抽象树的实现,抽象树是一个容器,各子树的根作为其元素:
class Tree : public virtual Container
{
class Iter;
public:
virtual Object& Key () const = 0;
virtual Tree& Subtree (unsigned int) const = 0;
virtual bool IsEmpty () const = 0;
virtual bool IsLeaf () const = 0;
virtual unsigned int Degree () const = 0;
virtual int Height () const;
virtual void DepthFirstTraversal (PrePostVisitor& visitor) const;
virtual void BreadthFirstTraversal (Visitor& visitor) const;
Iterator& NewIterator() const;
void Accept (Visitor& visitor) const;
};
我们看到,抽象树封装了所有树实现的通用元素:
函数Key:返回根节点对象的引用。
函数Subtree:返回给定树的第i课子树。
函数IsEmpty:判断这棵树是否是空树。
函数IsLeaf:判断这个节点是不是叶子节点。
函数Degree:返回根节点的度,即包含子树的个数,叶子节点度为0。
函数Height:返回树的高度。
函数DepthFirstTraversal:树的深度优先遍历。
函数BreadthFirstTraversal:树的广度优先遍历。
2 树的遍历
(1)深度优先遍历
//深度优先遍历
void Tree::DepthFirstTraversal( PrePostVisitor& visitor ) const
{
if (visitor.IsDone ())
{
return;
}
if (!IsEmpty ())
{
//前序遍历
visitor.PreVisit (Key ());
for (unsigned int i = 0; i < Degree (); ++i)
{
//深度优先遍历子树
Subtree(i).DepthFirstTraversal (visitor);
}
//后序遍历
visitor.PostVisit (Key ());
}
}
深度优先遍历是递归地访问根极其子树方式来遍历整个树,算法很简单,举例子:
如果是先序遍历,深度优先遍历首先访问H,然后递归遍历D和G,到D时又递归遍历A和C,到C时又递归遍历B,到G时又递归遍历F,到F时又递归遍历E。所以先序深度优先遍历最后的结果是:HDACBGFE。
如果是后序遍历,则首先递归遍历D和G,再H;到D时又递归遍历A、C再D;到C时又递归遍历B,再C。到G时先F再G,到F时先E再F,最后后续深度优先遍历的最后结果是:ABCDEFGH。
注意,这里的抽象树,还没有到二叉树的那个级别,所以暂不考虑中序遍历。
我们的先序、中序和后续遍历是通过访问者模式来实现的:
class PuttingVisitor : public Visitor
{
ostream& stream;
bool comma;
public:
PuttingVisitor (ostream& s) : stream (s), comma (false)
{}
void Visit (Object& object)
{
if (comma)
stream << ", ";
stream << object;
comma = true;
}
bool IsDone () const
{
return comma;
}
};
//遍历基类
class PrePostVisitor : public Visitor
{
public:
virtual void PreVisit (Object&) {}
virtual void Visit (Object&) {}
virtual void PostVisit (Object&) {}
};
//前序遍历类
class PreOrder : public PrePostVisitor
{
Visitor& visitor;
public:
PreOrder (Visitor& v) : visitor (v)
{}
void PreVisit (Object& object)
{
visitor.Visit (object);
}
};
//中序遍历类
class InOrder : public PrePostVisitor
{
Visitor& visitor;
public:
InOrder (Visitor& v) : visitor (v)
{}
void Visit (Object& object)
{
visitor.Visit (object);
}
};
//后序遍历类
class PostOrder : public PrePostVisitor
{
Visitor& visitor;
public:
PostOrder (Visitor& v) : visitor (v)
{}
void PostVisit (Object& object)
{
visitor.Visit (object);
}
};
(2)广度优先遍历
再来看树的广度优先算法,树的广度优先遍历是按照树的深度来访问的,并在每一层上按照从左到右的顺序访问节点:
//广度优先遍历
void Tree::BreadthFirstTraversal( Visitor& visitor ) const
{
Queue& queue = *new QueueAsLinkedList ();
queue.RescindOwnership ();
if (!IsEmpty ())
{
queue.Enqueue (const_cast<Tree&> (*this));
}
while (!queue.IsEmpty () && !visitor.IsDone ())
{
Tree const& head =
dynamic_cast<Tree const &> (queue.Dequeue ());
visitor.Visit (head.Key ());
for (unsigned int i = 0; i < head.Degree (); ++i)
{
Tree& child = head.Subtree (i);
if (!child.IsEmpty ())
queue.Enqueue (child);
}
}
delete &queue;
}
广度优先算法由于是按照高度来遍历的,所以不能用递归的方式了。我们还是通过一个例子来研究这个算法:
首先高度为3的H入队列,进入循环,H出队并接受访问;随后高度为2的D和G入队;
到D时,高度为A和C的节点又入队,D接受访问;
到G时F入队,G接受访问;
到A时没有入队,A接受访问;
到C时B入队,C接受访问;
到F时E入队,F接受访问;
到B时没有入队,B接受访问;
到E时没有入队,E接受访问。
最终广度优先算法是:HDGACFBE
(3)树的迭代器
使用树的迭代器也是一种遍历的方法:
//树的迭代器
class Tree::Iter : public Iterator
{
Tree const& tree;
Stack& stack;
public:
Iter (Tree const& _tree);
~Iter ();
void Reset ();
bool IsDone () const;
Object& operator * () const;
void operator ++ ();
};
Tree::Iter::Iter( Tree const& _tree ):
tree(_tree),
stack (*new StackAsLinkedList ())
{
stack.RescindOwnership ();
Reset ();
}
Tree::Iter::~Iter()
{
delete &stack;
}
void Tree::Iter::Reset()
{
stack.Purge ();
if (!tree.IsEmpty ()){
stack.Push (const_cast<Tree&> (tree));
}
}
bool Tree::Iter::IsDone() const
{
return stack.IsEmpty ();
}
Object& Tree::Iter::operator*() const
{
if (!stack.IsEmpty ())
{
Tree const& top =
dynamic_cast<Tree const&> (stack.Top ());
return top.Key ();
}
else
{
return NullObject::Instance ();
}
}
void Tree::Iter::operator++()
{
if (!stack.IsEmpty ())
{
Tree const& top =
dynamic_cast<Tree const&> (stack.Pop ());
for (int i = top.Degree () - 1; i >= 0; --i)
{
Tree& subtree = top.Subtree (i);
if (!subtree.IsEmpty ())
{
stack.Push (subtree);
}
}
}
}
我们主要关注++操作符,这个操作符号利用栈来实现子树的遍历。对于栈的运用,在这里是一个很好的提现,希望好好学习这个思想。
3 二叉树的实现
二叉树是抽象树Tree的派生类,但也是很多重要数据结构的基类,所以我们暂时不会实现一些通用的接口:
class BinaryTree : public virtual Tree
{
protected:
Object* key;
BinaryTree* left;
BinaryTree* right;
public:
BinaryTree ();
BinaryTree (Object& _key);
void purge();
~BinaryTree ();
Object& Key () const;
virtual BinaryTree& Left () const;
virtual BinaryTree& Right () const;
virtual void AttachLeft (BinaryTree&);
virtual void AttachRight (BinaryTree&);
virtual void AttachKey (Object&);
virtual Object& DetachKey ();
virtual BinaryTree& DetachLeft ();
virtual BinaryTree& DetachRight ();
// ...
void DepthFirstTraversal (PrePostVisitor& visitor) const;
};
BinaryTree::BinaryTree():
key(0),
left(0),
right(0){}
BinaryTree::BinaryTree( Object& _key ):
key(&_key)
left(new BinaryTree()),
right(new BinaryTree())
{}
void BinaryTree::purge()
{
if (!IsEmpty ())
{
if (IsOwner ())
delete key;
delete left;
delete right;
key = 0;
left = 0;
right = 0;
}
}
BinaryTree::~BinaryTree()
{
purge();
}
//二叉树的遍历,采用先序方式
void BinaryTree::DepthFirstTraversal( PrePostVisitor& visitor ) const
{
if (visitor.IsDone ())
{
return;
}
if (!IsEmpty ())
{
visitor.PreVisit (*key);
left->DepthFirstTraversal (visitor);
visitor.Visit (*key);
right->DepthFirstTraversal (visitor);
visitor.PostVisit (*key);
}
}