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

Posted 李雷还是要学英语

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二叉树之二叉搜索树的基本操作实现相关的知识,希望对你有一定的参考价值。

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

  • 遍历
    • 前序遍历
    • 中序遍历
    • 后序遍历
    • 层序遍历
  • 查找
    • 查找最大值
    • 查找最小值
    • 查找指定值
  • 获取指定属性
    • 获取总节点/叶节点数量
    • 获取二叉树的高度(根的高度为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 ="),正是此赋值语句保持了节点之间的关系.

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

  而使用循环的话解可以避免这样的问题,只需找到该节点建立的位置去新建立一个节点,再与其父节点连接就大功告成,但是我写了许多这样的if-else,没有递归的好看 :)

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

 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

 

以上是关于二叉树之二叉搜索树的基本操作实现的主要内容,如果未能解决你的问题,请参考以下文章

数据结构 - 从二叉搜索树说到AVL树之二叉搜索树的操作与详解(Java)

树与二叉树之二--二叉树的性质与存储

搜索二叉树之字典实现

数据结构(12)---二叉树之顺序结构

C++从青铜到王者第十九篇:C++二叉树进化之二叉搜索树

数据结构树之平衡二叉树