数据结构(基于java语言描述) --二分搜索树

基于二叉树的二分搜索树,可以更高效对数据进行查询。

import java.util.LinkedList;
import java.util.Queue;

public class BST<E extends Comparable<E>>{
    private class Node{
        public E e;
        public Node left, right;

        public Node(E e){
            this.e = e;
            left = null;
            right = null;
        }

    }

    private int size;
    private Node root;

    public BST(){
        size = 0;
        root = null;
    }

    public int getSize(){ return size;}

    public boolean isEmpty(){ return size == 0;}

    // 看二分搜索树中是否包含元素e
    public boolean contians(E e){
        return contians(root, e);
    }

    // 看以node为根的二分搜索树中是否包含元素e, 递归算法
    private boolean contians(Node node, E e){
        if (node == null){
            return false;
        }

        if (e.compareTo(node.e) < 0){
            return contians(node.left, e);
        }
        else if(e.compareTo(node.e) > 0){
            return contians(node.right, e);
        }else{
            return true;
        }
    }

    // 向二分搜索树中添加新的元素e
    public void add(E e){ root = add(root, e);}

    // 向以node为根的二分搜索树中插入元素e,递归算法
    // 返回插入新节点后二分搜索树的根
    public Node add(Node node, E e){
     
      if (node == null){
          node = new Node(e);
          size++;
          return node;
      }
if (e.compareTo(node. e) < 0){ node. left = add(node. left , e) ; } else if (e.compareTo(node. e) > 0){ node. right = add(node. right , e) ; } return node ; } // 二分搜索树的前序遍历 public void perOrder(){ perOrder( root) ;} // 前序遍历以 node 为根的二分搜索树 , 递归算法 private void perOrder(Node node){ if (node == null){ return; } System. out.println(node. e) ; perOrder(node. left) ; perOrder(node. right) ; } // 二分搜索树的中序遍历 public void inOrder(){ inOrder( root) ;} // 中序遍历以 node 为根的二分搜索树 , 递归算法 private void inOrder(Node node){ if (node == null){ return; } inOrder(node. left) ; System. out.println(node. e) ; inOrder(node. right) ; } // 二分搜索树的后序遍历 public void postOrder(){ perOrder( root) ;} // 后序遍历以 node 为根的二分搜索树 , 递归算法 public void postOrder(Node node){ if (node == null){ return; } postOrder(node. left) ; System. out.println(node. e) ; postOrder(node. right) ; } // 二分搜索树的层序遍历 // 用了队列这一先进先出的特点,通过迭代的方法,将二分树以从上往下,从左往右的方式 // 依次打印出来 public void levelOrder(){ Queue<Node> q = new LinkedList<>() ; q.add( root) ; while (!q.isEmpty()){ Node cur = q.remove() ; System. out.println(cur. e) ; if (cur. left != null){ q.add(cur. left) ; } if (cur. right != null){ q.add(cur. right) ; } } } // 寻找二分搜索树的最小元素 public E minimum(){ if ( size == 0){ throw new IllegalArgumentException( "index is empty") ; } return minimum( root). e ; } // 返回以 node 为根的二分搜索树的最小值所在的节点 private Node minimum(Node node){ if (node. left == null){ return node ; } return minimum(node. left) ; } // 寻找二分搜索树的最大元素 public E maximum(){ if ( size == 0){ throw new IllegalArgumentException( "index is empty") ; } return maximum( root). e ; } // 返回以 node 为根的二分搜索树的最大值所在的节点 private Node maximum(Node node){ if (node. right == null){ return node ; } return maximum(node. right) ; } // 从二分搜索树中删除最小值所在节点 , 返回最小值 public E removeMin(){ E ret = minimum( root). e ; root = removeMin( root) ; return ret ; } // 删除掉以 node 为根的二分搜索树中的最小节点 // 返回删除节点后新的二分搜索树的根 // 8 8 // / / // 5 5 // / \ / \ // 2 7 4 7 // \ / // 4 3 // / // 3 // 无论最小的节点是否有右节点,我们都假设它有一个右节点 private Node removeMin(Node node){ if (node. left == null){ Node rightNode = node. right ; // 此时找到最小节点,并用 rightNode 将此最小节点的右节点存储 node. right = null; size-- ; return rightNode ; // 迭代中的 node.left 指向的最小节点已经由 node 变为 node.right } // 通过不断迭代 node 的左节点并跟新(将最小节点删除后的调整) node. left = removeMin(node. left) ; return node ; } // 从二分搜索树中删除最大值所在节点 public E removeMax(){ E ret = maximum( root). e ; root = removeMax( root) ; return ret ; } // 删除掉以 node 为根的二分搜索树中的最大节点 // 返回删除节点后新的二分搜索树的根 // 8 8 // / \ / \ // 5 15 5 15 // / \ \ / \ \ // 2 7 19 2 7 17 // \ / \ \ // 4 17 4 18 // / \ / // 3 18 3 // 同上,我们假设最大节点存在左节点 private Node removeMax(Node node){ if (node. right == null){ Node leftNode = node. left ; node. left = null; size-- ; return leftNode ; } node. right = removeMax(node. right) ; return node ; } // 从二分搜索树中删除元素为 e 的节点 public void remove( E e){ root = remove( root , e) ; } // 删除掉以 node 为根的二分搜索树中值为 e 的节点 , 递归算法 // 返回删除节点后新的二分搜索树的根 private Node remove(Node node , E e){ // if 判断有两种意思: node 本身就为空,或者迭代到最后都没找到含有 e 的节点,均返回为空 if (node == null){ return null; } if (e.compareTo(node. e) < 0){ node. left = remove(node. left , e) ; return node ; } else if (e.compareTo(node. e) > 0){ node. right = remove(node. right , e) ; return node ; } else { // 找到了匹配的节点 // 待删除节点左子树为空的情况 // 8 8 // / \ / \ // 5 [15] 5 19 // / \ \ / \ / // 2 7 19 2 7 17 // \ / \ \ // 4 17 4 18 // / \ / // 3 18 3 // removeMax 是一样的方法 if (node. left == null){ Node rightNode = node. right ; node. right = null; size-- ; return rightNode ; } // 待删除节点右子树为空的情况 if (node. right == null){ Node leftNode = node. left ; node. left = null; size-- ; return leftNode ; } // 待删除节点左右子树均不为空的情况 // 找到比待删除节点大的最小节点 , 即待删除节点右子树的最小节点 // 用这个节点顶替待删除节点的位置 // 18 18 // / / // [12] 13 // / \ / \ // 7 16 7 16 // / \ / \ / \ / \ // 6 8 13 17 6 8 14 17 // \ // 14 //successor 为待删除节点右侧最小子节点 Node successor = minimum(node. right) ; // 将该节点单独拿出来 // 返回删除 successor 节点后跟新的待删除节点的右侧子节点,并储存在 successor 的有节点上 successor. right = removeMin(node. right) ; successor. left = node. left ; // 此时的 successor 节点已经拥有待删除节点的左节点和跟新过的右节点 node. left = node. right = null; return successor ; // 之所以没有 size-- 是因为调用 removeMin 的方法中含有该操作 } }}

我们再对其进行测试

public class Main {
    public static void main(String[] args) {

        BST<Integer> bst = new BST<>();

        int[] arr = new int[]{18,12,12,12,12,7,3,4,8,16,13,17,14,30,20,32,50,40};
        for (int i = 0; i < arr.length; i++){
            bst.add(arr[i]);
        }
        //                18
        //              /    \
        //            12      30
        //           /  \     / \
        //          7    16  20  32
        //         / \   / \      \
        //        3   8 13  17     50
        //        \       \        /
        //         4      14      40

        //前序遍历
//        bst.perOrder();
        //中序遍历(按顺序出来)
        bst.inOrder();
        //后续遍历
//        bst.perOrder();
        //层序遍历
//        bst.levelOrder();

    }
}

前辈传授的理解前中后序遍历的经验:

比如上图的二叉树,可以将每个元素的下面画上3个点,其中两个代表着相对左右位置的点分别为左右子树

前序遍历:三个点one,two,three依次为println,left,right。先打印 18,看left,是12,此时12下面又有三个点,先看one,是打印,接着打印12,看第二个点,是left,看left,是7。以此类推,到了属性为3的节点,先打印,再看left, left没有,看right,是4,看4的下面3个点,第一个是打印,打印4,left和right都没有,返回上一层,上一层3个点都看过了,再返回上一层。是7,它的前两个点都看了,看right,是8。看8下面的3个点。。。。。。。。。。。。。。。。

依照这个思路,你就会很轻易得出这些顺序。


假设这个二叉树是完全展开,我的意思是几乎每个节点都有左右两个子节点,是一棵平衡树,它的时间复杂度等同于O(logn),但这只是理想状态,当插入一组有序的元素,根据上面add方法可以看出,它等同于一条直线,时间复杂度为O(n)。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值