数据结构-二叉搜索树的增删与遍历

1、二叉搜索树概念:

二叉搜索树是一种节点值之间具有一定数量级次序的二叉树,对于树中每个节点:

  • 若其左子树存在,则其左子树中每个节点的值都不大于该节点值;
  • 若其右子树存在,则其右子树中每个节点的值都不小于该节点值。

性能分析

查询复杂度、构造复杂度和删除复杂度三种操作的时间复杂度皆为

的存在是应为二叉树有可能出现线性结构:

下面分析线性结构的三种操作复杂度,以二分法为例:

  • 查询复杂度,时间复杂度为 ,优于二叉搜索树;
  • 元素的插入操作包括两个步骤,查询和插入。查询的复杂度已知,插入后调整元素位置的复杂度为 ,即单个元素的构造复杂度为:
  • 删除操作也包括两个步骤,查询和删除,查询的复杂度已知,删除后调整元素位置的复杂度为 ,即单个元素的删除复杂度为:

由此可知,二叉搜索树相对于线性结构,在构造复杂度和删除复杂度方面占优;在查询复杂度方面,二叉搜索树可能存在类似于斜树,每层上只有一个节点的情况,该情况下查询复杂度不占优势。

2、二叉搜索树实现

2.1 二叉树节点构造

package com.suirui.binarytree;

/**
 * Created by zongx on 2020/1/10.
 */
public class TreeNode {
    public int data;
    public TreeNode right;
    public TreeNode left;

    public TreeNode() {
    }

    public TreeNode(int data) {
        this.data = data;
    }

    public TreeNode(int data, TreeNode left, TreeNode right) {
        this.data = data;
        this.right = right;
        this.left = left;
    }

    @Override
    public String toString() {
        return "当前节点值: " + this.data;
    }
}

2.2 创建搜索二叉树

    /**
     * @Description: 往二叉树中插入数值
     * @Author: zongx
     * @Date: 2020/1/10
     * @Param: value
     * @return void
    */
    public void add(int value) {
        //构建节点
        TreeNode node = new TreeNode(value);
        //判断是否有根节点,没有将当前值节点定义为根节点
        if (root == null) {
            root = node;
            return;
        }
        //需要比较的节点
        TreeNode current = new TreeNode();

        //从根节点开始比较
        current = root;
        while (true) {
            //要插入节点大于或等于当前节点,则判断右子树
            if (value >= current.data) {
                if (current.right == null) {
                    current.right = node;
                    return;
                }
                current = current.right;
            }
            //要插入节点小于当前节点,则判断左子树
            if (value < current.data) {
                if (current.left == null) {
                    current.left = node;
                    return;
                }
                current = current.left;
            }
        }
    }

2.3 查找节点

    /**
     * @Description: 通过key值查找节点
     * @Author: zongx
     * @Date: 2020/1/13
     * @Param: key
     * @return com.suirui.binarytree.TreeNode
    */
    public TreeNode find(int key) {
        if (root == null) {
            return null;
        }
        TreeNode current = root;
        while (current.data != key) {
            if(key < current.data ) {
                current = current.left;
            }else{
                current = current.right;
            }
            if (current == null) {
                return null;
            }
        }
        return current;
    }

2.4 前中后三种遍历方式

  • 先序遍历:访问根节点、先序遍历左子树、先序遍历右子树。
  • 中序遍历:中序遍历左子树、访问根节点、中序遍历右子树。
  • 后序遍历:后续遍历左子树、后续遍历右子树、访问根节点。(代码实现方式使用了倒推的方式,值得注意)
  /**
     * @Description: 递归实现前序遍历--中左右
     * @Author: zongx
     * @Date: 2020/1/10
     * @Param: root
     * @return void
     */
    public void preOrderRecursion(TreeNode root) {
        if (root != null) {
            System.out.print(root.data + " ,");
            preOrderRecursion(root.left);
            preOrderRecursion(root.right);
        }
    }

    /**
     * @Description: 前序遍历--中左右
     * @Author: zongx
     * @Date: 2020/1/10
     * @Param: root
     * @return void
     */
    public void preOrder(TreeNode root) {
        if (root == null) {
            return;
        }
        LinkedList<TreeNode> stack = new LinkedList<>();
        while (root != null || !stack.isEmpty()) {
            if(root != null) {
                //只要root不为null,就代表是在前序遍历的路径上(都是根节点)
                System.out.print(root.data + " ,");
                stack.push(root);
                root = root.left;
            }else {
                //此时不需要找最左侧节点,所以直接抛出(压栈的时候已经输出信息了),然后前序遍历右子树
                root = stack.pop();
                root =root.right;
            }
        }
    }

    /**
     * @Description: 递归实现中序遍历--左中右
     * @Author: zongx
     * @Date: 2020/1/10
     * @Param: root
     * @return void
    */
    public void inOrderRecursion(TreeNode root) {
        if (root != null) {
            inOrderRecursion(root.left);
            System.out.print(root.data + " ,");
            inOrderRecursion(root.right);
        }
    }

    /**
     * @Description: 中序遍历--左中右
     * @Author: zongx
     * @Date: 2020/1/10
     * @Param: root
     * @return void
    */
    public void inOrder(TreeNode root) {
        //使用栈的特性,新进后出
        LinkedList<TreeNode> stack = new LinkedList<>();

        if (root == null){
            return;
        }

        //使用栈的特性,新进后出,进行遍历
        while (root != null || !stack.isEmpty()) {
            if(root != null) {
                //如果节点不为null,则存入栈中
                stack.push(root);
                root = root.left;
            } else {
                //如果节点为null,则代表当前已没有更左侧的节点
                //此时栈顶节点,便是应该输出的节点
                //输出完栈顶节点,应该再找该节点的右子树的最左侧节点
                root = stack.pop();
                System.out.print(root.data + " ,");
                root = root.right;
            }
        }
    }

    /**
     * @Description: 递归实现后序遍历--左右中
     * @Author: zongx
     * @Date: 2020/1/10
     * @Param: root
     * @return void
     */
    public void postOrderRecursion(TreeNode root) {
        if (root != null) {
            postOrderRecursion(root.left);
            postOrderRecursion(root.right);
            System.out.print(root.data + " ,");
        }
    }

    /**
     * @Description: 实现后序遍历--左右中
     * @Author: zongx
     * @Date: 2020/1/10
     * @Param: root
     * @return void
     */
    public void postOrder(TreeNode root) {

        if(root == null) {
            return;
        }

        //利用栈进行树遍历
        LinkedList<TreeNode> stack = new LinkedList<TreeNode>();
        //获取后序遍历的逆顺序
        LinkedList<TreeNode> resultInverseStack = new LinkedList<TreeNode>();

        //精髓在逆序找后序遍历路径
        while (root != null || !stack.isEmpty()) {
            if(root != null) {
                stack.push(root);
                resultInverseStack.push(root);
                root = root.right;
            } else {
                root = stack.pop();
                root = root.left;
            }
        }

        for (TreeNode node : resultInverseStack) {
            System.out.print(node.data + " ,");
        }
    }

    /**
     * @Description: 层次遍历,核心思想使用队列保持层的顺序
     * @Author: zongx
     * @Date: 2020/1/10
     * @Param: root
     * @return void
    */
    public void levelOrder(TreeNode root) {
        if (root == null) {
            return;
        }
        LinkedList<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        while (!queue.isEmpty()) {
            root = queue.pop();
            System.out.print(root.data + " ,");
            if (root.left != null) {
                queue.add(root.left);
            }
            if (root.right != null) {
                queue.add(root.right);
            }
        }
    }

2.5 删除节点

分为三种情况:

  • 要删除节点有零个孩子,即叶子节点,直接删除

  • 要删除节点有一个孩子,只需要将parent.left(或者是parent.right)设置为curren.right(或者是current.left)即可

 

  • 要删除节点有两个孩子,此时需要注意后继节点的问题,代码中详细介绍
  • 1.后继节点为待删除节点的右子

  • 2.后继节点为待删除结点的右孩子的左子树

public boolean del(int key) {
        if (root == null) {
            return false;
        }
        //首先找到要删除节点和其父节点,以及要删除节点是其父节点的右节点还是左节点
        TreeNode current = root;
        TreeNode parent = null;
        Boolean isRightChild = null;
        while (current.data != key) {
            parent = current;
            if(key < current.data ) {
                current = current.left;
                isRightChild = false;
            } else {
                current = current.right;
                isRightChild = true;
            }
            if (current == null) {
                return false;
            }

        }
        // 此时current就是要删除的结点,parent为其父结点

        //判断三种情况
        //1、要删除节点,没有子节点
        if(current.right == null && current.left == null) {
            if (current == root) {
                //为根节点,清空树
                root = null;
            }else {
                if (isRightChild) {
                    //删除节点是其父节点的右节点,则置空父节点的右节点
                    parent.right = null;
                }else {
                    //删除节点是其父节点的左节点,则置空父节点的左节点
                    parent.left = null;
                }
            }
            return true;
        }
        //2、删除的节点,有一个子节点
        if (current.left == null) {
            //左子树为空,则肯定有一个右节点
            if (current == root) {
                root = current.right;
            }else {
                if (isRightChild) {
                    //删除节点是其父节点的右节点,则将其父节点的右节点设为删除节点的右节点
                    parent.right = current.right;
                }else {
                    //删除节点是其父节点的左节点,则将其父节点的左节点设为删除节点的右节点
                    parent.left = current.right;
                }
            }
            return true;
        }
        if (current.right == null) {
            //右子树为空,则肯定有一个左节点
            if (current == root) {
                root = current.left;
            }else {
                if (isRightChild) {
                    //删除节点是其父节点的右节点,则将其父节点的右节点设为删除节点的左节点
                    parent.right = current.left;
                }else {
                    //删除节点是其父节点的左节点,则将其父节点的左节点设为删除节点的左节点
                    parent.left = current.left;
                }
            }
            return true;
        }
        //3、有两个节点,此时需要确认:后继节点
        //后继节点的概念,如果将一棵二叉树按照中序周游的方式输出,则任一节点的下一个节点就是该节点的后继节点。
        TreeNode successor=delHelperGetSuccessor(current);
        //右子树为空,则肯定有一个左节点
        if (current == root) {
            root = successor;
        }else {
            if (isRightChild) {
                //删除节点是其父节点的右节点,则将其父节点的右节点设为删除节点的左节点
                parent.right = successor;
            }else {
                //删除节点是其父节点的左节点,则将其父节点的左节点设为删除节点的左节点
                parent.left = successor;
            }
        }
        return true;
    }

    /**
     * @Description: 确认删除节点的后继节点(中序遍历的下一个),并处理完成后继节点的结构
     * @Author: zongx
     * @Date: 2020/1/13
     * @Param: current
     * @return com.suirui.binarytree.TreeNode
    */
    private TreeNode delHelperGetSuccessor(TreeNode delNode) {
        //记录后继者的父节点,初始是要删除节点
        TreeNode successorParent = delNode;
        //记录后继节点,初始是父节点的右子树的根节点
        TreeNode successor = delNode.right;

        //因为是中序遍历,其实就是找右子树的最左端元素
        TreeNode current = delNode.right;
        while (current != null) {
            successorParent = successor;
            successor = current;
            current = current.left;
        }
        //此时的successor就是后继节点

        //下面根据情况构建后继节点的结构
        //判断要删除节点的删除节点右子树是否有左子树---这个依据是中序遍历的顺序:先遍历左子树,输出根节点,再遍历右子树。
        // 构建后继节点的右子树
        if (successor != delNode.right) {
            //如果后继节点直接不是右子树根节点,则后继节点肯定在右子树的左子树上,则需要优化树结构
            //1、需要将后继节点的右孩子(后继节点只可能有右孩子),放到后继节点父节点的左孩子上
            successorParent.left = successor.right;
            //2、处理后继节点升级到删除节点位置时,仅当前情况(后继节点在删除节点右子树的左子树上)需要处理
            //将要删除节点的右子树给后继节点的右子树
            successor.right = delNode.right;
        }

        //构建后继节点的左子树
        successor.left = delNode.left;
        return successor;
     }

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值