二分搜索树

学习目标:

掌握二分搜索树

学习内容:

1、 树结构 2、 二分搜索树的基础 3、 二分搜索树的操作 4、 二分搜索树的遍历

学习时间:

2021年9月12日

学习产出:

1、 技术笔记 1 遍 2、CSDN 技术博客 1 篇

树结构

树结构

为什么要有树结构

树结构本身是一种天然的组织结构,如果用树结构来存储数据,由它的关系可以是查找更加高效,数据也可以更有逻辑

二分搜索树的基础

二分搜索树的结点定义

class Node {
        T val;//结点的值
        Node left;//左孩子
        Node right;//右孩子

        public Node(T val) {//二叉树的构造方法
            this.val = val;
            this.left = this.right = null;
        }
    }

​ 二分搜索树和链表一样都是动态的数据结构

二分搜索树

结点特性

​ 二叉树只有一个根结点

​ 二叉树的结点最多有两个孩子

​ 二叉树的结点最多有一个父亲

![[结点特性]](https://img-blog.csdnimg.cn/d62b14310b9d4030b6010e9b349d2cd9.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6YeR5riF5rO9,size_20,color_FFFFFF,t_70,g_se,x_16)

二叉树的重要特性

二叉树具有天然的递归结构

每个结点的左子树也是二叉树

每个结点的右子树也是二叉树

二叉树的结构

满二叉树

叶子结点只出现在最底层,除了叶子结点外,其余结点都有两个孩子结点

一个层数为k 的满二叉树总结点数为:2^k-1

第i层上的结点个数为2^(i-1)

一个层数为k的满二叉树的叶子结点个数为2^(k-1)

满二叉树

二叉树不一定是满的

二叉树不一定是满的

二分搜索树

二分搜索树是二叉树

二分搜索树每个的结点值:

​ 大于其左子树的所有结点的值

​ 小于其右子树的所有结点的值

每一棵子树也都是二分搜索树

其所存储的元素也都具有可比较性

二分搜索树的操作

构建根结点

    // 定义根结点
    private Node root;
    int size;

    public BinarySearch() {
        this.root = null;
        this.size = 0;
    }

	//判断树是否为空,使用根结点判断
    public boolean isEmpty() {
        return this.root == null;
    }

添加元素

    // 向树中添加结点
    public void add(T ele) {
        // 使用递归的方式添加结点
        root = add(root, ele);
        this.size += 1;
    }

	//递归操作
    private Node add(Node node, T ele) {
        // 递归到底的情况
        if (node == null) {
            Node node1 = new Node(ele);
            return node1;
        }
        // 递归操作
        if (ele.compareTo(root.val) > 0) {
            node.right = add(node.right, ele);
        } else {
            node.left = add(node.left, ele);
        }
        return node;
    }

从树中查找结点

    // 从树中查找结点
    public Node contains(T ele) {
        return contains(root, ele);
    }

	//递归操作
    private Node contains(Node node, T ele) {
        // 递归到底的情况
        if (node == null) {
            return null;
        }
        // 递归操作
        T val = node.val;
        if (ele.compareTo(val) == 0) {
            return node;
        } else if (ele.compareTo(val) > 0) {
            return contains(node.right, ele);
        } else {
            return contains(node.left, ele);
        }
    }

查找最小结点

不适用递归的做法一直往左子树找

    // 查找最小结点
    public T findMixNode() {
        if (root == null) {
            return null;
        }
        Node curNode = root;
        while (curNode.left != null) {
            curNode = curNode.left;
        }
        return curNode.val;
    }

使用递归的做法

    public T findMixNodeDG() {
        if (root == null) {
            return null;
        }
        return findMixNodeDG(root).val;
    }

    private Node findMixNodeDG(Node root) {
        // 递归到底的情况
        if (root.left == null) {
            return root;
        }
        // 递归操作
        return findMixNodeDG(root.left);
    }

删除最小结点

    // 删除最小结点
    private void removeMixNode() {
        //先找到最小结点
        T mixNodeVal = findMixNodeDG();
        if (mixNodeVal == null) {
            System.out.println("the tree is empty");
            return;
        }
        System.out.println("Find mixNode value is " + mixNodeVal.toString());
        // 进行删除操作
        root = removeMixNode(root);
        this.size -= 1;
    }

    private Node removeMixNode(Node node) {
        if (node.left == null) {
            // 进行删除操作
            Node rightNode = node.right;
            node.right = null;
            return rightNode;
        }
        node.left = removeMixNode(node.left);
        return node;
    }

删除任意结点

删除结点需要考虑条件:

​ 1、删除只有右节点的情况

​ 2、删除只有左节点的情况

​ 3、左右孩子都不为空的情况

第三种情况下,找到需要删除结点的右子树中的最小结点,与删除结点替换

    // 删除任意结点
    public T removeNode(T val) {
        if (root == null) {
            System.out.println("The tree is null");
            return null;
        }
        // 查找结点
        Node node = findNodeDG(root, val);
        if (node != null) {
            //删除操作
            root = removeNode(root, val);
            this.size -= 1;
            return node.val;
        }
        return null;
    }

    /**
     * 从以root为根的二分搜索树中删除值为val的结点
     *
     * @param node 根结点
     * @param val  值
     * @return 删除之后二分搜索树的根结点
     */
    private Node removeNode(Node node, T val) {
        // 递归到底的情况,找到了删除的结点
        if (node.val.compareTo(val) == 0) {
            if (node.left == null) {
                Node rightNode = node.right;
                node.right = null;
                return rightNode;
            } else if (node.right == null) {
                Node leftNode = node.left;
                node.left = null;
                return leftNode;
            } else {
                // 左右都不为空
                // 1、找node的后继(node.right中的最小结点)
                Node s = findMixNodeDG(node.right);
                // 2、从node.right中删除最小结点
                Node rightRoot = removeMixNode(node.right);
                // 3、使用后继结点替换node
                s.left = node.left;
                s.right = rightRoot;
                // 4、生成新树,新树的根结点就是后继结点,返回新树的根结点
                node.left = node.right = null;
                return s;
            }
        }
        // 递归操作
        if (node.val.compareTo(val) > 0) {
            node.left = removeNode(node.left, val);
        } else {
            node.right = removeNode(node.right, val);
        }
        return node;
    }

二分搜索树的遍历

遍历操作:把所有结点都访问一遍

前序遍历

先输出根结点,再遍历左子树,再遍历右子树

    // 前序遍历
    public List<T> preOrder() {
        List<T> result = new ArrayList<>();
        preOrder(root, result);
        return result;
    }

    private void preOrder(Node root, List<T> result) {
        // 递归到底的情况
        if (root == null) {
            return;
        }
        // 先获取中间值,再遍历左树,然后遍历右树
        result.add(root.val);
        preOrder(root.left, result);
        preOrder(root.right, result);
    }

中序遍历

先遍历左子树,输出中间值,再遍历右子树

    // 中序遍历
    public List<T> middleOrder() {
        List<T> result = new ArrayList<>();
        middleOrder(root, result);
        return result;
    }

    private void middleOrder(Node root, List<T> result) {
        // 递归到底的情况
        if (root == null) {
            return;
        }
        // 先遍历左树,在获取中间值,然后遍历右树
        middleOrder(root.left, result);
        result.add(root.val);
        middleOrder(root.right, result);
    }

后序遍历

先遍历左子树,再遍历右子树,最后输出中间值

    // 后序遍历
    public List<T> subOrder() {
        List<T> result = new ArrayList<>();
        subOrder(root, result);
        return result;
    }

    private void subOrder(Node root, List<T> result) {
        // 递归到底的情况
        if (root == null) {
            return;
        }
        // 先遍历左树,再遍历右树,然后获取中间值
        subOrder(root.left, result);
        subOrder(root.right, result);
        result.add(root.val);
    }

层序遍历(广度优先)

层序遍历

将最后的输出结构用List集合存储

创建一个队列放入根结点,每次操作都是取出队首放入集合(也就是在这树中我们需要前序输出的结点)

如果这个结点的孩子不为空的话,就以从左到右的形式放入队列

一直循环到队列中没有元素结束

    // 层序编列(广度优先)
    public List<T> layerOrder() {
        List<T> list = new ArrayList<>();
        if (root != null) {
            Queue<Node> queue = new LinkedList<>();
            queue.add(root);
            while (!queue.isEmpty()) {
                // 从队列中取出队首元素
                Node node = queue.poll();
                list.add(node.val);
                if (node.left != null) {
                    queue.offer(node.left);
                }
                if (node.right != null) {
                    queue.offer(node.right);
                }
            }
        }
        return list;
    }

课后巩固

​ 力扣102. 二叉树的层序遍历

​ 力扣108. 将有序数组转换为二叉搜索树

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值