启迪思维:二叉树
很多初级点的程序员会认为树结构无用论,也有初级程序员仅仅以为只有面试才会用到,还有自认为实际工作用不到(我身边工作好几年程序员懂树结构也没有几个),其实归根到底还是不清楚树的实际用途,下面分享我参加培训时候一个小尴尬。
因为项目数据量很大(有很多表数据量都上亿的),对写sql能力要求很高,项目组会经常组织些数据库方面的培训,前段时间又参加公司一个SQL原理分析的一个培训,在培训中讲师问“为什么SQL走索引查询速度很快呢?”,我直接大声说“索引底层数据结构是B树,查询的时候用二分查找”,结果整个大房间就我一个人声音,所有同事都看过来,场面有点尴尬。
上一篇文章分析树基本概念、名词解释、树三种遍历方式,今天继续来看二叉树各种名词概率,看这些名词概念,肯定是各种不爽,大概浏览下知道怎么回事就ok。
一:概念,下面这些内容摘自维基百科
在计算机科学中,二叉树是每个节点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。二叉树常被用于实现二叉查找树和二叉堆。
二叉树的每个结点至多只有二棵子树(不存在度大于2的结点),二叉树的子树有左右之分,次序不能颠倒。二叉树的第i层至多有个结点;深度为k的二叉树至多有个结点;对任何一棵二叉树T,如果其终端结点数为,度为2的结点数为,则。
树和二叉树的三个主要差别:
树的结点个数至少为1,而二叉树的结点个数可以为0;
树中结点的最大度数没有限制,而二叉树结点的最大度数为2;
树的结点无左、右之分,而二叉树的结点有左、右之分。
完全二叉树和满二叉树
满二叉树:一棵深度为k,且有个节点成为满二叉树
完全二叉树:深度为k,有n个节点的二叉树,当且仅当其每一个节点都与深度为k的满二叉树中序号为1至n的节点对应时,称之为完全二叉树
二:示例图
三:动画图
四:代码分析
1、是否为空
1 /** 2 * 若树为空,则返回true;否则返回false 3 */ 4 bool IsEmpty() { 5 return root == 0; 6 }
2、计算树的深度
1 /** 2 * 以传入节点为基础计算树的深度 3 */ 4 int GetTreeDept(const TNode<T> *t) { 5 int i, j; 6 if (t == 0) { 7 return 0; 8 } else { 9 //递归计算左子树的深度 10 i = this->GetTreeDept(t->lchild); 11 //递归计算右子树的深度 12 j = this->GetTreeDept(t->rchild); 13 } 14 15 //t的深度为其左右子树中深度中的大者加1 16 return i > j ? i + 1 : j + 1; 17 }
3、清空树的节点
1 /** 2 *清空树的所有节点 3 */ 4 void Clear() { 5 Clear(root); 6 } 7 8 /** 9 *根据节点递归清空树 10 */ 11 void Clear(TNode<T>* t) { 12 //判断指针是否为空 13 if (t) { 14 //获取资源立即放入管理对象(参考Effective C++里边条款13) 15 //tr1::shared_ptr(引用计数智慧指针)管理对象比auto_ptr更强大 16 std::auto_ptr<TNode<T> > new_ptr(t); 17 18 //递归清空右子树 19 Clear(new_ptr->rchild); 20 21 //递归清空左子树 22 Clear(new_ptr->lchild); 23 } 24 25 //清空树的根节点 26 t = 0; 27 }
4、获取树最大节点和最小节点
1 /** 2 *获取树的最大节点 3 */ 4 TNode<T>* GetMax(TNode<T>* t) const { 5 //判断数节点是否为空 6 if (t) { 7 //根据二叉树特性,最大值一定在右子树; 8 //循环右子树,直到叶子节点 9 while (t->rchild) { 10 //指向下一个节点 11 t = t->rchild; 12 } 13 } 14 //返回找到最大节点 15 return t; 16 } 17 /** 18 *获取树的最小节点 19 */ 20 TNode<T>* GetMin(TNode<T>* t) const { 21 //判断数节点是否为空 22 if (t) { 23 //根据二叉树特性,最大值一定在左子树; 24 //循环左子树,直到叶子节点 25 while (t->lchild) { 26 //指向下一个节点 27 t = t->lchild; 28 } 29 } 30 //返回找到最小节点 31 return t; 32 } 33 /** 34 *根据模式类型查找树的最大值或者最小值 35 */ 36 TNode<T>* GetNode(Mode mode) const { 37 //t指向根节点 38 TNode<T>* t = root; 39 //判断数节点是否为空 40 if (t) { 41 if (mode == Min) { 42 //根据二叉树特性,最大值一定在左子树; 43 //循环左子树,直到叶子节点 44 while (t->lchild) { 45 //指向左子树下一个节点 46 t = t->lchild; 47 } 48 } else if (mode == Max) { 49 //根据二叉树特性,最大值一定在右子树; 50 //循环右子树,直到叶子节点 51 while (t->rchild) { 52 //指向右子树下一个节点 53 t = t->rchild; 54 } 55 } 56 } 57 //返回找到节点 58 return t; 59 }
5、获取传入节点父节点
1 /** 2 *获取传入的节点从传入树p中找到它的父节点 3 */ 4 TNode<T>* GetParentNode(TNode<T> *p, const T &value,TNode<T> *returnValue) { 5 //p节点存在并且传入的值不是根节点值 6 if (p && p->data == value) { 7 return 0; 8 } 9 10 //用二分查找定位值value所在节点 11 TNode<T> *t = this->SearchTree(p, value); 12 13 //判断t和p都不为空 14 if (t && p) { 15 //如果value的节点等于p节点左孩子或者右孩子,p就是value的节点父亲 16 if (p->lchild == t || p->rchild == t) { 17 //赋值p节点给返回值变量 18 returnValue = p; 19 } else if (value > p->data) {//如果value只大于p节点值,则递归右孩子 20 //直到找到value的父节点复制给返回值变量 21 returnValue = GetParentNode(p->rchild, value,returnValue); 22 } else {////如果value只小于p节点值,则递归左孩子 23 //直到找到value的父节点复制给返回值变量 24 returnValue = GetParentNode(p->lchild, value,returnValue); 25 } 26 } 27 28 return returnValue; 29 30 } 31 32 /** 33 *获取传入的节点的父节点 34 */ 35 TNode<T>* GetParentNode(const T &value,TNode<T> *returnValue) { 36 return this->GetParentNode(root, value,returnValue); 37 }
6、二分查找
代码分析:
1 /** 2 * 在以T为根节点的树中搜索值为value的节点 3 */ 4 TNode<T>* SearchTree(TNode<T>* &t, const T &value) { 5 //判断t节点是否为空 6 while (t) { 7 //如果节点值等于value,则表明已经找到目标节点 8 if (t->data == value) { 9 return t; 10 } else if (value > t->data) {//如果value大于t节点值,则递归查询右子树 11 return SearchTree(t->rchild, value); 12 } else {//如果value小于t节点值,则递归查询左子树 13 return SearchTree(t->lchild, value); 14 } 15 } 16 return t; 17 }
动画演示:
7、插入节点
代码分析:
1 /** 2 *插入一个节点到目标树中 3 */ 4 void Insert(const T &value, TNode<T>* &t) { 5 //如果目标树为空,则新new一个根节点 6 if (t == 0) { 7 //新创建一个节点,并把value设置为节点值 8 t = new TNode<T>(value); 9 } else if (value < t->data) {//如果value值小于t节点值 10 //递归左子树插入函数,直到找到节点插入 11 this->Insert(value, t->lchild); 12 } else if (value > t->data) {//如果value值大于t节点值 13 //递归右子树插入函数,直到找到节点插入 14 this->Insert(value, t->rchild); 15 } 16 } 17 /** 18 *插入一个节点到根节点为root的树中 19 */ 20 void Insert(const T &value) { 21 this->Insert(value, root); 22 }
动画演示
8、删除节点
代码分析:
1 /** 2 *根据节点值删除节点信息 3 */ 4 void Delete(const T &value) { 5 Delete(value, root); 6 } 7 /** 8 *根据节点值删除以传入t为根节点树节点信息 9 */ 10 void Delete(const T &value, TNode<T>* &t) { 11 //判断是否t为空null 12 if (t) { 13 //通过二分查找定位value所在的节点 14 TNode<T> *p = this->SearchTree(t, value); 15 //中间变量,用于待删除节点左右子树都不为空的情况下 16 TNode<T> *q = p; 17 if (p) { 18 //如果p节点的左右孩子都不为空,则根据二叉树定义 19 //必须在子右子树中找到最新节点作为新节点 20 //当左右子树都为空情况下,右子树最小节点就是树(中序遍历)节点的后继节点 21 if (p->lchild != 0 && p->rchild != 0) { 22 //获取右子树中最小的节点 23 q = this->GetMin(p->rchild); 24 } 25 //获取资源立即放入管理对象(参考Effective C++里边条款13) 26 //tr1::shared_ptr(引用计数智慧指针)管理对象比auto_ptr更强大 27 //如果p节点的左右子树都不为空,则释放p节点子右子树的最小节点 28 //改变p节点的值即可 29 auto_ptr<TNode<T> > new_ptr(q); 30 31 TNode<T> *parent = 0; 32 TNode<T> *returnValue; 33 //删除叶子节点(节点左右孩子都为空) 34 if (p->lchild == 0 && p->rchild == 0) { 35 //如果p节点和传入的根节点相等 36 if (t == p) { 37 //直接设置t为空 38 t = 0; 39 } else { 40 //获取p节点的父节点 41 parent = this->GetParentNode(t, p->data,returnValue); 42 43 //如果父节点的左孩子等于p节点 44 if (parent->lchild == p) { 45 //设置父节点的左孩子等于空 46 parent->lchild = 0; 47 } else {//如果父节点的右孩子等于p节点 48 //设置父节点的右孩子等于空 49 parent->rchild = 0; 50 } 51 } 52 53 } else if (p->rchild == 0) {//删除节点p右孩子为空,左孩子有节点 54 //如果p节点和传入的根节点相等 55 if (t == p) { 56 //直接设置t节点等于左孩子 57 t = t->lchild; 58 } else { 59 //获取p节点的父节点 60 parent = this->GetParentNode(t, p->data,returnValue); 61 //如果父节点的左孩子等于p节点 62 if (parent->lchild == p) { 63 //设置父节点左孩子等于p节点左孩子 64 parent->lchild = p->lchild; 65 } else {//如果父节点的右孩子等于p节点 66 //设置父节点右孩子等于p节点左孩子 67 parent->rchild = p->lchild; 68 } 69 } 70 71 } else if (p->lchild == 0) {//删除节点p左孩子为空,右孩子有节点 72 //如果p节点和传入的根节点相等 73 if (t == p) { 74 //直接设置t节点等于右孩子 75 t = t->rchild; 76 } else { 77 //获取p节点的父节点 78 parent = this->GetParentNode(t, p->data,returnValue); 79 //如果父节点的右孩子等于p节点 80 if (parent->rchild == p) { 81 //设置父节点右孩子等于p节点右孩子 82 parent->rchild = p->rchild; 83 } else {//如果父节点的左孩子等于p节点 84 //设置父节点右孩子等于p节点右孩子 85 parent->lchild = p->rchild; 86 } 87 } 88 } else {//删除节点p左右都有孩子 89 //获取q节点的父节点 90 parent = this->GetParentNode(t,q->data,returnValue); 91 //设置p节点值等于q节点值 92 p->data = q->data; 93 //如果q的父节点等于p 94 if (parent == p) { 95 //设置q节点的父节点右孩子为q节点右孩子 96 parent->rchild = q->rchild; 97 } else {// 98 //设置q节点的父节点左孩子为q节点右孩子 99 parent->lchild = q->rchild; 100 } 101 102 } 103 } 104 } 105 }
动画演示
9、获取目标节点后继节点(中序遍历)
1 /** 2 *在传入p的树中找出节点值为value的后继节点方法 3 */ 4 TNode<T>* TreeSuccessor(TNode<T> *p,const T &value,TNode<T> *returnValue){ 5 //如果t节点非空 6 if(p){ 7 //传入p树和节点值value找到对应的节点 8 TNode<T> *t = this->SearchTree(p, value); 9 //如果节点右子树不为空 10 if(t->rchild != 0){ 11 //直接获取右子树中最小节点,即是节点的后继节点 12 returnValue = this->GetMin(t->rchild); 13 }else{ 14 //获取目标节点父节点 15 TNode<T> *parent = this->GetParentNode(t->data,returnValue); 16 17 //如果父节点不为空并且t节点等于父节点的右节点,这一个文字不太好描述,请参照图 18 while(parent && t == parent->rchild){ 19 //父节点赋值给t节点 20 t = parent; 21 //获取父节点的父节点(目标节点爷爷) 22 parent = this->GetParentNode(parent->data,returnValue); 23 } 24 //找到后继节点赋值给返回变量 25 returnValue = parent; 26 } 27 } 28 29 return returnValue; 30 } 31 /** 32 *在以root为根节点中找出节点值为value的后继节点方法 33 */ 34 TNode<T>* TreeSuccessor(const T &value,TNode<T> *returnValue){ 35 return TreeSuccessor(root,value,returnValue); 36 }
如下图:
10、先序、中序、后序递归和非的递归遍历,更详细的请参考上一篇文章,为什么在这里有展示一遍,我坚信在复杂的东西,多动手写几次都能很好的理解
1 /** 2 *前序非递归(利用栈)遍历二叉树 3 *前序遍历的规则:根左右 4 *非递归遍历树会经常当做面试题,考察面试者的编程能力 5 *防止下次被鄙视,应该深入理解并且动手在纸写出来 6 */ 7 void PreOrderTraverse() { 8 //申明一个栈对象 9 stack<TNode<T>*> s; 10 //t首先指向根节点 11 TNode<T> *t = root; 12 //压入一个空指针,作为判断条件 13 s.push(0); 14 15 //如果t所值节点非空 16 while (t != 0) { 17 //直接访问根节点 18 std::cout << (&t->data) << " "; 19 20 //右孩子指针为非空 21 if (t->rchild != 0) { 22 //入栈右孩子指针 23 s.push(t->rchild); 24 } 25 26 //左孩子指针为非空 27 if (t->lchild != 0) { 28 //直接指向其左孩子 29 t = t->lchild; 30 } else {//左孩子指针为空 31 //获取栈顶元素(右孩子指针) 32 t = s.top(); 33 //清楚栈顶元素 34 s.pop(); 35 } 36 37 } 38 } 39 40 /** 41 *中序非递归(利用栈)遍历二叉树 42 *前序遍历的规则:左根右 43 */ 44 void InOrderTraverse() { 45 //申明一个栈对象 46 stack<TNode<T>*> s; 47 //t首先指向根节点 48 TNode<T>* t = root; 49 50 //节点不为空或者栈对象不为空,都进入循环 51 while (t != 0 || !s.empty()) { 52 //如果t节点非空 53 if (t) { 54 //入栈t节点 55 s.push(t); 56 //t节点指向其左孩子 57 t = t->lchild; 58 } else { 59 //获取栈顶元素(左孩子指针) 60 t = s.top(); 61 //清楚栈顶元素 62 s.pop(); 63 //直接访问t节点 64 std::cout << (&t->data) << " "; 65 //t节点指向其右孩子 66 t = t->rchild; 67 } 68 } 69 } 70 /** 71 *后序非递归(利用栈)遍历二叉树 72 *前序遍历的规则:左右根 73 */ 74 void PostOrderTraverse() { 75 //申明一个栈对象 76 stack<TNode<T>*> s; 77 //t首先指向根节点 78 TNode<T>* t = root; 79 //申请中间变量,用做判断标识 80 TNode<T>* r; 81 //节点不为空或者栈对象不为空,都进入循环 82 while (t != 0 || !s.empty()) { 83 //如果t节点非空 84 if (t) { 85 //入栈t节点 86 s.push(t); 87 //t节点指向其左孩子 88 t = t->lchild; 89 } else { 90 //获取栈顶元素(左孩子指针) 91 t = s.top(); 92 //判断t的右子树是否存在并且没有访问过 93 if (t->rchild && t->rchild != r) { 94 //t节点指向其右孩子 95 t = t->rchild; 96 //入栈t节点 97 s.push(t); 98 //t节点指向其左孩子 99 t = t->lchild; 100 } else { 101 //获取栈顶元素(左孩子指针) 102 t = s.top(); 103 //清楚栈顶元素 104 s.pop(); 105 //直接访问t节点 106 std::cout << (&t->data) << " "; 107 //设置已经访问过的节点,防止多次访问(右孩子指针) 108 r = t; 109 t = 0; 110 } 111 } 112 } 113 } 114 /** 115 * 根据模式递归遍历二叉树 116 * 下面代码相对比较简单,只要记住遍历树的规则并且弄一点递归,都可以写出来 117 * 如果在面试中实在写不出来非递归方式,可以写一个递归版本,也许可以争取一个好的工作 118 */ 119 void OrderTraverse(const TNode<T>* t, Style mode) { 120 if (t) { 121 //先序遍历二叉树:根左右 122 if (mode == Pre) { 123 //直接访问t节点 124 std::cout << (&t->data) << " "; 125 //递归遍历左子树 126 this->OrderTraverse(t->lchild, mode); 127 //递归遍历右子树 128 this->OrderTraverse(t->rchild, mode); 129 } 130 //中序遍历二叉树:左根右 131 if (mode == In) { 132 //递归遍历左子树 133 this->OrderTraverse(t->lchild, mode); 134 //直接访问t节点 135 std::cout << (&t->data) << " "; 136 //递归遍历右子树 137 this->OrderTraverse(t->rchild, mode); 138 } 139 //后序遍历二叉树:左右根 140 if (mode == Post) { 141 //递归遍历左子树 142 this->OrderTraverse(t->lchild, mode); 143 //递归遍历右子树 144 this->OrderTraverse(t->rchild, mode); 145 //直接访问t节点 146 std::cout << (&t->data) << " "; 147 } 148 } 149 }
11、按层级遍历树
1 /** 2 *按层级从左到右遍历树 3 */ 4 void LevelOrderTraverse(){ 5 //声明一个队列队形 6 queue<TNode<T>* > q; 7 //声明变量a,t;并把root赋值给t 8 TNode<T> *a,*t = root; 9 //若t节点非空 10 if(t){ 11 //t节点入队列 12 q.push(t); 13 //如果队列不为空 14 while(!q.empty()){ 15 //获取队列头结点 16 a = q.front(); 17 //数据队形出队列 18 q.pop(); 19 //直接访问队列头结点值 20 std::cout<<a->data<<" "; 21 //若a节点左子树不为空 22 if(a->lchild){ 23 //左子树入队列q 24 q.push(a->lchild); 25 } 26 //若a节点右子树不为空 27 if(a->rchild){ 28 //右子树入队列q 29 q.push(a->rchild); 30 } 31 } 32 } 33 }
12、运行结果,由于在虚拟机中打中文,实在太痛苦,弄一点英文装下B
测试代码如下:
1 void test() { 2 Insert(5); 3 Insert(3); 4 Insert(4); 5 Insert(6); 6 Insert(2); 7 Insert(1); 8 Insert(10); 9 Insert(9); 10 Insert(8); 11 Insert(11); 12 Insert(12); 13 std::cout << "create tree success" << std::endl; 14 15 std::cout << "create tree after is null ? "; 16 std::cout << boolalpha << this->IsEmpty(); 17 18 std::cout << std::endl; 19 std::cout << "calculated depth of the tree begins" << std::endl; 20 std::cout << "tree is dept = " << this->GetTreeDept(this->GetRoot()); 21 std::cout << std::endl; 22 std::cout << "calculated depth of the tree end" << std::endl; 23 24 std::cout << std::endl; 25 std::cout << "recursion--------------------begin" << std::endl; 26 std::cout << "pre order traverse:"; 27 OrderTraverse(GetRoot(), Pre); 28 std::cout << endl; 29 30 std::cout << "in order traverse:"; 31 OrderTraverse(GetRoot(), In); 32 std::cout << endl; 33 34 std::cout << "post order traverse:"; 35 OrderTraverse(GetRoot(), Post); 36 std::cout << endl; 37 std::cout << "recursion--------------------end" << std::endl; 38 39 std::cout << "get parent node--------------begin" << std::endl; 40 TNode<T> *returnValue; 41 TNode<T> *node = GetParentNode(5,returnValue); 42 if (node) { 43 std::cout << "node=" << node->data; 44 } 45 std::cout << "get parent node--------------end" << std::endl; 46 std::cout << "delete-----------------------begin" << std::endl; 47 Delete(5); 48 std::cout << "delete-----------------------end" << std::endl; 49 50 std::cout << "recursion--------------------begin" << std::endl; 51 std::cout << "in order traverse:"; 52 OrderTraverse(GetRoot(), In); 53 std::cout << endl; 54 std::cout << "recursion--------------------end" << std::endl; 55 std::cout<<"tree max:"<<GetMax(GetRoot())->data<<std::endl; 56 std::cout<<"tree min:"<<GetMin(GetRoot())->data<<std::endl; 57 }
结果如下图
11、完整代码
TNode.h
1 /* 2 * TLNode.h 3 * 4 * Created on: 2013-7-6 5 * Author: sunysen 6 */ 7 8 #ifndef TLNODE_H_ 9 #define TLNODE_H_ 10 template <class T> 11 class TNode{ 12 public: 13 T data; 14 TNode *rchild,*lchild; 15 TNode(T value):data(value),rchild(0),lchild(0){} 16 }; 17 18 #endif /* TLNODE_H_ */
BSTree.h
1 /* 2 * LinkTree.h 3 * Created on: 2013-7-6 4 * Author: sunysen 5 */ 6 7 #ifndef BSTREE_H_ 8 #define BSTREE_H_ 9 #include "core/common/Common.h" 10 #include "core/node/TNode.h" 11 //获取树最大最小值模式 12 enum Mode { 13 Max, Min 14 }; 15 //树的三种遍历方式 16 enum ORDER_MODE { 17 Pre, In, Post 18 }; 19 template<class T> 20 class BSTree { 21 private: 22 TNode<T> *root;//树的根节点 23 public: 24 /** 25 * 构造函数初始化树根节点 26 */ 27 BSTree() : 28 root(0) { 29 } 30 /** 31 *析构行数释放所有构造的资源 32 */ 33 ~BSTree(){ 34 Clear(); 35 } 36 /** 37 * 若树为空,则返回true;否则返回false 38 */ 39 bool IsEmpty() { 40 return root == 0; 41 } 42 43 /** 44 * 以传入节点为基础计算树的深度 45 */ 46 int GetTreeDept(const TNode<T> *t) { 47 int i, j; 48 if (t == 0) { 49 return 0; 50 } else { 51 //递归计算左子树的深度 52 i = this->GetTreeDept(t->lchild); 53 //递归计算右子树的深度 54 j = this->GetTreeDept(t->rchild); 55 } 56 57 //t的深度为其左右子树中深度中的大者加1 58 return i > j ? i + 1 : j + 1; 59 } 60 /** 61 *清空树的所有节点 62 */ 63 void Clear() { 64 Clear(root); 65 } 66 67 /** 68 *根据节点递归清空树 69 */ 70 void Clear(TNode<T>* t) { 71 //判断指针是否为空 72 if (t) { 73 //获取资源立即放入管理对象(参考Effective C++里边条款13) 74 //tr1::shared_ptr(引用计数智慧指针)管理对象比auto_ptr更强大 75 std::auto_ptr<TNode<T> > new_ptr(t); 76 77 //递归清空右子树 78 Clear(new_ptr->rchild); 79 80 //递归清空左子树 81 Clear(new_ptr->lchild); 82 } 83 84 //清空树的根节点 85 t = 0; 86 } 87 88 /** 89 *获取树的最大节点 90 */ 91 TNode<T>* GetMax(TNode<T>* t) const { 92 //判断数节点是否为空 93 if (t) { 94 //根据二叉树特性,最大值一定在右子树; 95 //循环右子树,直到叶子节点 96 while (t->rchild) { 97 //指向下一个节点 98 t = t->rchild; 99 } 100 } 101 //返回找到最大节点 102 return t; 103 } 104 /** 105 *获取树的最小节点 106 */ 107 TNode<T>* GetMin(TNode<T>* t) const { 108 //判断数节点是否为空 109 if (t) { 110 //根据二叉树特性,最大值一定在左子树; 111 //循环左子树,直到叶子节点 112 while (t->lchild) { 113 //指向下一个节点 114 t = t->lchild; 115 } 116 } 117 //返回找到最小节点 118 return t; 119 } 120 /** 121 *根据模式类型查找树的最大值或者最小值 122 */ 123 TNode<T>* GetNode(Mode mode) const { 124 //t指向根节点 125 TNode<T>* t = root; 126 //判断数节点是否为空 127 if (t) { 128 if (mode == Min) { 129 //根据二叉树特性,最大值一定在左子树; 130 //循环左子树,直到叶子节点 131 while (t->lchild) { 132 //指向左子树下一个节点 133 t = t->lchild; 134 } 135 } else if (mode == Max) { 136 //根据二叉树特性,最大值一定在右子树; 137 //循环右子树,直到叶子节点 138 while (t->rchild) { 139 //指向右子树下一个节点 140 t = t->rchild; 141 } 142 } 143 } 144 //返回找到节点 145 return t; 146 } 147 /** 148 *获取传入的节点从传入树p中找到它的父节点 149 */ 150 TNode<T>* GetParentNode(TNode<T> *p, const T &value,TNode<T> *returnValue) { 151 //p节点存在并且传入的值不是根节点值 152 if (p && p->data == value) { 153 return 0; 154 } 155 156 //用二分查找定位值value所在节点 157 TNode<T> *t = this->SearchTree(p, value); 158 159 //判断t和p都不为空 160 if (t && p) { 161 //如果value的节点等于p节点左孩子或者右孩子,p就是value的节点父亲 162 if (p->lchild == t || p->rchild == t) { 163 //赋值p节点给返回值变量 164 returnValue = p; 165 } else if (value > p->data) {//如果value只大于p节点值,则递归右孩子 166 //直到找到value的父节点复制给返回值变量 167 returnValue = GetParentNode(p->rchild, value,returnValue); 168 } else {////如果value只小于p节点值,则递归左孩子 169 //直到找到value的父节点复制给返回值变量 170 returnValue = GetParentNode(p->lchild, value,returnValue); 171 } 172 } 173 174 return returnValue; 175 176 } 177 178 /** 179 *获取传入的节点的父节点 180 */ 181 TNode<T>* GetParentNode(const T &value,TNode<T> *returnValue) { 182 return this->GetParentNode(root, value,returnValue); 183 } 184 185 /** 186 * 在以T为根节点的树中搜索值为value的节点 187 */ 188 TNode<T>* SearchTree(TNode<T>* &t, const T &value) { 189 //判断t节点是否为空 190 while (t) { 191 //如果节点值等于value,则表明已经找到目标节点 192 if (t->data == value) { 193 return t; 194 } else if (value > t->data) {//如果value大于t节点值,则递归查询右子树 195 return SearchTree(t->rchild, value); 196 } else {//如果value小于t节点值,则递归查询左子树 197 return SearchTree(t->lchild, value); 198 } 199 } 200 return t; 201 } 202 203 /** 204 *插入一个节点到目标树中 205 */ 206 void Insert(const T &value, TNode<T>* &t) { 207 //如果目标树为空,则新new一个根节点 208 if (t == 0) { 209 //新创建一个节点,并把value设置为节点值 210 t = new TNode<T>(value); 211 } else if (value < t->data) {//如果value值小于t节点值 212 //递归左子树插入函数,直到找到节点插入 213 this->Insert(value, t->lchild); 214 } else if (value > t->data) {//如果value值大于t节点值 215 //递归右子树插入函数,直到找到节点插入 216 this->Insert(value, t->rchild); 217 } 218 } 219 /** 220 *插入一个节点到根节点为root的树中 221 */ 222 void Insert(const T &value) { 223 this->Insert(value, root); 224 } 225 226 /** 227 *根据节点值删除节点信息 228 */ 229 void Delete(const T &value) { 230 Delete(value, root); 231 } 232 /** 233 *根据节点值删除以传入t为根节点树节点信息 234 */ 235 void Delete(const T &value, TNode<T>* &t) { 236 //判断是否t为空null 237 if (t) { 238 //通过二分查找定位value所在的节点 239 TNode<T> *p = this->SearchTree(t, value); 240 //中间变量,用于待删除节点左右子树都不为空的情况下 241 TNode<T> *q = p; 242 if (p) { 243 //如果p节点的左右孩子都不为空,则根据二叉树定义 244 //必须在子右子树中找到最新节点作为新节点 245 if (p->lchild != 0 && p->rchild != 0) { 246 //获取右子树中最小的节点 247 q = this->GetMin(p->rchild); 248 } 249 //获取资源立即放入管理对象(参考Effective C++里边条款13) 250 //tr1::shared_ptr(引用计数智慧指针)管理对象比auto_ptr更强大 251 //如果p节点的左右子树都不为空,则释放p节点子右子树的最小节点 252 //改变p节点的值,即可删除节点 253 auto_ptr<TNode<T> > new_ptr(q); 254 255 TNode<T> *parent = 0; 256 TNode<T> *returnValue; 257 //删除叶子节点(节点左右孩子都为空) 258 if (p->lchild == 0 && p->rchild == 0) { 259 //如果p节点和传入的根节点相等 260 if (t == p) { 261 //直接设置t为空 262 t = 0; 263 } else { 264 //获取p节点的父节点 265 parent = this->GetParentNode(t, p->data,returnValue); 266 267 //如果父节点的左孩子等于p节点 268 if (parent->lchild == p) { 269 //设置父节点的左孩子等于空 270 parent->lchild = 0; 271 } else {//如果父节点的右孩子等于p节点 272 //设置父节点的右孩子等于空 273 parent->rchild = 0; 274 } 275 } 276 277 } else if (p->rchild == 0) {//删除节点p右孩子为空,左孩子有节点 278 //如果p节点和传入的根节点相等 279 if (t == p) { 280 //直接设置t节点等于左孩子 281 t = t->lchild; 282 } else { 283 //获取p节点的父节点 284 parent = this->GetParentNode(t, p->data,returnValue); 285 //如果父节点的左孩子等于p节点 286 if (parent->lchild == p) { 287 //设置父节点左孩子等于p节点左孩子 288 parent->lchild = p->lchild; 289 } else {//如果父节点的右孩子等于p节点 290 //设置父节点右孩子等于p节点左孩子 291 parent->rchild = p->lchild; 292 } 293 } 294 295 } else if (p->lchild == 0) {//删除节点p左孩子为空,右孩子有节点 296 //如果p节点和传入的根节点相等 297 if (t == p) { 298 //直接设置t节点等于右孩子 299 t = t->rchild; 300 } else { 301 //获取p节点的父节点 302 parent = this->GetParentNode(t, p->data,returnValue); 303 //如果父节点的右孩子等于p节点 304 if (parent->rchild == p) { 305 //设置父节点右孩子等于p节点右孩子 306 parent->rchild = p->rchild; 307 } else {//如果父节点的左孩子等于p节点 308 //设置父节点右孩子等于p节点右孩子 309 parent->lchild = p->rchild; 310 } 311 } 312 } else {//删除节点p左右都有孩子 313 //获取q节点的父节点 314 parent = this->GetParentNode(t,q->data,returnValue); 315 //设置p节点值等于q节点值 316 p->data = q->data; 317 //如果q的父节点等于p 318 if (parent == p) { 319 //设置q节点的父节点右孩子为q节点右孩子 320 parent->rchild = q->rchild; 321 } else {// 322 //设置q节点的父节点左孩子为q节点右孩子 323 parent->lchild = q->rchild; 324 } 325 326 } 327 } 328 } 329 } 330 331 /** 332 *前序非递归(利用栈)遍历二叉树 333 *前序遍历的规则:根左右 334 *非递归遍历树会经常当做面试题,考察面试者的编程能力 335 *防止下次被鄙视,应该深入理解并且动手在纸写出来 336 */ 337 void PreOrderTraverse() { 338 //申明一个栈对象 339 stack<TNode<T>*> s; 340 //t首先指向根节点 341 TNode<T> *t = root; 342 //压入一个空指针,作为判断条件 343 s.push(0); 344 345 //如果t所值节点非空 346 while (t != 0) { 347 //直接访问根节点 348 std::cout << (&t->data) << " "; 349 350 //右孩子指针为非空 351 if (t->rchild != 0) { 352 //入栈右孩子指针 353 s.push(t->rchild); 354 } 355 356 //左孩子指针为非空 357 if (t->lchild != 0) { 358 //直接指向其左孩子 359 t = t->lchild; 360 } else {//左孩子指针为空 361 //获取栈顶元素(右孩子指针) 362 t = s.top(); 363 //清楚栈顶元素 364 s.pop(); 365 } 366 367 } 368 } 369 370 /** 371 *中序非递归(利用栈)遍历二叉树 372 *前序遍历的规则:左根右 373 */ 374 void InOrderTraverse() { 375 //申明一个栈对象 376 stack<TNode<T>*> s; 377 //t首先指向根节点 378 TNode<T>* t = root; 379 380 //节点不为空或者栈对象不为空,都进入循环 381 while (t != 0 || !s.empty()) { 382 //如果t节点非空 383 if (t) { 384 //入栈t节点 385 s.push(t); 386 //t节点指向其左孩子 387 t = t->lchild; 388 } else { 389 //获取栈顶元素(左孩子指针) 390 t = s.top(); 391 //清楚栈顶元素 392 s.pop(); 393 //直接访问t节点 394 std::cout << (&t->data) << " "; 395 //t节点指向其右孩子 396 t = t->rchild; 397 } 398 } 399 } 400 /** 401 *后序非递归(利用栈)遍历二叉树 402 *前序遍历的规则:左右根 403 */ 404 void PostOrderTraverse() { 405 //申明一个栈对象 406 stack<TNode<T>*> s; 407 //t首先指向根节点 408 TNode<T>* t = root; 409 //申请中间变量,用做判断标识 410 TNode<T>* r; 411 //节点不为空或者栈对象不为空,都进入循环 412 while (t != 0 || !s.empty()) { 413 //如果t节点非空 414 if (t) { 415 //入栈t节点 416 s.push(t); 417 //t节点指向其左孩子 418 t = t->lchild; 419 } else { 420 //获取栈顶元素(左孩子指针) 421 t = s.top(); 422 //判断t的右子树是否存在并且没有访问过 423 if (t->rchild && t->rchild != r) { 424 //t节点指向其右孩子 425 t = t->rchild; 426 //入栈t节点 427 s.push(t); 428 //t节点指向其左孩子 429 t = t->lchild; 430 } else { 431 //获取栈顶元素(左孩子指针) 432 t = s.top(); 433 //清楚栈顶元素 434 s.pop(); 435 //直接访问t节点 436 std::cout << (&t->data) << " "; 437 //设置已经访问过的节点,防止多次访问(右孩子指针) 438 r = t; 439 t = 0; 440 } 441 } 442 } 443 } 444 /** 445 * 根据模式递归遍历二叉树 446 * 下面代码相对比较简单,只要记住遍历树的规则并且弄一点递归,都可以写出来 447 * 如果在面试中实在写不出来非递归方式,可以写一个递归版本,也许可以争取一个好的工作 448 */ 449 void OrderTraverse(const TNode<T>* t, Style mode) { 450 if (t) { 451 //先序遍历二叉树:根左右 452 if (mode == Pre) { 453 //直接访问t节点 454 std::cout << (&t->data) << " "; 455 //递归遍历左子树 456 this->OrderTraverse(t->lchild, mode); 457 //递归遍历右子树 458 this->OrderTraverse(t->rchild, mode); 459 } 460 //中序遍历二叉树:左根右 461 if (mode == In) { 462 //递归遍历左子树 463 this->OrderTraverse(t->lchild, mode); 464 //直接访问t节点 465 std::cout << (&t->data) << " "; 466 //递归遍历右子树 467 this->OrderTraverse(t->rchild, mode); 468 } 469 //后序遍历二叉树:左右根 470 if (mode == Post) { 471 //递归遍历左子树 472 this->OrderTraverse(t->lchild, mode); 473 //递归遍历右子树 474 this->OrderTraverse(t->rchild, mode); 475 //直接访问t节点 476 std::cout << (&t->data) << " "; 477 } 478 } 479 } 480 481 482 TNode<T>* GetRoot() { 483 return root; 484 } 485 486 void test() { 487 Insert(5); 488 Insert(3); 489 Insert(4); 490 Insert(6); 491 Insert(2); 492 Insert(1); 493 Insert(10); 494 Insert(9); 495 Insert(8); 496 Insert(11); 497 Insert(12); 498 std::cout << "create tree success" << std::endl; 499 500 std::cout << "create tree after is null ? "; 501 std::cout << boolalpha << this->IsEmpty(); 502 503 std::cout << std::endl; 504 std::cout << "calculated depth of the tree begins" << std::endl; 505 std::cout << "tree is dept = " << this->GetTreeDept(this->GetRoot()); 506 std::cout << std::endl; 507 std::cout << "calculated depth of the tree end" << std::endl; 508 509 std::cout << std::endl; 510 std::cout << "recursion--------------------begin" << std::endl; 511 std::cout << "pre order traverse:"; 512 OrderTraverse(GetRoot(), Pre); 513 std::cout << endl; 514 515 std::cout << "in order traverse:"; 516 OrderTraverse(GetRoot(), In); 517 std::cout << endl; 518 519 std::cout << "post order traverse:"; 520 OrderTraverse(GetRoot(), Post); 521 std::cout << endl; 522 std::cout << "recursion--------------------end" << std::endl; 523 524 std::cout << "get parent node--------------begin" << std::endl; 525 TNode<T> *returnValue; 526 TNode<T> *node = GetParentNode(5,returnValue); 527 if (node) { 528 std::cout << "node=" << node->data; 529 } 530 std::cout << "get parent node--------------end" << std::endl; 531 std::cout << "delete-----------------------begin" << std::endl; 532 Delete(5); 533 std::cout << "delete-----------------------end" << std::endl; 534 535 std::cout << "recursion--------------------begin" << std::endl; 536 std::cout << "in order traverse:"; 537 OrderTraverse(GetRoot(), In); 538 std::cout << endl; 539 std::cout << "recursion--------------------end" << std::endl; 540 std::cout<<"tree max:"<<GetMax(GetRoot())->data<<std::endl; 541 std::cout<<"tree min:"<<GetMin(GetRoot())->data<<std::endl; 542 } 543 544 }; 545 546 #endif /* BSTREE_H_ */
五:环境
1、运行环境:Ubuntu 10.04 LTS+VMware8.0.4+gcc4.4.3;
2、开发工具:Eclipse+make
六:题记
1、上面的代码难免有bug,如果你发现代码写的有问题,请你帮忙指出,让我们一起进步,让代码变的更漂亮和更健壮;
2、我自己能手动写上面代码,离不开郝斌、高一凡、侯捷、严蔚敏等老师的书籍和视频指导,在这里感谢他们;
3、鼓励自己能坚持把更多数据结构方面的知识写出来,让自己掌握更深刻,也顺便冒充下"小牛";
4、所有动画都在网上找的,感谢制作做动画的朋友,这样好的动画比图片更便于大家理解复杂的内容;
欢迎继续阅读“启迪思维:数据结构和算法”系列