目录树 删除 数据结构_八、二分搜索树(BST)来了

c8eb7a9137b21e878f189e13c71b98c2.png

今天的目标是学习二分搜索树。那么首先我们来了解下什么是树。

是一种抽象数据类型(ADT)的数据结构,用来模拟具有树状结构性质的数据集合,是一个种天然的组织结构,和链表一样是一个动态数据结构。是由n(n>=1)个有限节点组成一个具有层次关系的集合。根朝上,叶朝下

树的特点是:

1.每个节点有零个或多个子节点

2.没有父节点的节点成为根节点

3.每一个非根节点有且只有一个父节点

4.除了根节点外,每个子节点可以分为多个不相交的子树

树的常见应用有:xml、html文档,MySQL数据库索引、文件系统的目录结构等。

树的优点:高效。

很多高效的运算结果,它的背后其实是因为有树这样的数据结构作为支撑,其实这也是我们要学习数据结构,包括数据结构在计算机科学领域非常重要的一个原因。数据结构虽然解决的是数据存储的问题,但是在使用的层面上,不仅仅是因为要存储数据,更重要的是当我们使用某种特殊的数据结构存储数据时,将帮助我们更加高效的解决某些算法问题。

那么接下来我们来看看什么是二叉树。在树中,二叉树是最常用的一种数据结构。具有唯一的根节点,每个节点最多有两个子树,通常子树被称作“左子树”和“右子树”,每个节点最多有一个父节点。

二叉树具有天然的递归性。每个节点的左子树也是二叉树,每个节点的右子树也是二叉树。但是二叉树不一定是满二叉树。

说完二叉树,我们来看看今天的重点:二分搜索树

对于二分搜索树来说,首先是一个二叉树,其次存储的元素必须有可比较性,对于二分搜索树来说,其值:

  1. 大于其左子树的所有节点的值,小于其右子树的所有节点的值。

2. 每一颗子树也是二分搜搜树。

接下来我们一起操作下增删查改

那么对于添加元素来说,我们基于递归思想来实现,并且本次实现不包含重复元素。直接上代码:

// 向二分搜索树种添加新的元素e
public void add(E e) {
    if (root == null) {
        root = new Node(e);
    } else {
        add(root, e);
    }
}

// 返回插入新节点后二分搜索树的根,不需要对左子树或右子树是否为空进行判断
private Node add(Node node, E e) {
    if (node == null) {
        size++;
        return new Node(e); // 节点和二叉树联系起来
    }
    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;
}

对于查询操作,我们用contains,看是否包含元素e。

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

// 查看以node为根的二分搜索树中是否包含元素e,递归算法
private boolean contains(Node node, E e) {
    if (node == null) {
        return false;
    }
    if (e.compareTo(node.e) == 0) {
        return true;
    } else if (e.compareTo(node.e) < 0) {
        return contains(node.left, e);
    } else {
        return contains(node.right, e);
    }
}

对于删除元素来说,有三种情况:

1.删除最大元素

2.删除最小元素

3.删除任意位置元素

在删除任意位置元素时,需要再分情况判断,删除的只有左节点的,删除只有右节点的,以及如果左右节点都有的话,思路就是要找到比待删除节点大的最小节点,即待删除节点右子树的最小节点,然后用这个节点顶替待删除节点的位置。

那么首先对于删除最大元素来说,需要查找到最大元素的节点位置。

// 寻找二分搜索树的最大元素
public E maxinum() {
    if (size == 0) {
        throw new IllegalArgumentException("BST is empty.");
    }
    return maxinum(root).e;
}

// 返回以node为根的二分搜索树的最小值所在的节点
private Node maxinum(Node node) {
    if (node.right == null) {
        return node;
    }
    return maxinum(node.right);
}

当确定最大元素位置后,即可以删除最大元素。

// 删除最大值所在节点,返回最大值
public E removeMax() {
    E ret = maxinum();
    root = removeMax(root);
    return ret;
}

// 删除以node节点为根的最大节点
// 返回删除节点后新的二分搜索树的根
private Node removeMax(Node node) {
    if (node.right == null) {
        Node leftNode = node.left;
        node.left = null; // 资源释放,删除该node节点 
        size--;
        return leftNode;
    }
    node.right = removeMax(node.right);
    return node;
}

对于删除最小元素的操作,我们就不过多阐述了,跟删除最大元素基本一样。

那么对于删除任意位置的元素,我们来看代码:

// 删除以node为根的二分搜索树中值为e的节点,递归算法
// 返回删除节点后新的二分搜索树的根
private Node remove(Node node,E 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 { // node.e==e
        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;
        }
        // 待删除节点左右子树均不为空的情况
        // 找得到比待删除节点大的最小节点,即待删除节点右子树的最小节点
        // 用这个节点顶替待删除节点的位置
        Node successor = mininum(node.right);
        successor.right=removeMin(node.right);
        successor.left = node.left;
        node.left = node.left = null;
        return successor;
    }
}

那么对于删除任意位置的元素e时,考虑的情况就会很多。具体的逻辑参考上述代码,不过多阐述了。

操作完增删查改后,我们一起来看看遍历。说起遍历,就是把所有节点都访问一遍。对于遍历,我们采用深度优先,也就是递归思想来实现。

前序遍历,也是我们常说的最自然最常用的遍历方式。采用根 左 右的方法,进行遍历。具体实现如下:

// 二分搜索树的前序遍历
public void preOrder() {
    preOrder(root);
}

// 前序遍历以node为根的二分搜索树,递归算法
private void preOrder(Node node) {

    if (node == null) {
        return;
    }
    System.out.println(node.e);
    preOrder(node.left);
    preOrder(node.right);
}

中序遍历,采用左 根 右的方式来实现。

// 中序遍历
public void inOrder() {
    inOrder(root);
}

private void inOrder(Node node) {
    if (node == null) {
        return;
    }
    inOrder(node.left);
    System.out.println(node.e);
    inOrder(node.right);
}

后序遍历,采用左 右 根的方式来实现,通常我们说的内存释放就类似于这样。

// 后序遍历
public void postOrder() {
    postOrder(root);
}

private void postOrder(Node node) {
    if (node == null) {
        return;
    }
    postOrder(node.left);
    postOrder(node.right);
    System.out.println(node.e);
}

好了,今天关于二分搜索树的介绍就到这里,重点就是二分搜索树的特点,以及增删查改和遍历的方式。其中删除任意位置的元素e,考虑的情况会有多种。

整体来说,二分搜索树的操作很有意思,在应用方面有很多场景,长路漫漫,继续努力!

OK,今天就到这里,拜了个拜~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值