從現在開始,我們研究演算法理論中最重要的樹的實現問題,其中包括抽象樹、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);}}