欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

week07_day03_Tree

程序员文章站 2022-05-09 13:50:19
...

二叉查找树(可以根据这棵树查找结点):
又叫二叉排序树(中序遍历的结果是有序的)。要求树中的结点可以按照某个规则进行比较。

左子树中所有结点的key比根结点的key小,并且左子树也是二叉搜索树。
右子树中所有结点的key比根结点的key大,并且右子树也是二叉搜索树。

  • Q1: BST 能存储null吗?为什么?
    不能,BST要求元素能进行比较

  • Q2: BST 能存储key相同的对象吗?为什么?如果不能,可以改进吗?
    按照BST的定义是不能存储key相同的对象的。
    如何改进?
    #1:可以在结点添加一个count属性。
    但是我需要存储的是Student对象,这个Student对象包含一个学生的所有信息。而不是有几个15岁的同学
    #2: 拉链法, 在结点添加一个next指针域。
    next执行和key相同的下一个元素。可以看成这个二叉排序树存储的是value值为key的链表开发中大多用拉链法
    #3:修改BST的定义: 左子树 <= (右子树 >=)

实现:
查找:先取根结点,如果它等于我们要查找的数就返回;如果查找的数据比根节点小,就在左子树中递归查找;如果要查找的数据比根结点大,那么就在右子树中递归查找。

插入:如果要插入的数据比结点大,并且结点的右子树为空,就将新数据直接插到右孩子的位置;如果不为空,就再递归遍历右子树,查找插入位置。同理,如果要插入的数据比结点小,并且结点的左子树为空,就将新数据插入到左孩子的位置;如果不为空,就再递归遍历左子树,查找插入位置。

删除:分三种情况处理
a. 如果要删除结点没有孩子,那么直接将该结点删除就行了。
b. 如果要删除结点只有一个孩子,那么需要就父亲结点对应的指针,指向孩子结点。
c. 如果要删除结点有两个孩子,那么我们可以找到这个结点的右子树中最小结点 (或者
左子树中最大结点),把它替换到要删除的结点上,然后再删除掉这个最小结点。

Q:查找,插入和删除操作的时间复杂度分别是多少?O(h)

Q:普通的BST最终都会倾斜,我们能否对其改进,实现自平衡的BST?

自平衡的二叉搜索树:
avl (任意一个结点,左子树和右子树的高度之差不超过1)
红黑树 (树的高度是log(n))

·························································································································································································

package com.cskaoyan.tree;

import sun.reflect.generics.tree.Tree;

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

/**
 * @author shihao
 * @create 2020-05-20 10:33
 * <p>
 * API:
 * 增:boolean add(E e) 添加成功返回true,否则返回false(二叉排序树中有和e相同的值)
 * 删:boolean remove(E e) 删除成功返回true,否则返回false(二叉排序树中无和e相同的值)
 * void clear();  清空树中所有元素
 * 查:boolean contains(E e)
 * E min()
 * E max()  求最大值和最小值
 * 遍历:List<E> preOrder();
 * List<E> inOrder();
 * List<E> postOrder();
 * List<E> levelOrder();
 * 建树:静态方法,因为建树操作不依赖于BinarySearchTree对象而存在
 * static <T> BinarySearchTree buildTree(List<T> preOrder,List<T> inOrder)
 * 获取属性:
 * boolean isEmpty()  判空
 * int size()   结点个数
 * int height()  树的高度(二叉排序树的性能是依赖于二叉排序树的高度的)
 */
//我们希望BinarySearchTree结点可以进行比较,在java中除了基本数据类型外,
// 要想实现比较,得实现Comparable接口,而Comparable接口本身就是泛型接口,
// 所以得Comparable<E>,如果不写<E>的话,我们认为它可以对任意元素(Object)进行比较的
//public class BinarySearchTree<E extends Comparable<E>>
//第一个<E>定义泛型,第二个<E>使用泛型。但这样的话只能对E类型的元素进行比较
//但这样写还不够好
// public class BinarySearchTree<E extends Comparable<? super E>>
// Comparable<? super E>表示能对E的父类进行比较
// 那能对E的父类进行比较,就一定能对E进行比较
public class BinarySearchTree<E extends Comparable<E>> {

    //属性
    private TreeNode root;
    private int size;

    private class TreeNode {
        TreeNode left;
        E value;
        TreeNode right;

        public TreeNode(E value) {
            this.value = value;
        }

        public TreeNode(TreeNode left, E value, TreeNode right) {
            this.left = left;
            this.value = value;
            this.right = right;
        }
    }

    //构造方法
    //刚开始创建一颗空树,size = 0, root = null就可以不用写构造方法

    //方法

    /**
     * 在BST中插入一个元素
     *
     * @param key 待插入的元素
     * @return 如果插入成功返回true,失败返回false
     */
/*
    public boolean add(E key) {
        if (key == null) {
            throw new IllegalArgumentException("key cannot be null");
        }
        //处理空树情况
        if (root == null) {
            root = new TreeNode(key);
            size++;
            return true;
        }
        TreeNode p = null;
        TreeNode x = root;
        int cmp;
        do {
            cmp = key.compareTo(x.value);
            if (cmp < 0) {
                //往左走
                p = x;
                x = x.left;
            } else if (cmp > 0) {
                //往右走
                p = x;
                x = x.right;
            } else return false;
        } while (x != null);
        //插入结点
        TreeNode node = new TreeNode(key);
        if (cmp > 0) p.right = node;
        else p.left = node;
        size++;
        return true;
    }
*/
    //add方法的递归实现
    public boolean add(E key) {
        if (key == null) {
            throw new IllegalArgumentException("key cannot be null");
        }
        int oldSize = size;
        // 在root这棵树中插入key,并把新的根节点赋值给root
        //不能直接调用add(E key)这个方法,这个方法不能把问题规模给减小
        // 也就是说,我只知道要插入key,不知道在哪个位置插入key
        root = add(root, key);
        return oldSize < size;
    }

    private TreeNode add(TreeNode node, E key) {
        //边界条件
        //node == null 说明这就是要插入的位置
        if (node == null) {
            size++;
            return new TreeNode(key);
        }
        int cmp = key.compareTo(node.value);
        if (cmp < 0) {
            //在左子树中插入key,并链接到node.left
            node.left = add(node.left,key);
        } else if(cmp>0){
            //在右子树中插入key,并链接到node.right
            node.right = add(node.right,key);
        }
        //如果触cmp == 0,就什么也不做
        return node;
    }

    /**
     * 获取BST中元素的个数
     *
     * @return 元素的个数
     */
    public int size() {
        return size;
    }

    /**
     * BST先序遍历
     *
     * @return 先序遍历列表
     */
    public List<E> preOrder() {
        List<E> list = new ArrayList<>();
        //遍历BST,并把元素放入list中
        preOrder(root, list);
        return list;
    }

    private void preOrder(TreeNode node, List<E> list) {
        //边界条件 node == null表示遍历到了叶子结点 直接返回即可
        if (node == null) return;
        //遍历根节点
        list.add(node.value);
        //遍历左子树
        preOrder(node.left, list);
        //遍历右子树
        preOrder(node.right, list);
    }

    /**
     * BST中序遍历
     *
     * @return 中序遍历列表
     */
    public List<E> inOrder() {
        ArrayList<E> list = new ArrayList<>();
        //遍历BST,并把元素放入list中
        inOrder(root, list);
        return list;
    }

    private void inOrder(TreeNode node, ArrayList<E> list) {
        if (node == null) return;
        //遍历左子树
        inOrder(node.left, list);
        //遍历根节点
        list.add(node.value);
        //遍历右子树
        inOrder(node.right, list);
    }

    /**
     * BST后序遍历
     *
     * @return 后序遍历列表
     */
    public List<E> postOrder() {
        ArrayList<E> list = new ArrayList<>();
        postOrder(root, list);
        return list;
    }

    private void postOrder(TreeNode node, ArrayList<E> list) {
        //边界条件
        if (node == null) return;
        //遍历左子树
        postOrder(node.left, list);
        //遍历右子树
        postOrder(node.right, list);
        //遍历根节点
        list.add(node.value);
    }

    public static void main(String[] args) {
        BinarySearchTree<Character> tree = new BinarySearchTree<>();
        System.out.println(tree.add('C'));
        System.out.println(tree.add('A'));
        System.out.println(tree.add('D'));
        System.out.println(tree.add('B'));
        System.out.println(tree.add('E'));
        //System.out.println(tree.add('E'));
        System.out.println(tree.size());
        System.out.println(tree.preOrder()); //[C, A, B, D, E]
        System.out.println(tree.inOrder()); // [A, B, C, D, E]
        System.out.println(tree.postOrder());//[B, A, E, D, C]
    }

}

关于这段代码中的增加结点的非递归遍历:
week07_day03_Tree

·························································································································································································

作业:

  1. LeetCode之翻转二叉树
  2. 递归实现BST的remove(E e)方法(选做)。

推荐阅读