上節博客我們介紹了通用樹到二叉樹的轉換,今天我們就來看看二叉樹的實現。我們先來看看它的組成結構,如下
設計要點:
1、BTree 爲二叉樹結構,每個結點最多隻有兩個後繼結點;
2、BTreeNode 只包含 4 個固定的公有成員(左樹、右樹、構造函數、工廠模式創建新結點函數);
3、實現樹結構的所有操作(增、刪、查等);
下來我們來看看 BTreeNode 和 BTree 的設計與實現,如下
下來我們來看看 BTree(二叉樹結構)的實現框架,如下圖所示
我們來看看具體的 BTree 和 BTreeNode 的源碼實現
BTreeNode.h 源碼
#ifndef BTREENODE_H #define BTREENODE_H #include "Tree.h" namespace DTLib { template < typename T > class BTreeNode : public TreeNode<T> { public: BTreeNode<T>* left; BTreeNode<T>* right; BTreeNode() { left = NULL; right = NULL; } static BTreeNode<T>* NewNode() { BTreeNode<T>* ret = new BTreeNode<T>(); if( ret != NULL ) { ret->m_flag = true; } return ret; } }; } #endif // BTREENODE_H
BTree.h 源碼
#ifndef BTREE_H #define BTREE_H #include "Tree.h" #include "BTreeNode.h" namespace DTLib { template < typename T > class BTree : public Tree<T> { public: bool insert(TreeNode<T>* node) { bool ret = true; return ret; } virtual bool insert(TreeNode<T>* node, BTNodePos pos) { bool ret = true; return ret; } bool insert(const T& value, TreeNode<T>* parent) { bool ret = true; return ret; } SharedPointer< Tree<T> > remove(const T& value) { return NULL; } SharedPointer< Tree<T> > remove(TreeNode<T>* node) { return NULL; } BTreeNode<T>* find(const T& value) const { return NULL; } BTreeNode<T>* find(TreeNode<T>* node) const { return NULL; } BTreeNode<T>* root() const { return dynamic_cast<BTreeNode<T>*>(this->m_root); } int degree() const { return 0; } int count() const { return 0; } int height() const { return 0; } void clear() { this->m_root = NULL; } ~BTree() { clear(); } }; } #endif // BTREE_H
我們看到它的框架和我們之前實現的通用樹差不多,下來我們來一一的實現它的所有操作
1、查找操作
a> 基於數據元素值的查找:BTreeNode<T>* find(const T& value) const;b> 基於結點的查找:BTreeNode<T>* find(TreeNode<T>* node) const;
a> 基於數據元素值的查找,定義功能:find(node, value)。在 node 爲根結點的二叉樹中查找 value 所在的結點,如下
b> 基於結點的查找,定義功能:find(node, obj)。在 node 爲根結點的二叉樹中查找是否存在 obj 結點,如下
具體源碼實現如下
#ifndef BTREE_H #define BTREE_H #include "Tree.h" #include "BTreeNode.h" #include "Exception.h" #include "LinkQueue.h" #include "DynamicArray.h" namespace DTLib { template < typename T > class BTree : public Tree<T> { protected: virtual BTreeNode<T>* find(BTreeNode<T>* node, const T& value) const { BTreeNode<T>* ret = NULL; if( node != NULL ) { if( node->value == value ) { ret = node; } else { if( ret == NULL ) { ret = find(node->left, value); } if( ret == NULL ) { ret = find(node->right, value); } } } return ret; } virtual BTreeNode<T>* find(BTreeNode<T>* node, BTreeNode<T>* obj) const { BTreeNode<T>* ret = NULL; if( node != NULL ) { if( node == obj ) { ret = node; } else { if( ret == NULL ) { ret = find(node->left, obj); } if( ret == NULL ) { ret = find(node->right, obj); } } } return ret; } public: BTreeNode<T>* find(const T& value) const { return find(root(), value); } BTreeNode<T>* find(TreeNode<T>* node) const { return find(root(), dynamic_cast<BTreeNode<T>*>(node)); } BTreeNode<T>* root() const { return dynamic_cast<BTreeNode<T>*>(this->m_root); } }; } #endif // BTREE_H
2、插入操作
我們在進行插入操作的時候不是二叉樹的任意結點都能插入,必須得指明插入新結點的位置,是左子樹還是右子樹,不指明便是從左向右的進行插入。因此我們得定義一個枚舉,用來表示二叉樹結點的位置。插入操作也分爲兩種:a> 插入新結點:bool insert(TreeNode<T>* node); bool insert(TreeNode<T>* node, BTNodePos pos); b> 插入數據元素:bool insert(const T& value, TreeNode<T>* parent); bool insert(const T& value, TreeNode<T>* parent, BTNodePos pos)。
新結點的插入如圖所示
a> 指定位置的結點插入,如下圖所示
插入新結點,如下圖所示
b> 插入數據元素
我們來看看源碼具體是怎麼實現的
namespace DTLib { enum BTTraversal { PreOrder, Inorder, PostOrder, LeverOrder }; template < typename T > class BTree : public Tree<T> { protected: virtual bool insert(BTreeNode<T>* n, BTreeNode<T>* np, BTNodePos pos) { bool ret = true; if( pos == ANY ) { if( np->left == NULL ) { np->left = n; } else if( np->right == NULL ) { np->right = n; } else { ret = false; } } else if( pos == LEFT ) { if( np->left == NULL ) { np->left = n; } else { ret = false; } } else if( pos == RIGHT ) { if( np->right == NULL ) { np->right = n; } else { ret = false; } } else { ret = false; } return ret; } public: bool insert(TreeNode<T>* node) { return insert(node, ANY); } virtual bool insert(TreeNode<T>* node, BTNodePos pos) { bool ret = true; if( node != NULL ) { if( this->m_root == NULL ) { node->parent = NULL; this->m_root = node; } else { BTreeNode<T>* np = find(node->parent); if( np != NULL ) { ret = insert(dynamic_cast<BTreeNode<T>*>(node), np, pos); } else { THROW_EXCEPTION(InvalidParameterException, "Invalid parent tree ndoe ...."); } } } else { THROW_EXCEPTION(InvalidParameterException, "Parameter node can not be NULL ..."); } return ret; } bool insert(const T& value, TreeNode<T>* parent) { return insert(value, parent, ANY); } virtual bool insert(const T& value, TreeNode<T>* parent, BTNodePos pos) { bool ret = true; BTreeNode<T>* node = BTreeNode<T>::NewNode(); if( node == NULL ) { THROW_EXCEPTION(NoEnoughMemoryException, "No memory to create new node ..."); } else { node->value = value; node->parent = parent; ret = insert(node, pos); if( !ret ) { delete node; } } return ret; } }; }
3、結點刪除與清除
a> 基於數據元素值的刪除:SharedPointer< Tree<T> > remove(const T& value); b> 基於結點的刪除:SharedPointer< Tree<T> > remove(TreeNode<T>* node)。
二叉樹中結點的刪除如下圖所示
刪除操作功能的定義:virtual void remove(BTreeNode<T>* node, BTree<T>*& ret); 將在 node 爲根結點的子樹從原來的二叉樹中刪除,ret 作爲子樹返回(ret 指向堆空間中的二叉樹對象),下來我們來看看刪除功能函數的實現流程,如下圖所示
清除操作的定義:void clear(),將二叉樹中的所有結點清除(釋放堆中的結點),如下所示
功能定義:free(node),清除 node 爲根結點的二叉樹,釋放二叉樹中的每一個結點,如下
我們來看看具體源碼的實現
namespace DTLib { template < typename T > class BTree : public Tree<T> { protected: virtual void remove(BTreeNode<T>* node, BTree<T>*& ret) { ret = new BTree<T>(); if( ret == NULL ) { THROW_EXCEPTION(NoEnoughMemoryException, "No memroy to create new tree ..."); } else { if( root() == node ) { this->m_root = NULL; } else { BTreeNode<T>* parent = dynamic_cast<BTreeNode<T>*>(node->parent); if( parent->left == node ) { parent->left = NULL; } else if( parent->right == node ) { parent->right = NULL; } node->parent = NULL; } ret->m_root = node; } } virtual void free(BTreeNode<T>* node) { if( node != NULL ) { free(node->left); free(node->right); if( node->flag() ) { delete node; } } } public: SharedPointer< Tree<T> > remove(const T& value) { BTree<T>* ret = NULL; BTreeNode<T>* node = find(value); if( node == NULL ) { THROW_EXCEPTION(InvalidParameterException, "Can not find the tree node via value ..."); } else { remove(node, ret); m_queue.clear(); } return ret; } SharedPointer< Tree<T> > remove(TreeNode<T>* node) { BTree<T>* ret = NULL; node = find(node); if( node == NULL ) { THROW_EXCEPTION(InvalidParameterException, "Parament node is invalid ..."); } else { remove(dynamic_cast<BTreeNode<T>*>(node), ret); m_queue.clear(); } return ret; } void clear() { free(root()); this->m_root = NULL; } }; }
4、屬性操作
在二叉樹中,它的屬性操作有三種:a> 結點數目;b> 高度;c> 度數。
a> 二叉樹中結點的數目,定義功能:count(node)。在 node 爲根結點的二叉樹中統計結點數目,如下圖所示
b> 二叉樹的高度,定義功能:height(node)。獲取 node 爲根結點的二叉樹的高度,如下圖所示
c> 二叉樹的度數,定義功能:degree(node)。獲取 node 爲根結點的二叉樹的度數,如下圖所示
下來我們來看看具體源碼是怎麼實現的
namespace DTLib { template < typename T > class BTree : public Tree<T> { protected: int count(BTreeNode<T>* node) const { return (node != NULL) ? (count(node->left) + count(node->right) + 1) : 0; } int height(BTreeNode<T>* node) const { int ret = 0; if( node != NULL ) { int lh = height(node->left); int rh = height(node->right); ret = ((lh > rh) ? lh : rh) + 1; } return ret; } int degree(BTreeNode<T>* node) const { int ret = 0; if( node != NULL ) { BTreeNode<T>* child[] = { node->left, node->right }; ret = !!node->left + !!node->right; for(int i=0; (i<2) && (ret<2); i++) { int d = degree(child[i]); if( ret < d ) { ret = d; } } } return ret; } public: int degree() const { return degree(root()); } int count() const { return count(root()); } int height() const { return height(root()); } }; }
5、層次遍歷
二叉樹的遍歷是指從根結點出發,按照某種次序訪問二叉樹中的所有結點,使得每個結點被訪問一次,且僅被訪問一次。設計思路(遊標),提供一組遍歷相關的函數,按層次訪問二叉樹中的數據元素,如下
層次遍歷算法:a> 原料:class LinkQueue<T>; b> 遊標:LinkQueue<T>::front();
思想:a> begin() --> 將根結點壓入隊列中;
b> current() --> 訪問隊頭元素指向的數據元素;
c> next() --> 隊頭元素彈出,將隊頭元素的蓋子壓入隊列中(核心);
d> end() --> 判斷隊列是否爲空。
層次遍歷算法示例如下
具體源碼實現如下
namespace DTLib { template < typename T > class BTree : public Tree<T> { protected: LinkQueue<BTreeNode<T>*> m_queue; public: bool begin() { bool ret = (root() != NULL); if( ret ) { m_queue.clear(); m_queue.add(root()); } return ret; } bool end() { return (m_queue.length() == 0); } bool next() { bool ret = (m_queue.length() > 0); if( ret ) { BTreeNode<T>* node = m_queue.front(); m_queue.remove(); if( node->left != NULL ) { m_queue.add(node->left); } if( node->right != NULL ) { m_queue.add(node->right); } } return ret; } T current() { if( !end() ) { return m_queue.front()->value; } else { THROW_EXCEPTION(InvalidParameterException, "No value at current position ..."); } } }; }
6、典型遍歷方式
典型的遍歷方式:a> 先序遍歷;b> 中序遍歷;c> 後序遍歷。
a> 先序遍歷:二叉樹爲空,則直接返回;若不爲空:1、訪問根結點中的數據元素,2、先序遍歷左子樹,3、先序遍歷右子樹。如下圖所示
先序遍歷功能定義如下
b> 中序遍歷:二叉樹爲空,則直接返回;若不爲空:1、中序遍歷左子樹,2、訪問根結點中的數據元素,3、中序遍歷右子樹。如下圖所示
中序遍歷功能定義如下
c> 後序遍歷:二叉樹爲空,則直接返回;若不爲空:1、後序遍歷左子樹,2、後序遍歷右子樹,3、訪問根結點中的數據元素。如下圖所示
後序遍歷功能定義如下
我們可以將二叉樹的典型遍歷算法集成到 BTree 中,設計要點:不能與層次遍歷函數衝突,必須設計新的函數接口;算法執行完成後,能夠方便的獲得遍歷結果;遍歷結果能夠反映結點訪問的先後次序。
函數接口設計:SharedPointer< Array<T> > traversal(BTTraversal order);根據參數 order 選擇執行遍歷算法(先序、中序、後序),返回值爲堆中的數組對象(生命週期由智能指針管理),數組元素的次序反映遍歷的先後次序。典型的遍歷示例如下所示
下來我們來看看具體的源碼實現
namespace DTLib { template < typename T > class BTree : public Tree<T> { protected: void perOrderTraversal(BTreeNode<T>* node, LinkQueue<BTreeNode<T>*>& queue) { if( node != NULL ) { queue.add(node); perOrderTraversal(node->left, queue); perOrderTraversal(node->right, queue); } } void inOrderTraversal(BTreeNode<T>* node, LinkQueue<BTreeNode<T>*>& queue) { if( node != NULL ) { inOrderTraversal(node->left, queue); queue.add(node); inOrderTraversal(node->right, queue); } } void postOrderTraversal(BTreeNode<T>* node, LinkQueue<BTreeNode<T>*>& queue) { if( node != NULL ) { postOrderTraversal(node->left, queue); postOrderTraversal(node->right, queue); queue.add(node); } } void traversal(BTTraversal order, LinkQueue<BTreeNode<T>*>& queue) { switch (order) { case PreOrder: perOrderTraversal(root(), queue); break; case Inorder: inOrderTraversal(root(), queue); break; case PostOrder: postOrderTraversal(root(), queue); break; default: THROW_EXCEPTION(InvalidParameterException, "Parameter order is invalid ..."); break; } } public: SharedPointer< Array<T> > traversal(BTTraversal order) { DynamicArray<T>* ret = NULL; LinkQueue<BTreeNode<T>*> queue; traversal(order, queue); ret = new DynamicArray<T>(queue.length()); if( ret != NULL ) { for(int i=0; i<ret->length(); i++, queue.remove()) { ret->set(i, queue.front()->value); } } else { THROW_EXCEPTION(NoEnoughMemoryException, "No memory to create return tree ..."); } return ret; } }; }
7、比較與相加
要進行比較與相加,首先必須得進行克隆。我們先來看看二叉樹的克隆:SharedPointer< BTree<T> > clone() const;克隆當前樹的一份拷貝,返回值爲堆空間中的一顆新二叉樹(與當前樹相等)。定義功能:clone(node),拷貝 node 爲根結點的二叉樹(數據元素在對應位置相等),如下圖所示
二叉樹比較操作的定義,判斷兩顆二叉樹中的數據元素是否對應相等:bool operator == (const BTree<T>& btree);bool operator != (const BTree<T>& btree)。如下
二叉樹的比較,定義功能:equal(lh, rh)。判斷 lh 爲根結點的二叉樹與 rh 爲根結點的二叉樹是否相等,如下
二叉樹的相加操作:SharedPointer< BTree<T> > add(const BTree<T>& btree) const;將當前二叉樹與參數 btree 中的數據元素在對應位置處相加,返回值(相加的結果)爲堆空間中的一顆新二叉樹。如下
二叉樹的加法,定義功能:add(lh, rh)。將 lh 爲根結點的二叉樹與 rh 爲根結點的二叉樹相加,如下
具體源碼實現如下
namespace DTLib { template < typename T > class BTree : public Tree<T> { protected: BTreeNode<T>* clone(BTreeNode<T>* node) const { BTreeNode<T>* ret = NULL; if( node != NULL ) { ret = BTreeNode<T>::NewNode(); if( ret != NULL ) { ret->value = node->value; ret->left = clone(node->left); ret->right = clone(node->right); if( ret->left != NULL ) { ret->left->parent = ret; } if( ret->right != NULL ) { ret->right->parent = ret; } } else { THROW_EXCEPTION(NoEnoughMemoryException, "No memory to clone old tree ..."); } } return ret; } bool equal(BTreeNode<T>* rh, BTreeNode<T>* lh) const { if( lh == rh ) { return true; } else if( (lh != NULL) && (rh != NULL) ) { return (lh->value == rh->value) && equal(lh->left, rh->left) && equal(lh->right, rh->right); } else { return false; } } BTreeNode<T>* add(BTreeNode<T>* lh, BTreeNode<T>* rh) const { BTreeNode<T>* ret = NULL; if( (lh == NULL) && (rh != NULL) ) { ret = clone(rh); } else if( (lh != NULL) && (rh == NULL) ) { ret = clone(lh); } else if( (lh != NULL) && (rh != NULL) ) { ret = BTreeNode<T>::NewNode(); if( ret != NULL ) { ret->value = lh->value + rh->value; ret->left = add(lh->left, rh->left); ret->right = add(lh->right, rh->right); if( ret->left != NULL ) { ret->left->parent = ret; } if( ret->right != NULL ) { ret->right->parent = ret; } } else { THROW_EXCEPTION(NoEnoughMemoryException, "No memory to create new node ..."); } } return ret; } public: SharedPointer< BTree<T> > clone() const { BTree<T>* ret = new BTree<T>(); if( ret != NULL ) { ret->m_root = clone(root()); } else { THROW_EXCEPTION(NoEnoughMemoryException, "No memory to create new btree ..."); } return ret; } bool operator == (const BTree<T>& btree) { return equal(root(), btree.root()); } bool operator != (const BTree<T>& btree) { return !(*this == btree); } SharedPointer< BTree<T> > add(const BTree<T>& btree) const { BTree<T>* ret = new BTree<T>(); if( ret != NULL ) { ret->m_root = add(root(), btree.root()); } else { THROW_EXCEPTION(NoEnoughMemoryException, "No memory to create new tree ..."); } return ret; } }; }
8、二叉樹的線索優化實現
什麼叫線索優化?將二叉樹轉換爲雙向鏈表的過程(非線性 --> 線性);能夠反映某種二叉樹的遍歷次序(結點的先後訪問次序),利用結點的 right 指針指向遍歷中的後繼結點,利用結點的 left 指針指向遍歷中的前驅節點。
如何對二叉樹進行線索化呢?過程如下
二叉樹的線索化,如下
層次遍歷算法如下:
1、將根結點壓入隊列中;
2、訪問隊頭元素指向的二叉樹結點;
3、隊頭元素彈出,將隊頭元素的孩子壓入隊列中;
4、判斷隊列是否爲空(非空:轉 2,空:結束)。
層次遍歷算法示例如下
函數接口設計:BTreeNode<T>* thread(BTTraversal order);根據參數 order 選擇線索化的次序(先序、中序、後序、層次),返回值線索化之後指向鏈表首結點的指針,線索化執行結束後對應的二叉樹變爲空樹。線索化流程如下所示
隊列中結點的連接算法如下所示 [connect(queue)]
具體源碼實現如下
namespace DTLib { template < typename T > class BTree : public Tree<T> { protected: void levelOrderTraversal(BTreeNode<T>* node, LinkQueue<BTreeNode<T>*>& queue) { if( node != NULL ) { LinkQueue<BTreeNode<T>*> tmp; tmp.add(root()); while( tmp.length() > 0 ) { BTreeNode<T>* n = tmp.front(); if( n->left != NULL ) { tmp.add(n->left); } if( n->right != NULL ) { tmp.add(n->right); } tmp.remove(); queue.add(n); } } } void traversal(BTTraversal order, LinkQueue<BTreeNode<T>*>& queue) { switch (order) { case PreOrder: perOrderTraversal(root(), queue); break; case Inorder: inOrderTraversal(root(), queue); break; case PostOrder: postOrderTraversal(root(), queue); break; case LeverOrder: levelOrderTraversal(root(), queue); break; default: THROW_EXCEPTION(InvalidParameterException, "Parameter order is invalid ..."); break; } } BTreeNode<T>* connect(LinkQueue<BTreeNode<T>*>& queue) { BTreeNode<T>* ret = NULL; if( queue.length() > 0 ) { ret = queue.front(); BTreeNode<T>* slider = queue.front(); queue.remove(); slider->left = NULL; while( queue.length() > 0 ) { slider->right = queue.front(); queue.front()->left = slider; slider = queue.front(); queue.remove(); } slider->right = NULL; } return ret; } public: BTreeNode<T>* thread(BTTraversal order) { BTreeNode<T>* ret = NULL; LinkQueue<BTreeNode<T>*> queue; traversal(order, queue); ret = connect(queue); this->m_root = NULL; m_queue.clear(); return ret; } }; }
通過對二叉樹的學習,總結如下:1、二叉樹的插入操作必須指明其插入的位置,正確處理指向父結點的指針;2、插入數據元素時需要從堆空間中創建結點,當數據元素插入失敗時需要釋放結點空間;3、刪除操作將目標結點所代表的子樹移除,必須完善處理父結點和子結點的關係;4、清除操作作用於銷燬樹中的每個結點,銷燬結點時判斷是否釋放對應的內存空間(工廠模式);5、二叉樹的典型遍歷都是以遞歸方式執行的,BTree 以不同的函數接口支持典型遍歷;6、層次遍歷與典型遍歷互不衝突,遍歷結果能夠反映樹結點訪問的先後次序;7、比較操作判斷兩棵二叉樹中的數據元素是否對應相等,克隆操作將當前二叉樹在堆空間中進行復制;8、相加操作將兩顆二叉樹中的數據元素在對應位置處相加,結果保存在堆空間的一顆二叉樹中。