递归创建二叉树以及一些基本操作
2017-05-27 20:08
417 查看
基本内容
二叉树的基本概念和遍历方式使用递归创建一个简单的二叉树
二叉树使用递归遍历时的调用栈帧
实现代码
一些基本操作的递归实现
一、一些基本的东西
首先我们要明确,二叉树是一种数据结构,相比于之前的顺序表和链表,二叉树是一种非线性结构,比较特殊,刚开始入门二叉树,我们需要了解二叉树的基本结构框架,知道如何搭建一个简单的二叉树,当然在這篇笔记或者博客里面提到的基本都是递归算法,要知道一些基本的二叉树的操作。之前我们在学习例如顺序表链表,栈和队列,会涉及到增删查改,但是在二叉树的基本部分我们不会涉及到增删查改,因为二叉树的实际用处不是很大,我们最重要的是需要了解二叉树的基本结构,为以后的搜索树和平衡树的学习打下基础。二叉树的遍历是目前阶段我们需要熟练掌握的内容。
二叉树的遍历方式有三种:前序、中序和后序。 在学习二叉树的时候,把二叉树分为三部分:根结点,左子树和右子树,所谓遍历方式即访问这三部分的先后顺序。
我对于二叉树遍历的方式的理解是這样的:
前序遍历:先访问根结点,再访问左子树,最后访问右子树。
中序遍历:先访问左子树,再访问根节点,最后访问右子树。
后序遍历:先访问左子树,再访问右子树,最后访问根结点。
以一个简单的二叉树为例子
该二叉树一共有六个结点,将6个整数放置其中。
一个结点里面分为三个部分,一个部分存放数据,另外两个存放指向左子树根结点的指针和指向右子树根结点的指针。
二、一些基本的代码
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> #include<string> #include<assert.h> #include<stack> #include<queue> using namespace std; template<class T> struct BinaryTreeNode { T _data; BinaryTreeNode<T>* _left; BinaryTreeNode<T>* _right; BinaryTreeNode(const T& d) :_data(d) ,_left(NULL) , _right(NULL) { cout << "构造二叉树结点" << endl; } }; template<class T> class BinaryTree { typedef BinaryTreeNode<T> Node; private: Node* _root; protected: Node* _CreateTree(T* a, size_t n, const T& invalid, size_t& index) //這里的index由调用這个函数的那个函数里面给传进来;但是這里出现了问题,还是因为栈帧的原因,在往根节点回的时候,下层的index并不会影响上层的index从而导致错 误解决方案是: & { //index = 0; //注意:這里不能给下标一个初值,递归会出现问题 Node* root = NULL; if (index < n && a[index] != invalid) { root = new Node(a[index]); root->_left = _CreateTree(a, n, invalid, ++index); //出现的问题: 我一开始写的是index++,但是出现了无限循环,注意创建的新的栈帧就会产生新的index root->_right = _CreateTree(a, n, invalid, ++index); } return root; } void _PrevOrder(Node* root) //前序遍历 { if (root == NULL) //返回条件 return; cout << root->_data << " "; _PrevOrder(root->_left); _PrevOrder(root->_right); } void _InOrder(Node* root) //中序遍历 { if (root == NULL) //返回条件 return; _InOrder(root->_left); cout << root->_data << " "; _InOrder(root->_right); } void _PostOrder(Node* root) //后序遍历 { if (root == NULL) //返回条件 return; _PostOrder(root->_left); _PostOrder(root->_right); cout << root->_data << " "; } void _LevelOrder(Node* root) // 层序遍历 使用非递归 利用queue的先进先出原则 [*] { queue<Node*> q; if (root) //根结点入队 q.push(root); else return; while (!q.empty()) { Node* cur = q.front(); cout << cur->_data << " "; if (cur->_left) q.push(cur->_left); if (cur->_right) q.push(cur->_right); q.pop(); //這里实际上是通过pop让树往下走的,完成了类似于递归的作用 } cout << endl; } size_t _Size(Node* root) //求结点个数 { if (root == NULL) return 0; if (root->_left == NULL && root->_right == NULL)// 在這个递归中的返回条件 return 1; return _Size(root->_left) + _Size(root->_right) + 1; //這里记得+1 因为有根结点 } size_t _LeafSize(Node* root) //求叶子结点个数 { if (root == NULL) return 0; if (root->_left == NULL&&root->_right == NULL) return 1; return _LeafSize(root->_left) + _LeafSize(root->_right); } size_t _GetKLevel(Node* root, size_t k) //求第K层结点个数 { if (root == NULL) return 0; if (k == 1) return 1; if (k > 1) return _GetKLevel(root->_left, k - 1) + _GetKLevel(root->_right, k - 1); //尾递归 else { cout << "'k' is wrong" << " "; return 0; } } size_t _Depth(Node* root) //求二叉树的深度 { if (root == NULL) return 0; if (root->_left == NULL && root->_right == NULL) return 1; size_t leftdepth = _Depth(root->_left); //分两边进行比较 size_t rightdepth = _Depth(root->_right); return leftdepth > rightdepth ? leftdepth + 1 : rightdepth + 1; //不要忘记+1 根结点 } Node* _Find(Node* root,const T& t) //查找 [*] { /*if (root == NULL) return NULL; Node* tmp = _Find(root->_left, t); if (root->_data == t) { return root; } _Find(root->_right, t);*/ if (root == NULL) return NULL; if (root->_data == t) return root; Node* tmp = _Find(root->_left, t); if (tmp) return tmp; return _Find(root->_right, t); } Node* _Copy(Node* root) //拷贝 [*] { if (root == NULL) return NULL; Node* copyroot = new Node(root->_data); copyroot->_left = _Copy(root->_left); copyroot->_right = _Copy(root->_right); return copyroot; } void Destory(Node* root) { if (root == NULL) return; Destory(root->_left); Destory(root->_right); delete root; //释放当前结点 root = NULL; } public: BinaryTree() //无参构造函数 : _root(NULL) { } BinaryTree(T* a, size_t n, const T& invalid) //带参的构造函数 //注意:无参的递归是不能写在公有的成员函数里面的 { size_t index = 0; //注意這里为什么我们把index单独拿出来给初始化0 _root = _CreateTree(a, n, invalid, index); } BinaryTree(const BinaryTree<T>& t) //拷贝构造 { _root = _Copy(t._root); } BinaryTree<T>& operator=(const BinaryTree<T>& t) //赋值运算符重载 { if (_root) Destory(_root); _root = _Copy(t._root); return *this; } void PrevOrder() //前序遍历 { _PrevOrder(_root); cout << endl; } void InOrder() //中序遍历 { _InOrder(_root); cout << endl; } void PostOrder() //后序遍历 { _PostOrder(_root); cout << endl; } void LevelOrder() //层序遍历 (這个用不到递归,所以可以不用這样写) { _LevelOrder(_root); } size_t Size() //结点个数 { return _Size(_root); } size_t LeafSize() //叶子结点个数 { return _LeafSize(_root); } size_t GetKLevel(size_t k) //K层结点 { return _GetKLevel(_root, k); } size_t Depth() //树的深度 { return _Depth(_root); } Node* Find(const T& d) //查找 { return _Find(_root, d); } ~BinaryTree() //析构 { Destory(_root); _root = NULL; cout << "析构" << endl; } }; void test() //———————— 测试 —————————— { int arr[] = { 1, 2, 3, '#', '#', 4, '#', '#', 5, 6 }; BinaryTree<int> t1(arr,sizeof(arr)/sizeof(arr[0]),'#'); t1.PrevOrder(); t1.InOrder(); t1.PostOrder(); t1.LevelOrder(); cout << t1.Size() << endl; cout << t1.LeafSize() << endl; cout<<t1.GetKLevel(3)<<endl; cout << t1.Find(3) << endl; BinaryTree<int> t2(t1); t2.PrevOrder(); t2.InOrder(); t2.PostOrder(); t2.LevelOrder(); cout << t2.Size() << endl; cout << t2.LeafSize() << endl; cout << t2.GetKLevel(3) << endl; cout << t2.Find(3) << endl; BinaryTree<int> t3; t3 = t1; t3.PrevOrder(); t3.InOrder(); t3.PostOrder(); t3.LevelOrder(); } int main() { test(); return 0; }
在树的创建代码中出现的一个问题:为什么下标index 要使用引用?
Node* _BinaryTree(T* a, size_t n, const T& invalid, size_t& index)
注意上面的构造函数里面的index使用了引用
如果不使用引用就会出现问题
原因是什么呢?我的理解是這样的:
由于我们使用数组来创建,index是下标,是需要不断的往后走的,如果不使用引用,当开辟了栈空间之后新的栈空间的index得上一层栈空间的index没有影响,這就导致了左子树建立完毕了回到根节点来建立右子树的时候index又回退了,因为是根据数组来建树的,index就不能回退,所以所以使用引用是为了让数组的下标不断往后走不受栈帧的影响。
三、递归创建以及遍历的调用栈帧
二叉树在建造的时候的栈帧图:(图中的代码已经简化不具有严格的正确性只是作为示意)
此二叉树在遍历(前序)的时候栈帧示意图:
三、一些基本的操作接口的递归实现
1.递归求第K层的叶子结点数:代码:
size_t _KLevelSize(Node* root,const size_t& K) [*] { if (root == NULL) return 0; if (K == 1) return 1; if (K > 1) return _KLevelSize(root->_left, K - 1) + _KLevelSize(root->_right, K - 1); else perror("K is wrong!"); }
调用栈帧示意:
2.递归求数的深度(左树与右树相比)
代码:
size_t _Depth(Node* root) { if (root == NULL) return 0; if (root->_left == NULL&&root->_right == NULL) return 1; size_t leftDepth = _Depth(root->_left); size_t rightDepth = _Depth(root->_right); return leftDepth > rightDepth? leftDepth + 1: rightDepth+1; //加1是加上根结点 }
3.递归查找 *
注:查找即一旦找到了就返回(返回到最顶上的栈空间) 结束整个递归的过程
代码:
Node* _Find(Node* root, const T& d) //查找[*] { if (root == NULL) return NULL; if (root->_data == d) return root; /*else b2e2 //這样写有问题 { _Find(root->_left,d); _Find(root->_right, d); }*/ Node* tmp = _Find(root->_left,d); if (tmp != NULL) //注意這个判断放的位置 return tmp; _Find(root->_right, d); //子问题思想 }
4.递归 析构树(Destory函数)
注意:這里要注意对于一棵树而言,析构或者释放空间是从底下往上面析构的,也就是需要先析构子节点再析构父亲节点,对于每一个子问题而言也是這样,所以再函数中delete应该放在递归调用的后面
代码:
void Destory(Node* root) //[*] { if (root == NULL) return; Destory(root->_left); Destory(root->_right); delete root; //注意這里的delete一定要放在最后 相当于后序析构,因为先析构根结点子树就找不到了,没办法析构 }
5.递归 拷贝树(Copy函数)
注意:针对于实现二叉树的拷贝构造和赋值运算符的重载的时候 使用到了拷贝函数,這里也需要建立子问题的思想
代码:
Node* _Copy(Node* root) { Node* newroot = NULL; if (root == NULL) return NULL; newroot = new Node(root->_data); newroot->_left = _Copy(root->_left); newroot->_right = _Copy(root->_right); return newroot; }
总结:
以上只是递归实现递归是用空间换取时间(写代码简单)的编程方式,如果使用非递归,编写的难度就会增加,在后面的博客会继续整理有关于非递归的方式遍历树以及一些非递归实现的操作接口
相关文章推荐
- 二叉树的创建及一些基本操作
- 二叉树的创建和基本操作(递归和非递归)
- 二叉树的基本操作(创建、递归和非递归遍历、求深度、求叶子数)
- 二叉树的基本操作精集(创建、遍历、求深度结点以及叶子结点个数)
- C++递归创建、非递归遍历二叉树的基本操作
- 二叉树与其基本递归操作:创建、遍历、特征量计算等
- 【数据库】创建表以及一些基本的表操作
- c++模板实现二叉树,线索化,线索化遍历,非递归遍历及一些基本操作
- 对树的一些操作.比如遍历.比如.根据先序和中序创建二叉树
- 二叉树的创建及基本操作。
- 嵌入式 QT创建数据库以及数据库基本操作问题
- 二叉树的递归遍历非递归遍历以及其他二叉树的相关操作实现(数据结构)
- 【mysql】表的创建以及基本操作
- Java 二叉树一些基本操作
- Maven学习记录之maven基本操作命令,maven本地工厂的创建,maven骨架的生成,以及在eclipse中创建maven工程:
- Flash的一些基本概念以及MTD操作注意点
- 二叉树的递归创建,以及二叉查找树查找的建立 和遍历查找的比较
- 黑马程序员JAVA基础-数组以及一些基本操作
- 链式二叉树的前序创建、递归前序遍历、非递归堆栈前序遍历、前序销毁以及求二叉树的深度
- 07单链表的一些基本操作(创建、测量、插入、打印、删除)