zoukankan      html  css  js  c++  java
  • 二叉树之二叉搜索树的基本操作实现

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

    • 遍历
      • 前序遍历
      • 中序遍历
      • 后序遍历
      • 层序遍历
    • 查找
      • 查找最大值
      • 查找最小值
      • 查找指定值
    • 获取指定属性
      • 获取总节点/叶节点数量
      • 获取二叉树的高度(根的高度为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
    ",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("
    ");
     73                 treeShow<<"
    ";
     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("
    write done!!
    ");
    113     treeShow.close();
    114 }
    二叉树的显示

    总结:

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

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

      递归很漂亮.

    完整代码见我的github

  • 相关阅读:
    java获取Mp3播放时长
    angular ajax的使用及controller与service分层
    mysql数据库不能远程访问的问题
    linux安装ant
    jquery中,使用append增加元素时,该元素的绑定监听事件失效
    类变量与实例变量
    ajax两张传输数据方式
    jquery的.html(),.text()和.val()方法
    如何在sublime text上快速访问html页面?
    java学习笔记之线程2wait和notifyAll
  • 原文地址:https://www.cnblogs.com/leihui/p/5995203.html
Copyright © 2011-2022 走看看