c++模板实现二叉树,线索化,线索化遍历,非递归遍历及一些基本操作

来源:互联网 发布:java中final变量 编辑:程序博客网 时间:2024/05/18 13:10

c++模板类实现二叉树一些基本操作:前中后 遍历 及其 非递归遍历。  二叉树线索化, 及线索化后采用非递归方式遍历。  层序遍历。  叶子结点个数, k层节点个数, 二叉树深度, 查找元素等。


两个头文件, 一个是线索化有关, 另一个是别的操作


BinaryTree:

#ifndef BINARY_TREE_H_#define BINARY_TREE_H_//树这,递归思想很重想(而且,不管是以递归还是非递归解决问题,重要思想是:子问题(按递归子问题思路想,比如只考虑树有左右两个节点的情况下(且都为NULL)怎么处理(或者有两个结点,且都为子节点(即节点后无结点))(再或者只有一个根呢?),再把左右树当新结点(根)采用同样的方法就可以了)  只要理解了这一点,很多题都很好做#include <queue>//层序遍历#include <stack>//非递归实现遍历template<class T>struct BinaryTreeNode{BinaryTreeNode<T>* _left;//<T>BinaryTreeNode<T>* _right;//<T>T _date;BinaryTreeNode( const T& x ):_date( x ),_left( NULL ),_right( NULL ){}};template<class T>class BinaryTree{typedef BinaryTreeNode<T> Node;//<T>,以及这条语句放在类里面(这个Node只是BinaryTreeNode  为了简便用Node,但为防止重命名, 所以放在类里面,而不是外面)public:BinaryTree( )//相当于还是自己写了个 默认的构造函数 只是使用了c++11初始化方式:_root( NULL ){}BinaryTree( T* a, size_t n, const T& invalid = T( ) )//成员函数不易含有递归{size_t index = 0;_root = CreateTree( a, n, invalid, index );//所以调用这个Fun}~BinaryTree( )//后序最好{_Destroy( _root );}BinaryTree( const BinaryTree<T>& rhs )//<T>深拷贝//参数const修饰  不可改{_root = Copy( rhs._root );//想想this指针//指针访问数据成员用->   对象用.!  傻不傻!?   ->用惯了,啥都用->?}/*  参数const修饰不可改, but返回的不是const对象, 所以返回值无const  */BinaryTree<T>& operator=( const BinaryTree<T>& rhs )//<T> 想想 t1 = t2 先释放t1原空间,再通过t2构建一棵新树 (即下面写的内容){if ( this != &rhs )//别忘了检查是否是自己赋值自己{_Destroy( _root );Node* newRoot;newRoot = Copy( rhs._root );_root = newRoot;}//不是返回const对象,所以,返回值别用const修饰  除非是const对象,返回时再用const修饰return *this;};//别用 const 修饰 this 指针//BinaryTree<T>& operator=( const BinaryTree<T>& rhs )//现代写法 自己写= 则开辟新的空间(根据你的写法)(先delete旧空间,再开辟新空间)( 临时对象就像临时变量一样,传引用不会有 )//{//swap( _root, rhs->_root );//直接  这样写不对吧?rhs->_root 变为 (this)->_root  rhs这个被引用的对象不是出问题了吗//return *this;//传对象时才构建临时对象(想临时变量),然后直接赋值(出作用域销毁, 如果是指针则出问题  所以是引用, 不存在临时对象问题)//}//都是默认的时, t1 = t2. 构造临时对象直接赋值?   参数是啥 引用(直接赋值)还是临时对象(如果自己写了开辟新空间,默认则直接赋值)。 临时对象销毁(参数有指针则出问题,对象销毁都会调用析构函数(此时又牵扯到默认析构函数是什么)),临时对象和t2相同(通过默认拷贝构造创建临时对象,值拷贝).   赋值构造和=(原参数引用)自己写都开辟新空间void PrevOrder( )//含有递归的不适宜放在public{_PrevOrder( _root );}void PrevOrderNonR( )//前序遍历非递归实现{Node* cur = _root;stack<Node*> s;while ( (NULL != cur) || (!s.empty( )) )//不只是stack不为空,第一次stack为空(未压栈) 此时需加条件cur不为空 而cur为空时,并不是一定遍历完了,所以也需要stack不为空.                访问完左树后,把右树当新树处理(子问题){while ( NULL != cur ){s.push( cur );cout << cur->_date << " ";cur = cur->_left;}Node* top = s.top( );s.pop( );cur = top->_right;}cout << endl;}void InOrder( )//前中后说的是根,  先左再右  写的时候从根开始写   再依次往上加  数组变为树{_InOrder( _root );}void PostOrder( ){_PostOrder( _root );}void LevelOrder( ){queue<Node*> q;//队列,尾进头出if ( NULL == _root ){return;}q.push( _root );while ( !q.empty( ) ){Node* front = q.front( );if ( NULL != front ){q.push( front->_left );q.push( front->_right );cout << front->_date << " ";q.pop( );}}}size_t Size( ){return _Size( _root );}Node* Find( const T& x ){return _Find( _root, x );}//size_t GetLeafSize( )//因为不能显式使用this指针, 所以再嵌套一个Fun//{//size_t count = 0;//count(引用) 为 局部变量 而不是 静态 所以 无线程安全问题(对照Size()Fun)//_GetLeafSize( _root, count );//return count;//}//ex1size_t GetLeafSize( ){return _GetLeafSize( _root );}//ex2//size_t GetKLevelSize( size_t k )//{//size_t count = 1;//记录层数结点数通过函数返回值得到  参数用count引用 而不需要在子函数中使用静态变量或直接使用全部变量////return _GetKLevelSize( _root, count, k );//}//ex1size_t GetKLevelSize( size_t k )//想子问题  根的第k层, k==1则就是这一层, 否则子节点的k-1层,直到k等于1时结束{return _GetKLevelSize( _root, k );}//ex2size_t Depth( ){return _Depth( _root );}//重建二叉树 通过:  找到根节点 和 左半边树 与 右半边树即可构建.     如通过前序找到根,通过中序找到左右半边子树 如: 324165(中 找左右半边树),123456(前序 找根)   protected:Node* _root;Node* CreateTree( T* a, size_t n, const T& invalid, size_t& index )//因为递归会创建多个index变量,为使index值正确,此处用引用  而第一处用引用是处于节省空间考虑{Node* root = NULL;//采用前序遍历创建二叉树if ( (index < n) && (a[index] != invalid) )//先序前序后序是以根为前中后.  通过数组写树时,  按顺序写  比如前序 根左右  就先写大框架,留出缝隙, 在当子问题处理, 往中间加数据(即把根 左 右都当作一个新根  子问题){root = new Node( a[index] );//注意圆括号与方括号的区别  圆括号构建一个用delete  而方括号构建多个 用delete[]root->_left = CreateTree( a, n, invalid, ++index );//构建完左子树再执行下面构建右子树root->_right = CreateTree( a, n, invalid, ++index );}return root;}void _PreOrder( Node* root )//前序遍历{if ( NULL == root )//注意顺序{return;//void return;  有数据类型 则return 0(or 别的).}cout << root->_data << " ";_PreOrder( root->_left );_PreOrder( root->_right );}void _InOrder( Node* root )//中序实现{if ( NULL == root ){return;}_InOrder( root->_left );cout << root->_data << " ";_InOrder( root->_right );}void _PostOrder( Node* root )//后序实现{if ( NULL == root ){return;}_PostOrder( root->_left );_PostOrder( root->_right );cout << root->_data << " ";}size_t _Size( Node* root )//结点数1{if ( NULL == root )//主要是判断某个节点的子节点是不是为NULL(不存在),而不是判断是否一进来是空树(当然也可以判断){return 0;}return ( _Size( root->_left ) + _Size( root->_right ) + 1 );//左节点+右节点+1(自己(根节点))}//size_t _Size( Node* root )//结点数2//{//if ( NULL == root )//{//return 0;//}//static int count = 0;//静态变量(编译时初始化), 因为静态区不像函数有自己栈帧. 它会被很多人访问, 所以如果有多个人同时访问此count 程序可能出错(线程安全问题,count值不正确) //++count;//_Size( root->_left );//转换为子问题//_Size( root->_right );//return count;//}Node* _Find( Node* root, const T& x ){if ( NULL == root ){return NULL;}if ( x == root-> _date ){return root;}Node* ret;//声明与定义分离ret = _Find( root->_left, x );if ( NULL != ret )//只需写一个条件, ret 不为NULL 即找到了//递归每层都是返回到哪一句呢? 递归的那一句{return ret;}return ( _Find( root->_right, x ) );//不用像上面一样写个条件判断,直接return 未找到即返回NULL,否则即找到了}Node* Copy( Node* root )//拷贝一个树{if ( NULL == root ){return NULL;}Node* newNode;newNode = new Node( root->_date );newNode->_left = Copy( root->_left );newNode->_right = Copy( root ->_right );return newNode;}void _Destroy( Node* root ){if ( NULL == root ){return;}_Destroy( root->_left );_Destroy( root->_right );delete root;}//void _GetLeafSize( Node* root, size_t& count )//注意此处count类型//{//if ( NULL == root )//{//return ;//}//if ( (NULL == root->_left) && (NULL == root->_right) )//{//++count;//}//_GetLeafSize( root->_left, count );//_GetLeafSize( root->_right, count );//}//ex1//因为count为引用,所以可以没有返回值size_t _GetLeafSize( Node* root )//放在保护,不让外部访问。 size_t return 0;  void return;{if ( NULL == root ){return 0;}if ( (NULL == root->_left) && (NULL == root->_right) ){return 1;}return ( _GetLeafSize( root->_left ) + _GetLeafSize( root->_right ) );}//ex2//size_t _GetKLevelSize( Node* root, size_t count, const size_t& k )//const别乱加, 需要再加   你不要把 const 和 & 当成必须写的。 就像这,count就不能传引用,传引用就出错了, 只能临时变量。  引用目的:1.节省空间  2.为了让这个变量不成为临时变量(如果是临时变量则有坑的情况)(不同情况,目的是1还是2不同).注意下,别盲目写  //{//if ( NULL == root )//{//return 0;//}//if ( k == count )//{//return 1;//}//++count;//return ( _GetKLevelSize( root->_left, count, k ) + _GetKLevelSize( root->_right, count, k ) );//count此时必须是临时变量,引用的话就出错了//}//ex1size_t _GetKLevelSize( Node* root, size_t k )//不能传引用,理由参照ex1{if ( NULL == root ){return 0;}if ( 1 == k ){return 1;}--k;return ( _GetKLevelSize( root->_left, k ) + _GetKLevelSize( root->_right, k ) );}size_t _Depth( Node* root ){if ( NULL == root ){return 0;}return (( _Depth( root->_left ) > _Depth( root->_right ) ? _Depth( root -> _left ): _Depth( root->_right ) ) + 1);}};#endif


BinaryTreeThread.h:

/*  线索化二叉树  *//*  树这,每棵树(一定要有当左右子树对待的思想)  *//*  什么是子问题, 一棵树如果前序访问, 当它到一个新的节点, 它又成为根, 可以前序访问时, 就是子问题  *//*  每次写完条件后根据很简单树自己想下所写条件是否正确(测试),也可根据很简单树来推演需要的条件(需要几种情况)  *///F5   只走断点!!!enum PointerTag{ THREAD, LINK };template<class T>struct BinaryTreeThreadNode//结点 多了两个成员, 左孩子为空则指向前驱结点, 右孩子为空则指向后继节点{T _date;BinaryTreeThreadNode<T>* _left;BinaryTreeThreadNode<T>* _right;//TPointerTag _leftTag;//他们不需要是指向这个节点的指针PointerTag _rightTag;BinaryTreeThreadNode( const T& x )//不需要<T>, T是参数类型:_date( x ),_left( NULL ),_right( NULL ),_leftTag( LINK ),_rightTag ( LINK ){}};//别忘了;template<class T>class BinaryTreeThread{typedef BinaryTreeThreadNode<T> Node;public:BinaryTreeThread( )//不给缺省值,则默认构造必须提供:_root( NULL ){}BinaryTreeThread( T* a, size_t n, const T& invalid = T( ) ){size_t index = 0;_root = CreateTree( a, n, index, invalid );}void InOrderThreading( )//中序线索化(需要递归实现,但是递归实现线索化二叉树后, 再通过线索化后树遍历时不再需要递归){Node* prev;prev = NULL;_InOrderThreading( _root, prev );}void PrevOrderThreading( ){Node* prev;prev = NULL;_PrevOrderThreading( _root, prev );}void InOrderThd( )//最左子节点开始,  (左子树的最右结点下一个)一直到主根,  再一直到最右结点结束{Node* cur = _root;while ( NULL != cur ){while ( LINK == cur->_leftTag ){cur = cur->_left;}cout << cur->_date << " ";while ( THREAD == cur->_rightTag )//等于LINK时(右结点(未线索化之前)不为空,这是一个新节点(此时当子问题处理)) 此时跳出循环{cur = cur->_right;cout << cur->_date << " ";}cur = cur->_right;//所以在此处  将其当子问题处理}}//中序中最左节点的左子树为NULL  最右结点的右子树也为NULL  其余结点都不为NULL   访问完左树必定跳到根,即左树的最右结点的右子树为根  。  处理完基准情形后,不管怎么跳,只要为跳为子问题就不用管,因为子问题嘛, 已经有处理它的方法了,只需再考虑最后的最后要处理的那个就行了(还需考虑的只有这么一个节点)void PrevOrderThd( ){Node* cur = _root;while ( NULL != cur ){while ( LINK == cur->_leftTag ){cout << cur->_date << " ";cur = cur->_left;}cout << cur->_date << " ";cur  = cur->_right;}}protected:Node* _root;Node* CreateTree( T* a, size_t n, size_t& index, const T& invalid )//index为引用但不能为const{Node* root = NULL;if ( (index < n) && (invalid != a[index]) )//在index小于n,情况下才执行第二个条件{root = new Node(a[index]);//index不能在这++  必须保证每次递归时,都在递归参数那增加index.  因为if内语句不一定每次都执行, 所以在这index++  可能导致 几次使用相同数组值, 看下面index如何++root->_left = CreateTree( a, n, ++index, invalid );root->_right = CreateTree( a, n, ++index, invalid );}return root;}//前序构建void _InOrderThreading( Node* cur, Node*& prev )//中序线索化 先别想太多,先把第一遍想正确,递归时强制理解为子问题就可以了  prev必须是引用,否则就出错了  返回上层后  prev用的是上层(本层)的值,而不是返回时的prev值, 向下调用不用引用可以,但返回没有引用就错了{if ( NULL == cur ){return;}_InOrderThreading( cur->_left, prev );if ( NULL == cur->_left )//最左边结点的左子树为NULL(前驱为NULL)(它之前没有访问过结点)我们也规定它被线索化了,只是前驱为NULL{cur->_leftTag = THREAD;cur->_left = prev;}if ( (NULL != prev) && (NULL == prev->_right) )//一定不要忘记,类成员前的下划线_啊  如果不判断prev为NULL  则prev为NULL情况, 第二个直接出错(所以利用 && 的特点时,注意条件的顺序){prev->_rightTag = THREAD;prev->_right = cur;} prev = cur;_InOrderThreading( cur->_right, prev );}//中 左必跳到根void _PrevOrderThreading( Node* cur, Node*& prev )//遍历时,访问完左子数最后一个结点  必跳到右 (前序)所以线索化时不用想访问完左子数最后一个结点 ,怎么办它一定跳向右树{if ( NULL == cur ){return;}if ( NULL == cur->_left ){cur->_leftTag = THREAD;cur->_left = prev;}if ( (NULL != prev) && (NULL == prev->_right) ){prev->_rightTag = THREAD;prev->_right = cur;} prev = cur;if ( LINK == cur->_leftTag )//无这个条件判断会死循环  int array [10] = {1, 2, 3, '#', '#', 4, '#' , '#', 5, 6};   2到3会死循环{_PrevOrderThreading( cur->_left, prev );}if ( (THREAD == cur->_rightTag) && (cur->_left == cur->_right) )//无这个条件判断会死循环  int array [10] = {1, 2, 3, '#', '#', 4, '#' , '#', 5, 6};   5到6  再往后走  6自己会死循环!{;}else{_PrevOrderThreading( cur->_right, prev );}}};//最开始就加上;


.c:

#include <iostream>#include <windows.h>using namespace std;//#include "BinaryTreeThread.h"#include "BinaryTree.h"void test( );int main( ){test( );system( "pause" );return 0;}void test( ){int a[15] = {1,2,'#',3,'#','#',4,5,'#',6,'#',7,'#','#',8};BinaryTree<int> t1( a, sizeof(a) / sizeof(a[0]), '#' );//BinaryTreeThread<int> t1( a, (sizeof(a) / sizeof(a[0])), '#' );//注意 sizeof 使用//t1.InOrderThreading( );//t1.InOrderThd( );//cout << t1.Find( 3 )->_date << endl;//注意此处的写法   ->//cout << t1.GetKLevelSize( 0 ) << endl;//cout << t1.Depth( ) << endl;//t1.InOrderThreading( );//t1.InOrderThd( );//t1.PrevOrderThreading( );//t1.PrevOrderThd( );t1.PrevOrderNonR( );}


0 0