二叉搜索树简介之java实现

这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战

声明

本文是对二叉搜索树(也叫排序二叉树)的基本介绍,及相关操作说明,最后附有代码说明,本文主要采用的编程语言为java。

定义

二叉搜索树,看字面意,本质就是一个特殊结构的二叉树,按照维基百科的说法:二叉搜索树是带根的二叉树,其内部每个节点都存储一个键(以及可选的关联值),并且每个都有两个不同的子树,通常表示为左和右。

本文说明主要采用java实现,每个节点会存储一个值(value),没有所谓的key。

根据二叉搜索树的性质,一颗二叉搜索树在根节点非空的情况下,节点分布应当遵守以下规则:

  1. 左子树非空,左子树上所有节点的值都小于左子树的父节点的值
  2. 右子树非空,右子树上所有节点的值都大于右子树的父节点的值
  3. 左右子树也应当是一棵二叉排序树

二叉搜索树多用来构造更抽象的数据结构,如集合、关联数组等。(这句话是维基说的,不是我)

操作

二叉搜索树支持的主要操作有:插入、删除、查询。

二叉搜索树在执行这几种操作时,需要与树的左或右子节点进行比较,所以插入、删除和查询操作的时间复杂度与树的深度有关(类似于二分查找),如树的节点数为n,则时间复杂度为O(log n),n为树的节点个数。

当时间复杂度处于最坏的情况下为O(n),即树退化为链表,具体可以看2.3节点查询操作介绍。

插入

根据二叉搜索树的性质,插入操作需要满足以下条件(即插入步骤):

  1. 如果树为空(根节点指向空),新增节点为根节点,否则以根节点作为当前节点开始搜索
  2. 如果新插入节点的值小于当前被比较节点的值且当前被比较节点的左子树不为空,则以当前被比较节点的左子节点作为当前节点继续进行搜索
  3. 如果新插入节点的值不小于当前被比较节点的值且当前被比较节点的右子树不为空,则以当前被比较节点的右子节点作为当前节点继续进行搜索
  4. 重复步骤2或3,直到找到满足条件5的节点
  5. 如果新增节点小于当前节点的值且当前节点的左子树为空,则新增节点作为当前节点的左子节点;否则,如果新增节点不小于当前节点且当前节点的右子树为空,则新增节点作为当前节点的右子节点。

以插入数组[18,5,4,66,32,78]为例 ,看下图就明白了。

如果执行中序遍历,则按从小到的顺序排列:4,5,18,32,66,78

删除

当删除二叉搜索树的某个结点后,需要保证删除后的二叉树依然是一个二叉搜索树的结构,进行删除时则应当遵守以下规则(按下方式/技巧进行删除):

  1. 被删除节点为根节点且只有一个根节点,删除根节点即可(根节点指向空)
  2. 被删除节点为叶子节点,从父节点中移除即可
  3. 被删除节点只有左子节点,将其左子节点作为其父节点的左子节点
  4. 被删除节点只有右子节点,将其右子节点作为其父节点的右子节点
  5. 被删除结点的左右子节点均不为空时,找到被删除节点前趋节点(后继节点也行,本次以前趋为例),前趋节点是左子树中最大的节点(也就是左子树中最右边的节点,根据性质,这个前趋节点要么有一个子节点,要么没有叶子节点),重复步骤2,3,4,直到被删除为止。当然删除还有其它的方法,读者可查阅其它资料了解,本文以该方法进行说明。

情况1、2、3、4、5其实看字面意思就很了解,重点图例说明下第5种情况的删除操作:

删除节点66后的中序遍历操作为:4,5,18,32,78

查询

查询操作其实就是被查询节点在树的每一层与其中一个节点的值比较,直到找到相等的值的节点,或者最后返回空。插入和删除也都是要应用查询操作的。

因为查询比较时,每一层只用和一个节点比较(要么左子节点,要么右子节点),所以它的时间复杂度为O(log n),与树的深度有关,但是在最坏的情况下,即二叉搜索树退化为链表,插入的值是有序的,比如(1,2,3,4,5),此时,时间复杂度为O(n),树的结构如下图:

java源码实现

package com.xuxd.binary.search;

import java.util.ArrayList;
import java.util.List;

/**
 * @Description: 二叉搜索树java实现
 */
public class BinarySearchTree<E extends Comparable> {

    /**
     * 根节点
     */
    private Node root;

    public BinarySearchTree() {
    }

    public BinarySearchTree(E e) {
        this.root = new Node(e, null, null, null);
    }

    /**
     * 增加一个节点
     *
     * @param e 节点的值
     */
    public void add(E e) {
        // 根节点为空
        if (root == null) {
            root = new Node(e, null, null, null);
        } else {
            Node current = root;
            Node parent;
            boolean lessThanLeft;
            do {
                parent = current;
                lessThanLeft = e.compareTo(current.data) < 0 ? true : false;
                // 如果新增节点小于当前节点的左子节点,则以当前节点的左子节点作为当前节点继续搜索
                if (lessThanLeft) {
                    current = parent.left;
                } else {
                    // 如果新增节点不小于当前节点的左子节点,则以当前节点的右子节点作为当前节点继续搜索
                    current = parent.right;
                }
            } while (current != null);
            Node newNode = new Node(e, parent, null, null);
            if (lessThanLeft) {
                parent.left = newNode;
            } else {
                parent.right = newNode;
            }
        }
    }

    /**
     * 根据输入的值删除指定节点
     *
     * @param e 要删除的节点的值
     */
    public void remove(E e) {
        // 获取要删除的节点
        Node delNode = getNode(e);
        while (delNode != null) {
            // 如果删除的节点为叶子节点,直接移除即可
            if (delNode.left == null && delNode.right == null) {
                if (delNode == root) {
                    root = null;
                } else {
                    // 如果被删除节点是左子节点,移除其父节点的左子节点,否则移除父节点的右子节点
                    if (delNode == delNode.parent.left) {
                        delNode.parent.left = null;
                    } else {
                        delNode.parent.right = null;
                    }
                    delNode.parent = null;
                }
                delNode = null;
            } else if (delNode.left != null && delNode.right == null) {
                // 被删除节点只有左子节点
                if (delNode == root) {
                    // 如果是根节点,让左子节点作为根节点
                    root = delNode.left;
                    root.parent = null;
                    delNode = null;
                } else {
                    delNode.left.parent = delNode.parent;
                    delNode.parent.left = delNode.left;
                    delNode = null;
                }
            } else if (delNode.left == null && delNode.right != null) {
                // 被删除节点只有右子节点
                if (delNode == root) {
                    // 如果是根节点,让右子节点作为根节点
                    root = delNode.right;
                    root.parent = null;
                    delNode.right = null;
                    delNode = null;
                } else {
                    delNode.right.parent = delNode.parent;
                    delNode.parent.right = delNode.right;
                    delNode = null;
                }
            } else {
                // 被删除节点的左、右子节点都存在
                // 找到被删除节点的左子树的最大节点,也就是左子树中最右边的节点
                Node leftMaxNode = delNode.left;
                while (leftMaxNode.right != null) {
                    leftMaxNode = leftMaxNode.right;
                }
                delNode.data = leftMaxNode.data;
                delNode = leftMaxNode;
            }
        }
    }

    /**
     * 查找指定值的节点
     *
     * @param e 节点的值
     * @return 符合条件的节点或null
     */
    public Node getNode(E e) {
        Node p = root;
        int cmp;
        while (p != null) {
            cmp = e.compareTo(p.data);
            if (cmp < 0) {
                p = p.left;
            } else if (cmp > 0) {
                p = p.right;
            } else {
                return p;
            }
        }
        return null;
    }

    // 以中序遍历的方式输出树的每个节点的值
    public void print() {
        List<Node> nodes = new ArrayList<Node>();
        if (root != null) {
            inorderTraversal(root, nodes);
        }
        System.out.println(nodes);
    }

    // 中序遍历
    private List<Node> inorderTraversal(Node node, List<Node> nodes) {
        if (node.left != null) {
            inorderTraversal(node.left, nodes);
        }
        nodes.add(node);
        if (node.right != null) {
            inorderTraversal(node.right, nodes);
        }
        return nodes;
    }

    /**
     * 节点类
     */
    private static class Node {
        private Object data;
        private Node parent;
        private Node left;
        private Node right;

        public Node(Object data) {
            this.data = data;
        }

        public Node(Object data, Node parent, Node left, Node right) {
            this.data = data;
            this.parent = parent;
            this.left = left;
            this.right = right;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (this.getClass() == obj.getClass()) {
                Node target = (Node) obj;
                return data.equals(target.data) && parent == target.parent && left == target.left && right == target.right;
            }
            return false;
        }

        @Override
        public String toString() {
            return data.toString();
        }
    }

    public static void main(String[] args) {
        BinarySearchTree<Integer> tree = new BinarySearchTree<Integer>();
        Integer[] integers = {18, 5, 4, 66, 32, 78};
        for (Integer i :
                integers) {
            tree.add(i);
        }
        tree.print();
        for (Integer i :
                integers) {
            tree.remove(i);
            tree.print();
        }
    }
}
复制代码

参考资料:

二叉搜索树-维基

《疯狂java:突破程序员基本功的16课》

おすすめ

転載: juejin.im/post/7031351535162359838