二叉树之二叉搜索树的基本操作实现

这篇文章用来回顾二叉搜索数的以下操作:

  • 遍历

    • 前序遍历
    • 中序遍历
    • 后序遍历
    • 层序遍历
  • 查找
    • 查找最大值
    • 查找最小值
    • 查找指定值
  • 获取指定属性
    • 获取总节点/叶节点数量
    • 获取二叉树的高度(根的高度为1)
  • 行为操作
    • 插入
    • 删除


二叉树的结构定义:

1 struct TreeNode{
2     TreeNode():data(),left(nullptr),right(nullptr){}
3     ELEMENT data;
4     SearchTree left;
5     SearchTree right;
6 };

这是一些typedef,一般传参的时候用SearchTree,声明变量的时候用Position,避免混之.

1 struct TreeNode;
2 typedef int ELEMENT;
3 typedef TreeNode *  Position;
4 typedef TreeNode *  SearchTree;

0.类中提供的API

 1 class CBinTree
 2 {
 3 public:
 4     CBinTree(void);
 5
 6     ~CBinTree(void);
 7
 8     // Return true when it‘s empty
 9     bool isEmpty() ;
10
11     // Insert a element.
12     size_t _insert_(ELEMENT &_data);
13
14     // Delete a element.
15     size_t _delete_(ELEMENT &_data);
16
17     // Traversal of preorder/inorder/postorder/sequence order.
18     void traversalPre();
19     void traversalIn();
20     void traversalPos();
21     void traversalSeq();
22
23     // Find something.
24     Position findMin();
25     Position findMax();
26     Position find(ELEMENT &_data);
27
28     // Get the number of node/leafnode.
29     void getNodeNum(int * _nodenum,int * _leafnodenum);
30
31     // Get the height of tree
32     int getTreeHeight();
33
34     // Show this tree
35     void showThisTree();
36
37 private:
38     // Record the size of nodes
39     int size;
40     // The root of binary tree
41     SearchTree stRoot;
42 private:
43     SearchTree insert_specific(ELEMENT &_data,SearchTree & _T);
44
45     SearchTree delete_specific(ELEMENT &_data,SearchTree &_T);
46
47     void traversalPre_specific(SearchTree _T);
48
49     void traversalIn_specific(SearchTree _T);
50
51     void traversalPos_specific(SearchTree _T);
52
53     void traversalSeq_specific(SearchTree _T);
54
55     Position findMin_specific(SearchTree _T);
56
57     Position findMax_specific(SearchTree _T);
58
59     Position find_specific(SearchTree _T,ELEMENT &_data);
60
61     int getTreeHeight_specific(SearchTree _T);
62
63     void getNodeNum_specific(SearchTree _T,int * _nodenum,int * _leafnodenum);
64
65     void showThisTree_specific(SearchTree _T);
66 };

  具体实现都在对应的*_specific函数中;

1.遍历

  因为二叉查找树的平均深度是O(logN),所以一般不用担心栈空间会被用尽.

  由于前/中/后序遍历只是把输出函数换了个位置,故这里只放出中序遍历的代码:

1 void CBinTree::traversalIn_specific(SearchTree _T){
2     if (_T)
3     {
4         traversalIn_specific(_T->left);
5         printf("%d ",_T->data);
6         traversalIn_specific(_T->right);
7     }
8 }

  层序遍历:

    利用了STL的队列.

 1 void CBinTree::traversalSeq_specific(SearchTree _T){
 2     if (_T)
 3     {
 4         // Remember the first node
 5         std::queue<Position> QNode;
 6         QNode.push(_T);
 7
 8         // Save the dequeued node
 9         Position popNode;
10
11         while (!QNode.empty())
12         {
13             // DeQueue
14             popNode = QNode.front();
15             QNode.pop();
16
17             // Output the first node of QNode
18             printf("%d ",popNode->data);
19
20             // EnQueue
21             if (popNode->left)
22             {
23                 QNode.push(popNode->left);
24             }
25             if (popNode->right)
26             {
27                 QNode.push(popNode->right);
28             }
29         }
30     }
31 }

2.查找

  这里我是用循环实现的,如此类似于链表的操作,简单易懂.

  基于搜索二叉树的定义出发,代码如下:

 1 Position CBinTree::findMin_specific(SearchTree _T){
 2     Position minNodePos = _T;
 3     while (minNodePos->left)
 4     {
 5         minNodePos = minNodePos->left;
 6     }
 7     return minNodePos;
 8 }
 9
10 Position CBinTree::findMax_specific(SearchTree _T){
11     Position minNodePos = _T;
12     while (minNodePos->right)
13     {
14         minNodePos = minNodePos->right;
15     }
16     return minNodePos;
17 }
18
19 Position CBinTree::find_specific(SearchTree _T,ELEMENT &_data){
20     Position foundNode = _T;
21     while (foundNode)
22     {
23         if (_data > foundNode->data)
24         {
25             foundNode = foundNode->right;
26         }
27         else if (_data < foundNode->data)
28         {
29             foundNode = foundNode->left;
30         }
31         else
32         {
33             return foundNode;
34         }
35     }
36     return nullptr;
37 }

3.获取指定属性:

  •  获取总结点和叶子节点的数量

    利用层序遍历的方式可以直接解决这两个问题.

    _nodenum是总结点的数量,_leafnodenum是叶节点的数量.调用者需要传递其指针以便计算.

 1 void CBinTree::getNodeNum_specific(SearchTree _T,int * _nodenum,int * _leafnodenum){
 2     Position T = _T;
 3     *_nodenum = 0;
 4     *_leafnodenum = 0;
 5     if (T)
 6     {
 7         // Remember the first node
 8         std::queue<Position> QNode;
 9         QNode.push(T);
10         (*_nodenum) ++ ;
11         // Save the dequeued node
12         Position popNode;
13
14         while (!QNode.empty())
15         {
16             // DeQueue
17             popNode = QNode.front();
18             QNode.pop();
19
20             // Output the first node of QNode
21             // printf("%d\n",popNode->data);
22
23             // EnQueue
24             if (popNode->left)
25             {
26                 QNode.push(popNode->left);
27                 (*_nodenum) ++ ;
28             }
29             if (popNode->right)
30             {
31                 QNode.push(popNode->right);
32                 (*_nodenum) ++ ;
33             }
34
35             // To determine whether the leafnode
36             if (!popNode->left && !popNode->right)
37             {
38                 (*_leafnodenum) ++;
39             }
40         }
41     }
42 }
  • 获取二叉树的高度(根的高度为1)

这里的递归很奇妙:),递归都很有魔法不是么?

思路是递归计算每条分支的高度,相比较取最大的那个,听起来很简单.具体的实现方式居然是从叶子节点开始返回1,然后递归向上不断的加1,这一点很有趣.

这也是在最后"+1"的原因(魔法).

可以将这八行代码缩短为三行.但我认为这样阅读起来很舒服.

 1 int CBinTree::getTreeHeight_specific(SearchTree _T){
 2     if (_T)
 3     {
 4         size_t
 5             lh = getTreeHeight_specific(_T->left),
 6             rh = getTreeHeight_specific(_T->right);
 7         return (lh > rh)?lh+1:rh+1;
 8     }
 9     return 0;
10 }

 4.行为操作:

  • 插入

  插入操作是利用二叉树天生的递归特性一个典例.

  我以前一直不太理解递归如何保持新创建节点与其父节点的关联性.

  后来发现漂亮的地方在与利用递归的返回值,与即将开始递归前的等待被赋值("_T->left ="),正是此赋值语句保持了节点之间的关系.

  但是这也是一个问题,因为对于已经确定关系的节点而言岂不是要多次赋值(建立连接),即新节点加入之后,以上的节点还要继续赋值以再建立已经存在的连接.

  而使用循环的话解可以避免这样的问题,只需新建立一个节点,与其父节点连接就大功告成,但是我试过,代码写起来没有递归的好看 :)

  太漂亮了,献上代码敬之.

 1 SearchTree CBinTree::insert_specific(ELEMENT &_data,SearchTree & _T){
 2     if (_T == nullptr)
 3     {
 4         // Create a new node
 5         _T = new TreeNode;
 6         _T->data = _data;
 7         size++;
 8         // Return to the father node ,tell him this node be created already.
 9         // return T;
10     }
11     else if (_data < _T->data)
12     {
13         _T->left = insert_specific(_data,_T->left);
14     }
15     else if (_data > _T->data)
16     {
17         _T->right = insert_specific(_data,_T->right);
18     }
19     // Back to the above node,remain their linear relation.
20     return _T;
21 }
  • 删除  

  花点时间总结删除操作.我确实花了点时间理解 :(

  这里的递归也是太漂亮了.

  删除节点的情况可以分为三种,即节点间的连接方式种类.

    1.   有2个子节点
    2.   有1个子节点
    3.   有0个子节点

  最困难的莫过于删除第一种情况的节点了.在此之前复习一下后两种的删除方法:
  对于2.当前节点直接被非空的子节点替换即可,注意释放原先节点.

  对于3.删除就好,在利用魔法的递归,将此节点(已经被置为nullptr)返回给他的父节点.

  好了那么对于1.的解决办法如下:

  将被删除的节点与右子树中最小值或者左子树中最大值替换(从比它小的中找个最大的,比它大的中找个最小的).

  可行的理由是:对于一棵二叉树来说,最大值或最小值所在的节点的子节点数量是一个或两个.这不就转换为了2./3.种情况了嘛.

  再调用处理2./3.的函数即可.

  [8-15]行很像insert的查找过程.

  [22-24]行是替换当前节点的值,再把那个用来替换的节点删除.

  [30-40]行是在处理2./3.种情况,保存当前节点.在被替换后,释放保存节点,此处是任何删除操作的必经之处.很漂亮的处理.

 1 SearchTree CBinTree::delete_specific(ELEMENT &_data,SearchTree &_T){
 2
 3     if (_T)
 4     {
 5         Position tmp;
 6
 7         // Search node to delete
 8         if (_data < _T->data)
 9         {
10             _T->left = delete_specific(_data,_T->left);
11         }
12         else if (_data > _T->data)
13         {
14             _T->right = delete_specific(_data,_T->right);
15         }
16         // Search Done!
17         // Two chidren.
18         else if (_T->left && _T->right)
19         {
20             // Replace with smallest in right subtree.
21             // Or tmp = findMin_specific(_T->left);
22             tmp = findMin_specific(_T->right);
23             _T->data = tmp->data;
24             _T->right = delete_specific(tmp->data,_T->right);
25         }
26         // One or zero chidren.
27         else
28         {
29             tmp = _T;
30             if (!_T->left)
31             {
32                 _T = _T->right;
33             }
34             else if (!_T->right)
35             {
36                 _T = _T->left;
37             }
38             size--;
39             delete tmp;
40             tmp = nullptr;
41         }
42     }
43     return _T;
44 }

5.showThisTree()

  正如名字那样,他的功能是把二叉树显示出来.

  像这样:

  

  虽然没有连线,但是看看二叉树长什么样子也挺有趣的.

  代码敬上,主要用于后面AVL树的检验,见笑了.

  

  1 void CBinTree::showThisTree_specific(SearchTree _T){
  2     std::ofstream treeShow("treeShow.txt",std::ios::out);
  3     if (!treeShow.is_open())
  4     {
  5         return ;
  6     }
  7
  8     if (_T)
  9     {
 10         int treeHeight = getTreeHeight();
 11         int showRootBlank = (int)pow(2,treeHeight);
 12
 13         // Remember the first node
 14         std::queue<Position> QNode;
 15         QNode.push(_T);
 16
 17         int
 18             // 当前层显示出节点的数量
 19             levelShowSize    = 0,
 20             // 所有显示节点的数量
 21             totalShowSize    = 0,
 22             // 当前层数
 23             levelSize        = 0;
 24
 25         // Save the dequeued node.
 26         Position popNode;
 27
 28         // Size is the num of nodes.
 29         while (totalShowSize != size)
 30         {
 31             // DeQueue
 32             popNode = QNode.front();
 33             QNode.pop();
 34
 35             // 节点已经输出,对输出的节点进行计数
 36             levelShowSize ++;
 37
 38             // 对有效节点的进行计数,用于循环退出
 39             if (popNode->data != 666666)
 40             {
 41                 totalShowSize ++;
 42             }
 43
 44             // 判断空格的输出数量 第一次输出需要/2,后面的不需要,具体请画图会意.
 45             int blankSize = (levelShowSize == 1)?showRootBlank/2:showRootBlank;
 46             for (int i = 0;i < blankSize-1;i++)
 47             {
 48                 printf(" ");
 49                 treeShow<<" ";
 50             }
 51
 52             // 显示节点值
 53             // 显示一个假节点
 54             if (popNode->data == 666666)
 55             {
 56                 printf(" ");
 57                 treeShow<<" ";
 58             }
 59             // 显示一个真节点
 60             else
 61             {
 62                 printf("%d",popNode->data);
 63                 treeShow<<popNode->data;
 64             }
 65
 66             // 判断这层是否已经完结
 67             if (levelShowSize == pow(2,levelSize))
 68             {
 69                 levelSize ++;
 70                 levelShowSize = 0;
 71                 showRootBlank = showRootBlank / 2 ;
 72                 printf("\n");
 73                 treeShow<<"\n";
 74             }
 75
 76             // EnQueue operation
 77             // 假节点 or 叶子节点
 78             if ((!popNode->left) && (!popNode->right))
 79             {
 80                 Position fakeNode = new TreeNode;
 81                 fakeNode->data = 666666;
 82                 QNode.push(fakeNode);
 83                 QNode.push(fakeNode);
 84             }
 85             // 只含有左节点
 86             else if (popNode->left && !popNode->right)
 87             {
 88                 QNode.push(popNode->left);
 89                 // As a right node push to qeueu
 90                 Position fakeNode = new TreeNode;
 91                 fakeNode->data = 666666;
 92                 QNode.push(fakeNode);
 93             }
 94             // 只含有右节点
 95             else if (popNode->right && !popNode->left)
 96             {
 97                 // As a left node push to qeueu
 98                 Position fakeNode = new TreeNode;
 99                 fakeNode->data = 666666;
100                 QNode.push(fakeNode);
101                 // Can‘t swap.
102                 QNode.push(popNode->right);
103             }
104             // 含有左右节点
105             else if (popNode->left && popNode->right)
106             {
107                 QNode.push(popNode->left);
108                 QNode.push(popNode->right);
109             }
110         }
111     }
112     printf("\nwrite done!!\n");
113     treeShow.close();
114 }

二叉树的显示

总结:

  发现二叉树是一个递归的世界,这点从树的结构上就可以看出来.

  其相关的算法用来学习递归的好工具,若自己去想真的得花点功夫.

  递归很漂亮.

完整代码见我的github

时间: 2024-08-28 08:33:19

二叉树之二叉搜索树的基本操作实现的相关文章

二叉树、二叉搜索树、AVL树的java实现

数据结构一直都是断断续续的看,总是觉得理解的不够深入,特别是对树的理解,一直都很浅显,今儿又看了一遍,来做个总结吧. 首先,树中的一些概念: 1.树的节点包含一个数据元素,以及若干指向其子树的分支.节点拥有的子树的数量称为节点的度.节点的最大层次称为树的深度或高度. 2.二叉树是一种树形结构,其特点是每个节点至多有两棵子树,且子树有左右之分,次序不能随意颠倒. 3.满二叉树:一棵深度为k且有2^k - 1个节点的二叉树,称之为满二叉树. 4.完全二叉树:对一个深度为k,节点个数为n的二叉树,当且

数据结构学习笔记04树(二叉树、二叉搜索树、平衡二叉树)

一.树 树的基本术语 ①结点的度(Degree):结点的子树个数 ②树的度:树的所有结点中最大的度数 ③叶结点(Leaf):度为0的结点 ④父结点(Parent):有子树的结点是其子树的根结点的父结点 ⑤子结点(Child):若A结点是B结点的父结点,则称B结点是A结点的子结点:子结点也称孩子结点. ⑥兄弟结点(Sibling):具有同一父结点的各结点彼此是兄弟结点. ⑦路径和路径长度:从结点n1到nk的路径为一个结点序列n1 , n2 ,… , nk , ni是 ni+1的父结点.路径所包含边

二叉树之二叉搜索树(BSTree)

二叉搜索树(Binary Search Tree) 二叉搜索树是一种二叉树,子结点数量上限为2:为了方便搜索,结点是有序的,左结点的key不大于父节点,右节点的key不小于父节点,且左右子树也是二叉搜索树. 下面是一个二叉搜索树的样图,来自维基 一棵树一般都要提供一些函数,例如遍历.搜索.最大最小值.插入,删除,销毁等 代码含注释,下面是输出效果(msys2) 代码 开发环境:Qt Creator 4.8.2 Mingw64 7.3 windows 8.1 完整代码:https://github

【二叉树】二叉搜索树

二叉搜索树: 1.每个节点都有一个关键码(key)作为搜索依据,关键码互不相同. 2.左子树的所有关键码都小于根节点的关键码. 3.右子树的所有关键码都大于根节点的关键码. 4.左右子树都是二叉搜索树. 删除key:左为空,右为空,左右都不空 1)左为空:cur的右树链到父节点 2)右为空:cur的左树链到父节点 3)左右都不空:找右树最左节点或左树最右节点,将找到的节点与cur交换后删除它. 二叉搜索树的增.删.查(非递归及递归)程序代码如下: #pragma once #include<st

【转载】Morris遍历二叉树 &amp; BST(二叉搜索树) Traverse &amp; 空间O(1) 时间O(n)

因为做一道Leetcode的题目(前面博客有:link),需要用Space O(1)空间复杂度来中序遍历树, 看了Discuss,也上网搜了一下,发现空间O(1)可以用 Morris遍历的方法.方法介绍如下: http://www.cnblogs.com/AnnieKim/archive/2013/06/15/MorrisTraversal.html其中,中序遍历和前序遍历比较方便解决: 通常,实现二叉树的前序(preorder).中序(inorder).后序(postorder)遍历有两个常用

B-Tree 漫谈 (从二叉树到二叉搜索树到平衡树到红黑树到B树到B+树到B*树)

关于B树的学习还是需要做点笔记. B树是为磁盘或者其他直接存取辅助存储设备而设计的一种平衡查找树.B树与红黑树的不同在于,B树可以有很多子女,从几个到几千个.比如一个分支因子为1001,高度为2的B树,他可以存储超过10亿个关键字,尽管如此,因为根节点(只有一个)保留在主存中,故这可书中,寻找某一个关键字之多需要两次磁盘存取. 关于磁盘的结构,以及写入,读取数据的原理,这里就略过了. 一.概述: 1) 对于B树的每个节点x有: a)n[x],当前存储在结点x中的关键字数, b)关键字以非降序存放

二叉树系列 - 二叉搜索树 - 线性时间内把有序链表转化为BST

引言 本文来自于Google的一道题目: how to merge two binary search tree into balanced binary search tree. how to merge two binary search tree into balanced binary search tree.. Let there be m elements in first tree and n elements in the other tree. Your merge funct

树, 二叉树, 二叉搜索树

转载:Vamei   出处:http://www.cnblogs.com/vamei 树的特征和定义 树(Tree)是元素的集合.我们先以比较直观的方式介绍树.下面的数据结构是一个树: 树有多个节点(node),用以储存元素.某些节点之间存在一定的关系,用连线表示,连线称为边(edge).边的上端节点称为父节点,下端称为子节点.树像是一个不断分叉的树根. 每个节点可以有多个子节点(children),而该节点是相应子节点的父节点(parent).比如说,3,5是6的子节点,6是3,5的父节点:1

纸上谈兵: 树, 二叉树, 二叉搜索树

树的特征和定义 树(Tree)是元素的集合.我们先以比较直观的方式介绍树.下面的数据结构是一个树: 树有多个节点(node),用以储存元素.某些节点之间存在一定的关系,用连线表示,连线称为边(edge).边的上端节点称为父节点,下端称为子节点.树像是一个不断分叉的树根. 每个节点可以有多个子节点(children),而该节点是相应子节点的父节点(parent).比如说,3,5是6的子节点,6是3,5的父节点:1,8,7是3的子节点, 3是1,8,7的父节点.树有一个没有父节点的节点,称为根节点(