zoukankan      html  css  js  c++  java
  • 二叉树介绍

    二叉树可以用来做什么?

    答:可以搜索、排序

    可是,排序有快速排序、归并排序,查找有二分法、直接遍历等,那么为什么要用二叉树呢?

    二叉树确实在实际运用中比较少,因为有更高级的树,但是二叉树作为一种最基本最典型的排序树,是研究其他树的基础。

    我们知道,在有序数组中,可以快速找到特定的值;但是在有序数组内插入一个新数据项,或者删除数据项,需要费时的移动所有位置改变的数据项,所以在做插入和删除操作时,不该选用有序数组。

    另一方面,链表中可以快速添加和删除某个数据项,但是在链表中查找数据不容易,必须从头开始访问链表的每一个数据项,直到找到该数据为止,过程很慢。

    树这种数据结构,技能像链表那样快速插入和删除,又能像有序数组那样快速查找。这里主要实现一种特殊的树--二叉(搜索)树。

    二叉(搜索)树有如下特点:

    1. 一个子节点的关键字值小于这个节点,右子节点的关键字值大于或者等于这个节点。

    2. 插入一个节点需要根据这个规则进行插入。

    3. 删除节点时,二叉搜索树最复杂的操作,但是删除节点在很多树中的应用又非常重要,所以详细研究并总结如下特点。

      1)删除节点要从查找要删的节点开始入手,首先找到节点,这个要删除的节点可能有三种情况需要考虑:

      a. 该节点是叶节点,没有子节点;

      b. 该节点有一个子节点;

      c. 该节点有2个子节点;

     第一种最简单,第二种也比较简单,第三种就相当复杂了。下面分析这三种删除情况:

    一、要删除叶节点,只需要改变该节点的父节点对应子字段的值即可,由指向该节点改为 null 就可以了。垃圾回收器会自动回收叶节点,不需要自己手动删掉。

    二、当节点有一个子节点时,这个节点只有两个连接:连向父节点和连向它唯一的子节点。需要从这个序列中剪断这个节点,把它的子节点直接连到它的父节点上即可,这个过程要求改变父节点适当的引用(左子节点还是右子节点),指向要删除节点的子节点即可。

    三、这种情况最复杂,如果要删除有两个子节点的节点,就不能只用它的一个子节点代替它,比如要删除节点25,如果用35取代它,那35的左子节点是15呢还是30?

                                                                                                           

    因此需要考虑另一种方法,寻找它的中序后继来代替该节点。下图显示的就是要删除节点用它的后继代替它的情况,删除后还是有序的。(这里还有更麻烦的情况,即它的后继自己也有右子节点,下面再讨论。)

                                                                                                           

    那么如何找后继节点呢?首先得找到要删除的节点的右子节点,它的关键字值一定比待删除节点的大。然后转到待删除节点右子节点的左子节点那里(如果有的话),然后到这个左子节点的左子节点,以此类推,顺着左子节点的路径一直向下找,这个路径上的最后一个左子节点就是待删除节点的后继。如果待删除节点的右子节点没有左子节点,那么这个右子节点本身就是后继。寻找后继的示意图如下:

                                                                                                   

     找到了后继节点,现在开始删除了,先看第一种情况,后继节点是delNode右子节点的做后代,这种情况要执行以下四个步骤:

    • 把后继父节点的leftChild字段置为后继的右子节点;

    • 把后继的rightChild字段置为要删除节点的右子节点;

    • 把待删除节点从它父节点的leftChild或rightChild字段删除,把这个字段置为后继;

    • 把待删除的左子节点移除,将后继的leftChild字段置为待删除节点的左子节点。

    如下图所示:

                                                                                             

    如果后继节点就是待删除节点的右子节点,这种情况就简单了,因为只需要把后继为跟的子树移到删除的节点的位置即可。如下图所示:

                                                                                             

    看到这里,就会发现删除时相当棘手的操作。实际上,因为它非常复杂,一些程序员都尝试着躲开它,他们在Node类中加了一个Boolean字段来标识该节点是否已经被删除,在其他操作之前会先判断这个节点是不是已经删除了,这样删除节点不会改变树的结构。当然树中还保留着这种已经删除的节点,对存储造成浪费,但是如果没有那么多删除的话,这也不失为一个好方法。

    另外二叉树有三种遍历方式:前序、中序和后序。这个比较简单,直接看下代码即可。

    下面手写个二叉搜索树的代码

      1  public class BinaryTree {
      2       private BNode root;   //根节点
      3      public BinaryTree() {
      4         root = null;  
      5     }    
      6    
      7     //二叉搜索树查找的时间复杂度为O(logN)
      8     public BNode find(int key) { //find node with given key
      9       BNode current = root;
     10       while(current.key != key) {
     11         if (key < current.key) {
     12              current = current.leftChild;    
     13          }else {
     14              current = current.rightChild;
     15          }
     16          if(current == null) {
     17              return null;
     18          }    
     19      }
     20      return current; 
     21    }
     22 
     23    // 插入节点
     24    public void insert(int key, double value) {
     25        BNode newNode = new BNode();
     26        newNode.key = key;
     27        newNode.data = value;
     28        if(root == null) { //if tree is null
     29             root = newNode;
     30         }
     31         else {
     32             BNode current = root;
     33             BNode parent;
     34             while(true) {
     35                 parent = current;
     36                 if(key < current.data) { //turn left
     37                     current = current.leftChild;
     38                     if(current == null) {
     39                         parent.leftChild = newNode;
     40                         newNode.parent = parent;
     41                         return;
     42                     }
     43                 }else { //turn right
     44                     current = current.rightChild;
     45                     if(current == null) {
     46                         parent.rightChild = newNode;
     47                         newNode.parent = parent;
     48                         return;
     49                     }
     50                 }
     51             }
     52         }
     53     }
     54  
     55   //遍历二叉树    
     56   public void traverse(int traverseType) {
     57      switch (traverseType){
     58         case 1: System.out.println("Preorder traversal:");
     59                 preOrder(root);//前向遍历
     60                 break;
     61         case 2: System.out.println("Inorder traversal:");
     62                 inOrder(root);//中向遍历
     63                 break;
     64         case 3: System.out.println("Postorder traversal:");
     65                 postOrder(root);//后向遍历
     66                 break;
     67         default: System.out.println("Inorder traversal:");
     68                 inOrder(root);
     69                 break;
     70         }
     71         System.out.println("");
     72     }
     73 
     74     
     75   //前向遍历
     76   private void preOrder(BNode localRoot) {
     77       if(localRoot != null) {
     78            System.out.print(localRoot.data + " ");
     79            preOrder(localRoot.leftChild);
     80            preOrder(localRoot.rightChild);
     81       }
     82   }
     83    
     84   //中向遍历
     85   private void inOrder(BNode localRoot) {
     86      if(localRoot != null) {
     87          inOrder(localRoot.leftChild);
     88          System.out.print(localRoot.data + " ");
     89          inOrder(localRoot.rightChild);
     90      }
     91   }
     92 
     93     
     94   //后向遍历
     95   private void postOrder(BNode localRoot) {
     96      if(localRoot != null) {
     97           postOrder(localRoot.leftChild);
     98           postOrder(localRoot.rightChild);
     99           System.out.print(localRoot.data + " ");
    100       }
    101   }
    102 
    103   //查找最小值
    104     
    105   /*根据二叉搜索树的存储规则,最小值应该是左边那个没有子节点的那个节点*/
    106     
    107   public BNode minNumber() {
    108      BNode current = root;
    109      BNode parent = root;
    110      while(current != null) {
    111           parent = current;
    112           current = current.leftChild;
    113       }   
    114       return parent;
    115   }
    116    
    117   //查找最大值
    118     
    119   /*根据二叉搜索树的存储规则,最大值应该是右边那个没有子节点的那个节点*/
    120     
    121   public BNode maxNumber() {
    122         BNode current = root;
    123         BNode parent = root;
    124         while(current != null) {
    125             parent = current;
    126             current = current.rightChild;
    127         }   
    128         return parent;
    129     }
    130 
    131     
    132   //删除节点
    133     
    134   /*
    135      * 删除节点在二叉树中是最复杂的,主要有三种情况:
    136      * 1. 该节点没有子节点(简单)
    137      * 2. 该节点有一个子节点(还行)
    138      * 3. 该节点有两个子节点(复杂)
    139      * 删除节点的时间复杂度为O(logN)
    140      */
    141     
    142   public boolean delete(int key) {
    143         BNode current = root;
    144 //        BNode parent = root;
    145         boolean isLeftChild = true;
    146      if(current == null) {
    147            return false;
    148         }
    149         //寻找要删除的节点
    150         while(current.key != key) {
    151 //            parent = current;
    152             if(key < current.key) {
    153                 isLeftChild = true;
    154                 current = current.leftChild;
    155             }
    156             else{
    157                 isLeftChild = false;
    158                 current = current.rightChild;
    159             }
    160             if(current == null) {
    161                 return false;
    162             }
    163         }
    164         //找到了要删除的节点,下面开始删除
    165         //1. 要删除的节点没有子节点,直接将其父节点的左子节点或者右子节点赋为null即可
    166         if(current.leftChild == null && current.rightChild == null) {
    167             return deleteNoChild(current, isLeftChild);
    168         }
    169      //3. 要删除的节点有两个子节点
    170         else if(current.leftChild != null && current.rightChild != null) {
    171             return deleteTwoChild(current, isLeftChild);
    172         }
    173 
    174         //2. 要删除的节点有一个子节点,直接将其砍断,将其子节点与其父节点连起来即可,要考虑特殊情况就是删除根节点,因为根节点没有父节点
    175         else{
    176             return deleteOneChild(current, isLeftChild);
    177         }
    178 
    179     }
    180 
    181     
    182   public boolean deleteNoChild(BNode node, boolean isLeftChild) {
    183         if(node == root) {
    184            root = null;
    185             return true;
    186         }
    187         if(isLeftChild) {
    188             node.parent.leftChild = null;
    189         }
    190         else{
    191             node.parent.rightChild = null;
    192         }
    193         return true;
    194     }
    195 
    196     
    197   public boolean deleteOneChild(BNode node, boolean isLeftChild) {
    198         if(node.leftChild == null) {
    199             if(node == root) {
    200                 root = node.rightChild;
    201                 node.parent = null;
    202                 return true;
    203             }
    204             if(isLeftChild) {
    205                 node.parent.leftChild  = node.rightChild;
    206             }
    207             else{
    208                 node.parent.rightChild = node.rightChild;
    209             }
    210             node.rightChild.parent = node.parent;
    211         }
    212         else{
    213             if(node == root) {
    214                 root = node.leftChild;
    215                 node.parent = null;
    216                 return true;
    217             }
    218             if(isLeftChild) {
    219                 node.parent.leftChild  = node.leftChild;
    220             }
    221             else{
    222                 node.parent.rightChild = node.leftChild;
    223             }
    224             node.leftChild.parent = node.parent;
    225         }
    226         return true;
    227     }
    228 
    229     
    230   public boolean deleteTwoChild(BNode node, boolean isLeftChild) {
    231         BNode successor = getSuccessor(node);
    232         if(node == root) {
    233             successor.leftChild = root.leftChild;
    234             successor.rightChild = root.rightChild;
    235             successor.parent = null;
    236             root = successor;
    237         }
    238         else if(isLeftChild) {
    239             node.parent.leftChild = successor;
    240         }
    241         else{
    242             node.parent.rightChild = successor;
    243         }
    244         successor.leftChild = node.leftChild;//connect successor to node's left child
    245         return true;
    246     }
    247 
    248     //获得要删除节点的后继节点(中序遍历的下一个节点)
    249     public BNode getSuccessor(BNode delNode) {
    250         BNode successor = delNode;
    251         BNode current = delNode.rightChild;
    252         while(current != null) {
    253             successor = current;
    254             current = current.leftChild;
    255         }
    256         if(successor != delNode.rightChild) {
    257             successor.parent.leftChild = successor.rightChild;
    258             if(successor.rightChild != null) {      
    259                 successor.rightChild.parent = successor.parent;//删除后续节点在原来的位置
    260             }
    261             successor.rightChild = delNode.rightChild;//将后续节点放到正确位置,与右边连上
    262         }
    263         return successor;
    264     }
    265 }   
    266 
    267 class BNode {
    268     public int key;
    269     public double data;
    270     public BNode parent;
    271     public BNode leftChild;
    272     public BNode rightChild;
    273 
    274     public void displayNode() {
    275         System.out.println("{" + key + ":" + data + "}");
    276     }
    277 }         
  • 相关阅读:
    IE 插件 Trixie 介绍
    .net开发沉淀之:HttpHandler(一)
    在Eclipse中导入已经存在的jar包
    浏览器的GreaseMonkey和Trixie插件
    为什么export>runnable jar file的launch configuration没有东西可以选择?
    SQL2008:如何解决“阻止保存要求重新创建表的更改”
    在IIS6下部署MVC2.0的注意事项
    ASP.NET 4.0中使用FreeTextBox和FCKeditor遇到安全问题警告的解决办法
    MyEclipse 设置JDK指向目录错误信息
    RHEL5.5 安装 oracle 11g
  • 原文地址:https://www.cnblogs.com/starrk-01/p/10043336.html
Copyright © 2011-2022 走看看