树与哈希表---二分搜索树(BST)

本文详细介绍了二分搜索树的实现,包括添加元素、查找元素、前序、中序、后序和层序遍历、获取最小值和最大值、删除最小值和最大值以及任意元素的方法。同时,提供了迭代和递归两种实现方式,涵盖了数据结构与算法中的核心概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

二分搜索树

代码位置:TreeMap.java

1.何为二分搜索树

二分搜索树本身就是二叉树,但是又具备一些规则:对于每个节点而言,大于其左边节点的所有值,小于其右边节点的所有值

同样,每个节点的子树也是一个二分搜索树,二分搜索数中的元素要具有可比性,树中的元素不能重复

image-20220218142310605

2.添加元素

不管是当前的操作,还是之后的操作,都会有两种写法吗,迭代and递归

先看一下添加元素

迭代:

这个很好理解,我们只需要从头结点开始比较,小于头结点则想左走,大于则向右走,直到节点的下面为空,我们就将该元素插入即可

public void add(E e) {
    //添加的迭代思路
    Node node = new Node(e);
    if (isEmpty()) { // 如果当前节点为空,直接让当前节点成为root节点
        root = node;
        size++;
    }
    Node cur = root; // 定义一个节点,用来迭代
    while (true) {
        //新元素比当前大 往右走
        if (node.e.compareTo(cur.e) > 0) {
            if (cur.right == null) { // 如果右边为空,则插入
                cur.right = node;
                size++;
                break;
            } else {
                cur = cur.right;
            }
            //新元素比当前小 往左走
        } else if (node.e.compareTo(cur.e) < 0) {
            if (cur.left == null) { // 如果左边为空,则插入
                cur.left = node;
                size++;
                break;
            } else {
                cur = cur.left;
            }
        } else {
            break;
        }
    }
}

递归:

递归这个稍微有一点点抽象,因为和迭代不一样,迭代是直接找到然后进行添加,递归则是我一直递归到我需要添加的位置,然后对该位置进行添加,之后就是一直向上返回,最终是返回一个新的root节点,举个例子,假如现在有一个机器人,他没有腿,现在要装上腿,迭代来讲就是我先找到腿和腰链接的地方,然后以腰部向下进行安装,递归则是先从脚到大腿根部拼好,再返回给腰部

//在以node为根的树中 插入元素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;
}

3.包含元素

只需要依次向下进行迭代即可,找到和目标相同的值

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

4.前序遍历

递归

//前序遍历-递归方式 以node为根节点 前序遍历DLR
private void preOrder(Node node) {
    if (node == null) {
        return;
    }
    System.out.println(node.e);
    preOrder(node.left);
    preOrder(node.right);
}

迭代

前序遍历是【中左右】的方式进行遍历的,先中后左最后右,我们借助栈完成的话,因为栈是先进后出的特性,所以我们应当先将节点 右边进栈,后将节点左边进栈

//前序遍历-迭代方式
public void preOrderNR() {
    LinkedList<Node> stack = new LinkedList<>();
    if (isEmpty()) {
        return;
    }
    stack.push(root);
    while (!stack.isEmpty()) {
        Node cur = stack.pop();
        System.out.println(cur.e);
        if (cur.right != null) { // 先将右节点入栈
            stack.push(cur.right);
        }
        if (cur.left != null) {
            stack.push(cur.left);
        }
    }
}

5.中序遍历

递归

//中序遍历-递归方式 以node为根节点 中序遍历LDR
private void inOrder(Node node) {
    if (node == null) {
        return;
    }
    inOrder(node.left);
    System.out.println(node.e);
    inOrder(node.right);
}

迭代

中序表达式是【左中右】,因此我们先一股脑将节点左边-左边-左边-……一直入栈,然后再弹出数值,再判断该节点是否有右节点,如果有右节点,进而判断该右节点是否有左节点,如果有再入,总而言之,就是左节点优先级最高

//中序遍历-迭代方式
public void inOrderNR() {
    LinkedList<Node> stack = new LinkedList<>();
    Node p = root;
    while (p != null) {
        stack.push(p);
        p = p.left;
    }
    while (!stack.isEmpty()) {
        Node cur = stack.pop();
        System.out.println(cur.e);
        if (cur.right != null) {
            Node n = cur.right;
            while (n != null) {
                stack.push(n);
                n = n.left;
            }
        }
    }
}

6.后序遍历

递归

//后序遍历-递归方式 以node为根节点 后序遍历LRD
private void postOrder(Node node) {
    if (node == null) {
        return;
    }
    postOrder(node.left);
    postOrder(node.right);
    System.out.println(node.e);
}

迭代

后序遍历是左右中,就是先将左右走完,再走中间值,因此我们需要借助一个链表来存储我们遍历的结果,每次将得到的数添加到第一位,或者也可以依次添加,最后进行链表反转

// 后序遍历-迭代
public void postOrderNR() {
    LinkedList<Node> stack = new LinkedList<>();
    LinkedList<E> list = new LinkedList<>();
    if (root == null) {
        return;
    }

    stack.add(root);
    while (!stack.isEmpty()) {
        Node cur = stack.pop();
        list.addFirst(cur.e);
        if (cur.left != null) {
            stack.add(cur.left);
        }
        if (cur.right != null) {
            stack.add(cur.right);
        }
    }
    System.out.println(list);
}

7.层序遍历

因为需要一层一层的去遍历,所以我们采用队列的方式去对他进行遍历

//层序遍历-迭代方式
public void levelOrder() {
    LinkedList<Node> queue = new LinkedList<>();
    if (isEmpty()) {
        return;
    }
    queue.offer(root);
    while (!queue.isEmpty()) {
        Node cur = queue.poll();
        System.out.println(cur.e);
        if (cur.left != null) {
            queue.offer(cur.left);
        }
        if (cur.right != null) {
            queue.offer(cur.right);
        }
    }
}

8.最小值和最大值

BST的定义是,对于任意一个节点来说,它右节点全部小于它,它的左节点全部大于他

因此我们可以得出对于BST来说,从根节点一直向右可以找出它的最小值,一直向左可以找到它的最大值

//寻找二分搜索树中的最小值
//自己尝试迭代方式实现
public E minimum() {
    if (isEmpty()) {
        throw new IllegalArgumentException("bst is empty");
    }
    return minimum(root).e;
}

//以node为根节点 查找最小值所在的结点 - 递归
private Node minimum(Node node) {
    if (node.left == null) {
        return node;
    }
    return minimum(node.left);
}

// 迭代
    public E minimumNR() {
        Node cur = root;
        while (cur.left != null) {
            cur = cur.left;
            if (cur.left == null) {
                break;
            }
        }
        return cur.e;
    }
//寻找二分搜索树中的最大值
//自己尝试迭代方式实现
public E maximum() {
    if (isEmpty()) {
        throw new IllegalArgumentException("bst is empty");
    }
    return maximum(root).e;
}

//以node为根节点 查找最大值所在的结点 - 递归
private Node maximum(Node node) {
    if (node.right == null) {
        return node;
    }
    return maximum(node.right);
}

9.删除最小值和最大值

在删除最小值和最大值时,我们选择递归的方式去实现,这个过程可以理解为将一个机甲的腿坎掉,然后返回给它的腰部,这时这个新的腰子(没有腿)再返回给头部

//删除最小值
public E removeMin() {
    E ret = minimum();
    root = removeMin(root);
    return ret;
}

//以node为根 删除其中的最小值结点 返回删除后新树的根
private Node removeMin(Node node) {
    if (node.left == null) {
        Node rightNode = node.right;
        node.right = null;
        size--;
        return rightNode;
    }
    node.left = removeMin(node.left);
    return node;
}
//删除最大值
public E removeMax() {
    E ret = maximum();
    root = removeMax(root);
    return ret;
}

//以node为根 删除其中的最大值结点 返回删除后新树的根
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;
}	

10.删除任意元素

删除任意元素时,我们唯一需要进行考虑的是这个节点如果有子树,那么我们删除后该怎么办

这时候有三种情况

1.左边为空,右边补上,即使右边为空,也依旧可以补上

2.右边为空,在前面的基础上,此时左边肯定不为空,让左边补上

3.都不为空,我们将该节点右子树的最小值补上去 ,当然也可以是左子树的最大值

public void remove(E e) {
    root = remove(root,e);
}

//以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 {    //找到了
        //如果待删除的结点左边为空 则右子树直接上
        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 = minimum(node.right); // 右子树的最小值的节点
        successor.right = removeMin(node.right); //
        successor.left = node.left;
        node.left = node.right = null;
        return successor; // 最终将新的节点返回
    }
}

11.toString

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("[");
        if (isEmpty()) {
            sb.append(']');
        } else {
            Iterator<E> it = iterator();
            for (int i = 0; i < size; i++) {
                sb.append(it.next());
                if (i == size - 1) {
                    sb.append(']');
                } else {
                    sb.append(',');
                }
            }

//            while (it.hasNext()) {
//                sb.append(it.next());
//                sb.append(',');
//            }
//            sb.deleteCharAt(sb.length() - 1);
//            sb.append(']');
        }
        return sb.toString();
    }

12.迭代器

@Override
public Iterator<E> iterator() {
    return new BinarySearchTreeIterator();
}
private class BinarySearchTreeIterator implements Iterator<E> {
    private Iterator<E> it;
    public BinarySearchTreeIterator() {
        LinkedList<E> list = new LinkedList<>();
        LinkedList<Node> stack = new LinkedList<>();
        Node p = root;
        while (p != null) {
            stack.push(p);
            p = p.left;
        }
        while (!stack.isEmpty()) {
            Node cur = stack.pop();
            list.offer(cur.e);
            if (cur.right != null) {
                Node n = cur.right;
                while (n != null) {
                    stack.push(n);
                    n = n.left;
                }
            }
        }
        it = list.iterator();
    }

    @Override
    public boolean hasNext() {
        return it.hasNext();
    }

    @Override
    public E next() {
        return it.next();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值