上节博客我们介绍了通用树到二叉树的转换,今天我们就来看看二叉树的实现。我们先来看看它的组成结构,如下
设计要点:
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 ×××ert(TreeNode<T>* node) { bool ret = true; return ret; } virtual bool ×××ert(TreeNode<T>* node, BTNodePos pos) { bool ret = true; return ret; } bool ×××ert(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 ×××ert(TreeNode<T>* node); bool ×××ert(TreeNode<T>* node, BTNodePos pos); b> 插入数据元素:bool ×××ert(const T& value, TreeNode<T>* parent); bool ×××ert(const T& value, TreeNode<T>* parent, BTNodePos pos)。
新结点的插入如图所示
a> 指定位置的结点插入,如下图所示
插入新结点,如下图所示
b> 插入数据元素
我们来看看源码具体是怎么实现的
BTreeNode.h 中添加如下
enum BTNodePos { ANY, LEFT, RIGHT };
namespace DTLib { template < typename T > class BTree : public Tree<T> { protected: virtual bool ×××ert(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 ×××ert(TreeNode<T>* node) { return ×××ert(node, ANY); } virtual bool ×××ert(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 = ×××ert(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 ×××ert(const T& value, TreeNode<T>* parent) { return ×××ert(value, parent, ANY); } virtual bool ×××ert(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 = ×××ert(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); } 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); } 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、相加操作将两颗二叉树中的数据元素在对应位置处相加,结果保存在堆空间的一颗二叉树中。