• 二叉查找树的递归实现及递归分析


    整体思路:二叉查找树是一棵树,对于树,需要把节点表示出来。由于节点仅仅在树的内部使用,故采用内部类的形式实现。

    树作为一种ADT,需要属性及在其上进行的操作。由于大部分树的操作都是从根节点开始的,因此需要一个根节点属性,并可根据自己的需求来确定需要实现哪些操作。

    对于二叉查找树,它不是一般的二叉树,它具有特点:任一节点的左子树上的节点都比它小,右子树上的节点都比它大。因此,二叉查找树的方法实现需要满足这个特点。

    一,树由结点组成

    结点的定义如下:

     1     private static class BinaryNode<T>{
     2         T element;
     3         BinaryNode left;
     4         BinaryNode right;
     5         
     6         public BinaryNode(T element) {
     7             this(element, null, null);
     8         }
     9         
    10         public BinaryNode(T element, BinaryNode<T>left, BinaryNode<T>right) {
    11             this.element = element;
    12             this.left = left;
    13             this.right = right;
    14         }
    15     }

    二,二叉查找树 类

    对于树,需要有根结点。BinaryNode<T>以静态内部类方式声明在BinarySearchTree<T>中。静态内部类的功能就是:静态内部类的对象的创建不需要依赖其外部类的对象。

    BinarySearchTree采用默认构造方法创建对象,然后调用 insert 方法向树中添加节点。

    public class BinarySearchTree<T extends Comparable<? super T>> {
        
        ......//BinaryNode<T>的声明
        
        private BinaryNode<T> root;
    
        ....... // 二叉查找树中的方法

    三,二叉查找树的一些方法的递归实现及分析

    1) insert(T ele),向二叉查找树中插入一个元素。插入元素之后,返回树根节点。

    正确版本:

     1 private BinaryNode<T> insert(T ele, BinaryNode<T> root){
     2         if(root == null)
     3             return new BinaryNode<T>(ele);
     4         int compareResult = ele.compareTo(root.element);
     5         if(compareResult > 0)
     6             root.right = insert(ele, root.right);
     7         else if(compareResult < 0)
     8             root.left = insert(ele, root.left);
     9         else
    10             ;
    11         return root;
    12     }

    错误版本一:

     1     private BinaryNode<T> insert1(T ele, BinaryNode<T> root){
     2         if(root == null)
     3             return new BinaryNode<T>(ele);
     4         
     5         int compareResult = ele.compareTo(root.element);
     6         if(compareResult > 0)
     7             return insert(ele, root.right);
     8         else if(compareResult < 0)
     9             return insert(ele, root.left);
    10         else
    11             return root;
    12         
    13     }

    这种版本的递归,返回的是:最后一层递归调用时,root所指向的节点。最后一层递归调用发生在叶子节点上,故返回的root是最后一个插入的元素。

    判断递归调用每一层的返回值时,可按照“压栈”的顺序进行分析。

    如,上面程序当运行到第7行时,执行递归调用,那么会将当前的环境压入栈中,保存起来。然后执行下一层的方法调用。

    当下面的递归调用结束后,程序返回到第7行。继续向后执行,可以看出第8行的 else if 和 第10行的 else的条件都不成立(因为程序已经在第7行条件成立了)

    错误版本二:

     1     private BinaryNode<T> insert2(T ele, BinaryNode<T> root){
     2         if(root == null)
     3             return new BinaryNode<T>(ele);
     4         int compareResult = ele.compareTo(root.element);
     5         if(compareResult > 0)
     6             root = insert2(ele, root.right);
     7         else if(compareResult < 0)
     8             root = insert2(ele, root.left);
     9         else
    10             ;
    11         return root;
    12     }


    版本二的错误和版本一一样,都没有建立起父节点与左右孩子节点的连接。因此,最终得到的树根节点指向的是最后一个插入的元素。

    2)查找二叉树中元素最大的节点

    正确版本:

     1  /*
     2      * 关于尾递归的返回值,该方法只会返回二个值: null 和 'root'
     3      * root 是最后一层递归调用时findMax的 root 参数
     4      */
     5     private BinaryNode<T> findMax(BinaryNode<T> root){
     6         if(root == null)
     7             return null;
     8         if(root.right == null)
     9             return root;
    10         else
    11             return findMax(root.right);
    12     }

    只有当树为空时,才返回null。第一个if判断才会执行,否则第一个if永远不会执行。每一次递归都会使问题的规模缩小---从以root为根的树,缩小成以root的右孩子为根的树。

    错误版本:

    1     private BinaryNode<T> findMax1(BinaryNode<T> root){
    2         if(root == null)
    3             return null;
    4         else
    5             return findMax(root.right);
    6     }
    7     

    该findMax递归版本,不管root是否为空,不管树中有多少个节点,返回的值都为null。从中可以看出,这种形式的递归返回值,由其基准条件来决定。

    3)先序遍历的递归算法分析

    1     public void preOrder(BinaryNode<T> root){
    2         if(root == null)
    3             return;
    4         System.out.print(root.element + " ");
    5         preOrder(root.left);
    6         preOrder(root.right);
    7     }

    该方法能够清晰地分析递归的步骤。假设先序遍历为e d c f g
                   e
                d      f

            c             g

    1)对于节点e,执行到第4行,输出e。在第5行,递归调用preOrder(d),并把preOrder(e)的相关信息压入栈中保存。

    2)对于节点d,执行到第4行,输出d。在第5行,递归调用preOrder(c),并把preOrder(d)的相关信息压入栈中保存。

    3)对于节点c,执行到第4行,输出c。在第5行,递归调用preOrder(c.left ‘null’),执行到第三行return null.

    此时,返回到 2)由于preOrder(d)在栈顶,弹出preOrder(d)的信息。

    由于preOrder(d)在它的下一层递归前执行到了第5行,故它从第6行继续向前执行:preOrder(d.right)=preOrder(null)。并又把preOrder(d)的相关信息压入栈中。

    4)执行preOrder(d.right==null)从第3行 return null

    此时,又弹出preOrder(d),此时从第6行向下继续执行,遇到了右大括号,执行结束,返回到上层递归处.

    5)此时preOrder(e)在栈顶,弹出。从第5行断点处继续向下执行:preOrder(e.right)=preOrder(f)

    6) 对于节点f,执行到第4行,输出f。在第5行,递归调用preOrder(f.left)=preOrder(null),并把preOrder(f)的相关信息压入栈保存。

    7)执行preOrder(f.left)在第三行返回,弹出preOrder(f)

    8) 从preOrder(f)的断点第5行处向下执行到第6行:preOrder(f.right)=preOrder(g)

    9)对于节点g,执行到第4行,输出g。在第5行递归调用preOrder(g.left),并将preOrder(g)的相关信息入栈....

    .....

    .....

    二叉查找树完整实现代码如下:

      1 package c4;
      2 
      3 import java.util.Random;
      4 
      5 import c2.C2_2_8;
      6 
      7 public class BinarySearchTree<T extends Comparable<? super T>> {
      8 
      9     private static class BinaryNode<T> {
     10         T element;
     11         BinaryNode<T> left;
     12         BinaryNode<T> right;
     13 
     14         public BinaryNode(T element) {
     15             this(element, null, null);
     16         }
     17 
     18         public BinaryNode(T element, BinaryNode<T> left, BinaryNode<T> right) {
     19             this.element = element;
     20             this.left = left;
     21             this.right = right;
     22         }
     23 
     24         public String toString() {
     25             return element.toString();
     26         }
     27     }
     28 
     29     private BinaryNode<T> root;
     30 
     31     public BinarySearchTree() {
     32         root = null;
     33     }
     34 
     35     public void makeEmpty() {
     36         root = null;
     37     }
     38 
     39     public boolean isEmpty() {
     40         return root == null;
     41     }
     42 
     43     public boolean contains(T ele) {
     44         return contains(ele, root);
     45     }
     46 
     47     private boolean contains(T ele, BinaryNode<T> root) {
     48         if (root == null)
     49             return false;
     50         int compareResult = ele.compareTo(root.element);
     51         if (compareResult > 0)
     52             return contains(ele, root.right);
     53         else if (compareResult < 0)
     54             return contains(ele, root.left);
     55         else
     56             return true;
     57     }
     58 
     59     public BinaryNode<T> findMax() {
     60         return findMax(root);
     61     }
     62 
     63     /*
     64      * 关于尾递归的返回值,该方法只会返回二个值: null 和 'root' root 是最后一层递归调用时findMax的 root 参数
     65      */
     66     private BinaryNode<T> findMax(BinaryNode<T> root) {
     67         if (root == null)
     68             return null;
     69         if (root.right == null)
     70             return root;
     71         else
     72             return findMax(root.right);
     73     }
     74 
     75     public BinaryNode<T> findMin() {
     76         return findMin(root);
     77     }
     78 
     79     private BinaryNode<T> findMin(BinaryNode<T> root) {
     80         if (root == null)
     81             return null;
     82         if (root.left == null)
     83             return root;
     84         else
     85             return findMin(root.left);
     86     }
     87 
     88     public void insert(T ele) {
     89         root = insert(ele, root);// 每次插入操作都会'更新'根节点.
     90     }
     91 
     92     private BinaryNode<T> insert(T ele, BinaryNode<T> root) {
     93         if (root == null)
     94             return new BinaryNode<T>(ele);
     95         int compareResult = ele.compareTo(root.element);
     96         if (compareResult > 0)
     97             root.right = insert(ele, root.right);
     98         else if (compareResult < 0)
     99             root.left = insert(ele, root.left);
    100         else
    101             ;
    102         return root;
    103     }
    104 
    105     public void preOrder(BinaryNode<T> root) {
    106         if (root == null)
    107             return;
    108         System.out.print(root.element + " ");
    109         preOrder(root.left);
    110         preOrder(root.right);
    111     }
    112 
    113     public void inOrder(BinaryNode<T> root) {
    114         if (root == null)
    115             return;
    116         inOrder(root.left);
    117         System.out.print(root.element + " ");
    118         inOrder(root.right);
    119     }
    120 
    121     public int height() {
    122         return height(root);
    123     }
    124 
    125     private int height(BinaryNode<T> root) {
    126         if (root == null)
    127             return -1;// 叶子节点的高度为0,空树的高度为1
    128 
    129         return 1 + (int) Math.max(height(root.left), height(root.right));
    130     }
    131 
    132     public static void main(String[] args) {
    133         BinarySearchTree<String> tree = new BinarySearchTree<>();
    134         tree.insert("e");
    135         tree.insert("d");
    136         tree.insert("c");
    137         tree.insert("f");
    138         tree.insert("g");
    139 
    140         System.out.println("contains g? " + tree.contains("g"));
    141         System.out.println("contains h? " + tree.contains("h"));
    142         System.out.println("max node: " + tree.findMax().toString());
    143         tree.preOrder(tree.root);
    144         System.out.println();
    145         tree.inOrder(tree.root);
    146         System.out.println("
    height: " + tree.height());
    147 148
  • 相关阅读:
    C# WebApi 获取客户端ip地址
    C# 构造函数快捷键
    2 .SHELL 5~10节
    Spring项目集成apidoc生成api接口文档
    根据域名查找对应的ip及端口
    高质量SQL的30条建议
    1 .shell编程1~5
    CentOS7安装mysql8
    编译安Apache2.4.43报错checking for APR... no configure: error: APR not found. Please read the documentation.
    rm -rf * 的正确用法
  • 原文地址:https://www.cnblogs.com/hapjin/p/5390451.html
Copyright © 2020-2023  润新知