数据结构之二叉搜索树—Java实现

二叉查找树(Binary Search Tree,简称BST)是一棵二叉树,它的左子节点的值比父节点的值要小,右节点的值要比父节点的值大。它的高度决定了它的查找效率。

在理想的情况下,二叉查找树增删查改的时间复杂度为O(logN)(其中N为节点数),最坏的情况下为O(N)。当它的高度为logN+1时,我们就说二叉查找树是平衡的。

首先定义二叉树节点
class TreeNode {
    TreeNode(int value) {
        this.value = value;
    }

    TreeNode left;
    TreeNode right;
    int value;
}
复制代码

本文中rootNode为创建二叉搜索树的root节点,在类初始化阶段赋值。

private final TreeNode rootNode;

public BinarySearchTree(int value) {
    rootNode = new TreeNode(value);
}
复制代码

插入操作:

首先从root节点开始循环遍历,

  • 如果插入节点等于当前root节点,直接结束不做任何处理。
  • 如果插入节点小于当前root节点,从左节点开始查找。 (root=root.left)
  • 如果插入节点大于当前root节点,从右节点开始查找。 (root=root.right)
  • 当当前节点的左节点或者右节点为空时,插入新节点,结束过程。

上代码:

public TreeNode insert(int value) {
    TreeNode currentRoot = rootNode;

    TreeNode newNode = new TreeNode(value);
    while (true) {
        if (value == currentRoot.value) {
            return null;
        }
        else if (value < currentRoot.value) {
            if (currentRoot.left != null) {
                currentRoot = currentRoot.left;
            }
            else {
                return currentRoot.left = newNode;
            }
        }
        else {
            if (currentRoot.right != null) {
                currentRoot = currentRoot.right;
            }
            else {
                return currentRoot.right = newNode;
            }
        }
    }
}
复制代码

查找操作:

类似于插入操作:

  • 如果插入节点等于root节点,查找完成
  • 如果插入节点小于root节点,从left节点开始查找。 (root=root.left)
  • 如果插入节点大于root节点,从right节点开始查找。(root=root.right)

上代码:

public TreeNode get(int value) {
    TreeNode currentRoot = rootNode;

    while (true) {
        if (currentRoot == null) {
            return null;
        }

        if (value == currentRoot.value) {
            return currentRoot;
        }
        else if (value < currentRoot.value) {
            currentRoot = currentRoot.left;
        }
        else {
            currentRoot = currentRoot.right;
        }
    }
}
复制代码

遍历操作:

遍历分为

  • 前序:根节点放在左节点的左边        (→左→右)
  • 中序:根节点放在左节点和右节点的中间(左→→右)
  • 后序:根节点放在右节点的右边        (左→右→

中序遍历的代码:

public void getTreeByInOrder(List<TreeNode> list, TreeNode root) {
    if (root != null) {
        getTreeByInOrder(list, root.left);
        list.add(root);
        getTreeByInOrder(list, root.right);
    }
}
复制代码

前序后序和其类似,就不贴代码了

层次遍历(也可以叫做广度优先遍历):

思路:

  1. 由根从上往下循环遍历,利用队列(queue)先进先出的原则,每访问一个节点之后,将其左右子节点入队。这样,同一层的所有节点肯定先于下一层的节点先访问

  2. 如果需要分层保存节点呢?(比如需要换行打印):问题的难点是如何知道本层已经遍历完了,可以使用两个指针last和nextLast,一个指向当前层的最后一个节点,一个指向下一层的最后一个节点,这样难点转换成了last和nextLast的更新。当节点入队时,更新nextLast(设置nextLast为队列的最后一个节点),因为队列中的最后一个节点一定是下一层的最后节点。

上代码:

public List<List<Integer>> printTreeByHierarchy() {
    TreeNode currentRoot = rootNode;

    Queue<TreeNode> queue = new LinkedList<>();

    TreeNode last = currentRoot;
    TreeNode nextLast = null;

    queue.offer(currentRoot);

    List<List<Integer>> lists = new ArrayList<>();
    List<Integer> nodes = new ArrayList<>();
    while (!queue.isEmpty()) {
        currentRoot = queue.poll();

        nodes.add(currentRoot.value);

        if (currentRoot.left != null) {
            queue.offer(currentRoot.left);
            nextLast = currentRoot.left;
        }

        if (currentRoot.right != null) {
            queue.offer(currentRoot.right);
            nextLast = currentRoot.right;
        }

        if (last == currentRoot) {//因为last是当前层的最后一个节点,如果等式成立,说明这层已经遍历完
            last = nextLast;

            lists.add(nodes);
            nodes = new ArrayList<>();
        }
    }

    System.out.println(lists);
    return lists;
}
    
复制代码

删除操作:

删除操作比较麻烦,需要分4种情况考虑。我们以上面这颗树作为参考:

  1. 如果删除节点含有左右子节点:需要用删除节点的后继节点(以中序遍历)取代删除节点的位置。----比如节点删除20,需要用26代替20的位置
  2. 如果删除节点只含有单个节点(或左或右):需要用删除节点的左(右)节点取代删除节点的位置。----比如节点删除27,需要用26代替27的位置
  3. 如果删除节点无子节点(是叶子节点):直接删除。

上代码:

public void remove(int value) {
    TreeNode currentRoot = rootNode;

    while (true) {
        if (currentRoot == null) {
            return;//没找到,返回或者抛异常
        }

        if (value < currentRoot.value) {
            currentRoot = currentRoot.left;
        }
        else if (value > currentRoot.value) {
            currentRoot = currentRoot.right;
        }
        else {
            TreeNode replacement;//代替者
            if (currentRoot.left == null && currentRoot.right == null) {//无双子节点
                resetParentNode(currentRoot, null);
            }
            else if (currentRoot.left != null && currentRoot.right != null) {//双子节点
                replacement = getSuccessor(currentRoot);

                replacement.left = currentRoot.left;
                replacement.right = currentRoot.right;

                resetParentNode(replacement, null);       //重设后继节点的父节点
                resetParentNode(currentRoot, replacement);//重设正在移除节点的父节点
            }
            else if (currentRoot.left != null) {
                resetParentNode(currentRoot, currentRoot.left);  //
            }                                                    //
            else {                                               //单节点
                resetParentNode(currentRoot, currentRoot.right); //
            }                                                    //

            return;
        }
    }
}
复制代码

所有代码:BinarySearchTree.java

如果文章中有分析错误的地方或者值得改进的地方,欢迎大家指出。

参考文章:

美团技术博客---红黑树深入剖析及Java实现

转载于:https://juejin.im/post/5c80db06e51d453bbb04ed6a

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值