树及树的算法(1) —— 二叉树
2012-05-31 22:55
169 查看
从现在开始,我们研究算法理论中最重要的树的实现问题,其中包括抽象树、N叉树及二叉树,这里重点研究二叉树。树状图是一种数据结构,它是由n(n>=1)个有限结点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:每个结点有零个或多个子结点;每一个子结点只有一个父结点;没有前驱的结点为根结点;除了根结点外,每个子结点可以分为m个不相交的子树。
树的总体架构如图所示:
首先来看抽象树的实现,抽象树是一个容器,各子树的根作为其元素:
我们看到,抽象树封装了所有树实现的通用元素:
函数Key:返回根节点对象的引用。
函数Subtree:返回给定树的第i课子树。
函数IsEmpty:判断这棵树是否是空树。
函数IsLeaf:判断这个节点是不是叶子节点。
函数Degree:返回根节点的度,即包含子树的个数,叶子节点度为0。
函数Height:返回树的高度。
函数DepthFirstTraversal:树的深度优先遍历。
函数BreadthFirstTraversal:树的广度优先遍历。
深度优先遍历是递归地访问根极其子树方式来遍历整个树,算法很简单,举例子:
如果是先序遍历,深度优先遍历首先访问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。
注意,这里的抽象树,还没有到二叉树的那个级别,所以暂不考虑中序遍历。
我们的先序、中序和后续遍历是通过访问者模式来实现的:
(2)广度优先遍历
再来看树的广度优先算法,树的广度优先遍历是按照树的深度来访问的,并在每一层上按照从左到右的顺序访问节点:
广度优先算法由于是按照高度来遍历的,所以不能用递归的方式了。我们还是通过一个例子来研究这个算法:
首先高度为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)树的迭代器
使用树的迭代器也是一种遍历的方法:
我们主要关注++操作符,这个操作符号利用栈来实现子树的遍历。对于栈的运用,在这里是一个很好的提现,希望好好学习这个思想。
树的总体架构如图所示:
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); } }
相关文章推荐
- 算法大全(3) 二叉树
- 数据结构_二叉树_遍历算法应用
- 数据结构复习:几种排序算法的C++实现和二叉树的相关算法实现
- 【算法与数据结构】二叉树的 先序 遍历
- 【数据结构与算法】二叉树的层序遍历
- 第十一周 项目一 -二叉树算法验证(3)中序线索化二叉树的算法验证
- 算法基础(九):超详细最优二叉树构建(2)求编码
- 数据结构和算法学习(8)-二叉树
- 第十一周项目1——二叉树算法验证(3) 中序线索化二叉树的算法验证
- 数据结构与算法6:二叉树的存储结构与遍历
- 算法训练 求先序排列(二叉树的建立)
- 二叉树创建及遍历算法(递归及非递归)
- 第十一周【项目一-(3)中序线索化二叉树的算法验证】
- 树---关于二叉树的三个小算法
- 第十一周项目3-中序线索化二叉树的算法
- 笔试面试算法经典--二叉树的镜像-递归与非递归实现(Java)
- 4. 二叉树前序、中序、后序递归遍历算法,二叉树前序非递归遍历算法
- 【LeetCode-面试算法经典-Java实现】【111-Minimum Depth of Binary Tree(二叉树的最小深度)】
- 计算二叉树中分支结点数的算法
- 数据结构与算法简记:按层次顺序遍历和存储二叉树