二叉搜索树(java版)

二叉搜索树(Binaru Search Tree)

性质
  • 二叉搜索树是二叉树的一种,应用非常广泛,简称BST
  • 任意一个节点的值都大于其左子数所有节点的值
  • 任意一个节点的值都小于其右子树所有节点的值
  • 它的左右子树也是一个二叉搜索树

需求:

  • 可以存储任何元素
  • 自定义类型,需要指定比较方式
  • 不能为null

新建一个BinarySearchTree.java文件

定义节点
private static class Node<E> {
        E element;
        Node<E> left;
        Node<E> right;
        Node<E> parent;

        public Node(E element, Node<E> parent) {
            this.element = element;
            this.parent = parent;
        }
        /**
         * 是否为叶子节点
         * @return
         */
        public boolean isLeaf() {
            return left == null && right == null;
        }

        /**
         * 度为2的结点
         * @return
         */
        public boolean hasTwoDeg() {
            return left != null && right != null;
        }
 }
构造函数以及所需成员变量
public class BinarySearchTree<E>  {

    private int size; //容量
    private Node<E> root; //根节点
    private Comparator<E> comparator; //比较器

    public BinarySearchTree() {
        this(null);
    }

    public BinarySearchTree(Comparator<E> comparator) {
        this.comparator = comparator;
    }
}
添加节点思路
  • 判断添加的元素是否为空,为空直接抛出异常
  • 如果是添加第一个节点(直接判断根节点是否为空)
    • 为空就直接new一个节点,parent为null,size++即可
  • 如果不是添加第一个节点
    • 先找到父节点 (利用二叉搜索树的性质)
    • 判断添加的节点和当前节点元素的大小,
    • 大于就往右边继续查找
    • 小于就往左边查找
    • 等于这里只做覆盖处理,不做其他处理
/**
   * 添加元素
   * @param element
   */
  public void add(E element)  {
      if (element == null ) {
          throw new IllegalArgumentException("元素不能为空");
      }
      //添加第一个节点
      if (root == null) {
          root = new Node<>(element, null);
          size++;
          return;
      }
      //其它节点
      Node<E> parent = root; //记录父亲节点
      Node<E> node = root;  //用于循环查找
      int cmp = 0;
      do {
          cmp = compare(element, node.element); 
          parent = node;
          if (cmp > 0) {
              node = node.right;
          }else if (cmp < 0) {
              node = node.left;
          }else {
              //相等
              node.element = element; //节点值覆盖
              return;
          }
      }while (node != null);

      Node<E> newNode = new Node<>(element, parent);
      if (cmp > 0){
          parent.right = newNode;
      }else  {
          parent.left = newNode;
      }
      size++;

  }
比较函数

compare(E element, E element1)

  • 对于自定义类型,需要指定比较方式,所以需要用到Comparator
  • 允许外界传入一个 Comparator 自定义比较方案
  • 如果没有传入 Comparator,强制认定元素实现了 Comparable 接口
//比较元素大小
    private int compare(E element, E element1) {
        if (comparator != null) {
            return comparator.compare(element, element1);
        }
        return ((Comparable<E>) element).compareTo(element1);
    }

使用方法
compareTo()
自定义一个Student类用age的大小来存放,需要重写compareTo函数

@Override
    public int compareTo(Person o) {
        return this.age - o.age;
    }

comparator()
使用匿名内部类实现

BinarySearchTree<Student> bst = new BinarySearchTree<>(new Comparator<Student>() {
            public int compare(Studento1, Studento2) {
                return o2.getAge() - o1.getAge();
            }
        });
遍历二叉树
前序遍历(递归)
  • 访问顺序
    • 根节点 > 左节点 > 右节点
public void preorderTraversal() {
        preorderTraversal(root);
    }

public void preorderTraversal(Node<E> node) {
    if (node == null) return;
    System.out.println(node.element);
    preorderTraversal(node.left);
    preorderTraversal(node.right);
}
前序遍历(非递归思想)
  • 利用栈实现
    • 将root入栈
    • 循环下面操作
    • 弹出栈顶节点top,
    • 将top.right入栈
    • 将top.left入栈
中序遍历(递归)
  • 访问顺序
    • 左节点 > 根节点 > 右节点
 //中序遍历
public void inorderTraversal() {
    inorderTraversal(root);
}

public void inorderTraversal(Node<E> node) {
    if (node == null) return;

    inorderTraversal(node.left);
    System.out.println(node.element);
    inorderTraversal(node.right);
}
中序遍历(非递归思想)
  • 利用栈实现
    • 设置 node = root
    • 循环执行以下操作
    • 如果 node != null
    • 将 node 入栈
    • 设置 node = node.left
    • 如果 node == null
    • 如果栈为空,结束遍历
    • 如果栈不为空,弹出栈顶元素并赋值给 node
    • 对 node 进行访问
    • 设置 node = node.right
/**
     * 不使用递归解决
     */
public List<Integer> inorderTraversal2(TreeNode root) {
        Stack<TreeNode> stack = new Stack<>();
        List<Integer> result = new ArrayList<>();

        if (root == null) return result;

        TreeNode cur = root;
         while (cur != null || !stack.empty()) {
             if (cur != null) {
                 stack.push(cur);
                 cur = cur.left;
             }else {
                 cur = stack.pop();
                 result.add(cur.val);
                 cur = cur.right;
             }
         }
        return result;
}
后序遍历(递归)
  • 访问顺序
    • 左节点 > 右节点 > 根节点
//后序遍历
public void postorderTraversal() {
     postorderTraversal(root);
 }

 public void postorderTraversal(Node<E> node) {
     if (node == null) return;

     postorderTraversal(node.left);
     postorderTraversal(node.right);
     System.out.println(node.element);
 }
层序遍历
  • 访问顺序
    • 从上到下,从左到有依次访问
    • 使用队列实现
    • 先将根节点入队
    • 循环以下操作
    • 将对头节点出队
    • 将出队的节点的左子节点和右子节点入队
public void leverOderTraversal() {
       if (null == root) return;

       Queue<Node<E>> queue = new LinkedList<>();
       queue.offer(root);

       while (!queue.isEmpty()) {
           Node<E> node = queue.poll();
           System.out.println(node.element);

           if (null != node.left) {
               queue.offer(node.left);
           }
           if (null != node.right) {
               queue.offer(node.right);
           }
       }

   }
删除节点(可查找相关文章查看如何实现,不想写了)

在删除节点之前需要了解的一些概念
前驱节点 : 某个节点按中序遍历后的前一个节点
后继节点 : 某个节点按中序遍历后的后一个节点

 /**
     * 查找某个节点的前驱节点
     * 前驱节点:中序遍历的前一个节点
     * @param node
     * @return
     */
    private Node<E> predecessor(Node<E> node) {
        if (node == null) return null;

        //前驱节点再左子树当中(left.right.right....)
        Node<E> pre = node.left;
        if (pre != null) {
            while (pre.right != null) {
                pre = pre.right;
            }
            return pre;
        }

        //从父节点、祖父节点中寻找前驱节点
        while (node.parent != null && node == node.parent.left){
            node = node.parent;  //叶子节点在parent的左边
        }

        return node.parent;
    }

    /**
     * 获取节点的后继节点
     * 后继结点:中序遍历的后一个节点
     * @param node
     * @return
     */
    private Node<E> sucessor(Node<E> node) {
        if (node == null) return null;

        // 前驱节点在左子树当中(right.left.left.left....)
        Node<E> pre = node.right;
        if (pre != null) {
            while (pre.left != null) {
                pre = pre.left;
            }
            return pre;
        }

        // 从父节点、祖父节点中寻找前驱节点
        while (node.parent != null && node == node.parent.right) {
            node = node.parent;
        }

        return node.parent;
    }
删除节点
/**
* 根据元素查找节点
* @param element
* @return
*/
private Node<E> node(E element) {
   Node<E> node = root;
   while (node != null) {
       int cmp = compare(element, node.element);
       if (cmp == 0) return node;
       if (cmp > 0) {
           node = node.right;
       }else {
           node = node.left;
       }
   }
   return null;
}


/**
 * 删除节点
 * @param element
 */
public void remove(E element) {
    remove(node(element));
}
  
private void remove(Node<E> node){
        if (node == null) return;
        size--;

        if (node.hasTwoDeg()) {
            //度为2的节点
            //找到后继节点
            Node<E> s = sucessor(node);
            //用后继节点覆盖度为2的节点的值
            node.element = s.element;
            //删除后继节点
            node = s;
        }

        //删除node节点(度为1或者0)
        Node<E> replace = node.left != null ? node.left : node.right;
        if (replace != null) { // node是度为1的节点
            // 更改parent
            replace.parent = node.parent;
            // 更改parent的left、right的指向
            if (node.parent == null) { // node是度为1的节点并且是根节点
                root = replace;
            } else if (node == node.parent.left) {
                node.parent.left = replace;
            } else { // node == node.parent.right
                node.parent.right = replace;
            }
        } else if (node.parent == null) { // node是叶子节点并且是根节点
            root = null;
        } else { // node是叶子节点,但不是根节点
            if (node == node.parent.left) {
                node.parent.left = null;
            } else { // node == node.parent.right
                node.parent.right = null;
            }
        }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值