敲黑板!数据结构精讲之二分搜索树

Posted 东软睿道

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了敲黑板!数据结构精讲之二分搜索树相关的知识,希望对你有一定的参考价值。

童鞋们好!今天,我们来学习面试常考知识点:数据结构之二分搜索树!记得做好笔记哦!


关于二分搜索树

  • 二分搜索树是一颗二叉树,满足二叉树的所有定义。

  • 二分搜索树每个节点左子树值都小于该节点值,每个节点右子树值都大于该节点值。

  • 任意节点每颗子树都满足二分搜索树定义。


二分搜索树如此定义的意义何在?


二分搜索树实际上是做数据整理。因为左右子树的值和根节点关系,我们每次查找元素在根节点进行对比后,每次几乎能排除掉近一半的查找范围,大大加快了查询速度。插入元素时也一样,比如,如果我们知道超市二楼卖水果,就不用在一楼找了,大大加快了购买速度。


为了达到这样的高效,树结构也需要每个节点间具备可比较性。而链表数据结构就没有这类要求,所以有得必有失。


定义二分搜索树类


向上滑动阅览


public class BinarySearchTree<E> {

// 节点内部类

    private static class Node<E>{

        E element; // 当前节点

        Node<E> left; // 左子节点

        Node<E> right;// 右子节点

        Node<E> parent;// 父节点

        public Node(E element,Node<E> parent){

            this.element = element;

            this.parent = parent;

        }

    }

}

添加属性及相应接口;



向上滑动阅览


public class BinarySearchTree<E> {
    private int size;// 记录个数
    private Node<E> root;// 根节点
    // 树的大小
    public int size() {
        return size;
    }
    // 是否为空的方法
    public boolean isEmpty() {
        return size == 0;
    }
    // 添加节点方法
    public void add(E element) {}
    // 节点内部类
    private static class Node<E>{
        E element; // 当前节点
        Node<E> left; // 左子节点
        Node<E> right;// 右子节点
        Node<E> parent;// 父节点
        public Node(E element,Node<E> parent) {
            this.element = element;
            this.parent = parent;
        }
    }
}

因二分搜索树节点不能为null,所以添加判断方法;



向上滑动阅览


public class BinarySearchTree<E> {

    private int size;// 记录个数

    private Node<E> root;// 根节点

    // 树的大小

    public int size() {

        return size;

    }

    // 是否为空的方法

    public boolean isEmpty() {

        return size == 0;

    }

    // 添加节点方法

    public void add(E element) {}

    // 判断节点不能为null

    private void elementCheckNull(E element) {

        if (element == null) {

            throw new IllegalArgumentException("element must not be null");

        }

    }

    // 节点内部类

    private static class Node<E>{

        E element; // 当前节点

        Node<E> left; // 左子节点

        Node<E> right;// 右子节点

        Node<E> parent;// 父节点

        public Node(E element,Node<E> parent) {

            this.element = element;

            this.parent = parent;

        }

    }

}

add方法,添加节点设计。添加步骤如下:

  • 找到父节点

  • 创建新节点 node

  • parent.left = node 或 parent.right = node


相等的值怎么处理?方案:return或覆盖



向上滑动阅览


public class BinarySearchTree<E> {

    ...

    // 添加节点方法

    public void add(E element) {

        // 判断添加的是否为空

        elementCheckNull(element);

        // 添加的是第一个节点

        if (root == null) {

            root = new Node<>(element, null);

            size++;

            return;

        }

        // 添加的不是第一个节点

        // 定义一个父节点

        Node<E> parent = root;

        Node<E> node = root;

        int compare = 0;

        while (node != null) {

            compare = compare(element, node.element);

            parent = node;

            if (compare > 0) {

                node = node.right;

            } else if (compare < 0) {

                node = node.left;

            }else{// 相等,这里我们暂时设计的是return

                return;

            }

        }

        // 判断是左子节点还是右子节点

        Node<E> newNode = new Node<>(element,parent);

        if (compare > 0) {

            parent.right = newNode;

        }else{

            parent.left = newNode;

        }

        size++;

    }

    // 判断节点大小,0代表相等,大于0代表e1较大,小于0代表e2较大

    private int compare(E e1, E e2) {

        return 0;

    }

...

}


现在创建测试类;



向上滑动阅览


public class Main {

    public static void main(String[] args) {

        Integer[] nums = new Integer[]{7, 4, 9, 2, 5, 8, 11, 3};

        BinarySearchTree<Integer> bst = new BinarySearchTree<>();

        for (Integer num : nums) {

            bst.add(num);

        }

    }

}


此时别忘了,compare的方法还没实现;



向上滑动阅览


private int compare(E e1, E e2) {

    return 0;

}


如何实现呢?如果数字还好,可直接通过运算符直接比较;但如果是自定义数据类型呢?例如:比较两个Student类型对象



向上滑动阅览


public class Student {

    private int age;

    public Student(int age) {

        this.age = age;

    }

    public int getAge() {

        return age;

    }

    public void setAge(int age) {

        this.age = age;

    }

}



向上滑动阅览


public class Main {

    public static void main(String[] args) {

        Integer[] nums = new Integer[]{7, 4, 9, 2, 5, 8, 11, 3};

        BinarySearchTree<Integer> bst = new BinarySearchTree<>();

        for (Integer num : nums) {

            bst.add(num);

        }


        BinarySearchTree<Student> bst2 = new BinarySearchTree<>();

        bst2.add(new Student(22));

        bst2.add(new Student(26));

    }

}



第一种处理方式:新建Comparable接口


该接口的意义是:如果想把元素存入二分搜索树,必须实现该接口;


向上滑动阅览


public interface Comparable<E> {

    int compareTo(E e);

}


然后修改BinarySearchTree及compare方法



向上滑动阅览


public class BinarySearchTree<E extends Comparable> {

    private int compare(E e1, E e2) {

      return e1.compareTo(e2);

    }

}


再将Student实现Comparable接口 ,补充重写方法



向上滑动阅览


public class Student implements Comparable<Student> {

    ...

    @Override

    public int compareTo(Student e) {

        return age - e.age;

    }

}


然而,这种设计存在一些“瑕疵”。


比如,在不同情况下,希望比较的规则也对应变换,例如:我们现在希望年龄大一点的去左子树,即年龄大的我们认为它小,正好与上例中的规则相反;则需要第二种处理方式。


第二种处理方式:新建比较器接口


向上滑动阅览


public interface Comparator<E> {

    int compare(E e1, E e2);

}


将这个比较器传进二叉树类里,同时不再必须实现Comparable接口



向上滑动阅览


public class BinarySearchTree<E> {
    private int size;// 记录个数
    private Node<E> root;// 根节点
    private Comparator<E> comparator;

    public BinarySearchTree(Comparator<E> comparator) {
        this.comparator = comparator;
    }

...
  // 判断节点大小
  private int compare(E e1, E e2) {
        return comparator.compare(e1,e2);
    }

}

Main



向上滑动阅览


public class Main {

    private static class StudentComparator implements Comparator<Student> {

        @Override

        public int compare(Student e1, Student e2) {

            return e1.getAge() - e2.getAge();

        }

    }

    private static class StudentComparator2 implements Comparator<Student> {

        @Override

        public int compare(Student e1, Student e2) {

            return e2.getAge() - e1.getAge();

        }

    }



    public static void main(String[] args) {

        BinarySearchTree<Student> bst2 = new BinarySearchTree<>(new StudentComparator());

        bst2.add(new Student(22));

        bst2.add(new Student(26));


        BinarySearchTree<Student> bst3 = new BinarySearchTree<>(new StudentComparator2());

        bst2.add(new Student(22));

        bst2.add(new Student(26));

    }

}


这样,在不同情况下就有了不同的比较方式。但很多时候并不需要这样,此时为了代码保证正确,还必须传一个比较器进去,有些麻烦,较完美的处理方式是兼容以上两种情况。



向上滑动阅览


public class BinarySearchTree<E> {

...

    // 判断节点大小

    private int compare(E e1, E e2) {

        if (comparator != null) {

            return comparator.compare(e1,e2);

        }

        return ((Comparable) e1).compareTo(e2);

    }

...

}


此时我们用的这两个接口,其实Java官网提供了,可直接使用官方的。再比较时,就可以有多种方式了。



向上滑动阅览


public class Main {


    private static class StudentComparator implements Comparator<Student> {

        @Override

        public int compare(Student e1, Student e2) {

            return e1.getAge() - e2.getAge();

        }

    }


    private static class StudentComparator2 implements Comparator<Student> {

        @Override

        public int compare(Student e1, Student e2) {

            return e2.getAge() - e1.getAge();

        }

    }


    public static void main(String[] args) {

        BinarySearchTree<Student> bst2 = new BinarySearchTree<>(new StudentComparator());

        bst2.add(new Student(22));

        bst2.add(new Student(26));


        BinarySearchTree<Student> bst3 = new BinarySearchTree<>(new StudentComparator2());

        bst3.add(new Student(22));

        bst3.add(new Student(26));


        BinarySearchTree<Student> bst4 = new BinarySearchTree<>();

        bst4.add(new Student(22));

        bst4.add(new Student(26));


        BinarySearchTree<Student> bst5 = new BinarySearchTree<>(new Comparator<Student>() {

            @Override

            public int compare(Student o1, Student o2) {

                return 0;

            }

        });

        bst5.add(new Student(22));

        bst5.add(new Student(26));

    }

}



这里,可以采取一些网站测试:

http://520it.com/binarytrees/

采取打印工具类,测试一下:

https://github.com/CoderMJLee/BinaryTrees/tree/master/BinaryTreePrinter/src/com/mj/printer


BinaryTreeInfo的四个实现方法


向上滑动阅览


public class BinarySearchTree<E> implements BinaryTreeInfo {

    @Override

    public Object root() {

        return root;

    }

    @Override

    public Object left(Object node) {

        return ((Node<E>) node).left;

    }

    @Override

    public Object right(Object node) {

        return ((Node<E>) node).right;

    }

    @Override

    public Object string(Object node) {

        return ((Node<E>) node).element;

    }

}





import java.util.Comparator;


public class Main {


    private static class StudentComparator implements Comparator<Student> {

        @Override

        public int compare(Student e1, Student e2) {

            return e1.getAge() - e2.getAge();

        }

    }


    private static class StudentComparator2 implements Comparator<Student> {

        @Override

        public int compare(Student e1, Student e2) {

            return e2.getAge() - e1.getAge();

        }

    }


    public static void main(String[] args) {

        Integer[] nums = new Integer[]{7, 4, 9, 2, 5, 8, 11, 3};

        BinarySearchTree<Integer> bst = new BinarySearchTree<>();

        for (Integer num : nums) {

            bst.add(num);

        }


        BinarySearchTree<Student> bst2 = new BinarySearchTree<>(new StudentComparator());

        for (int i = 0; i < nums.length; i++) {

            bst2.add(new Student(nums[i]));

        }


        BinaryTrees.print(bst);

        BinaryTrees.print(bst2);

    }

}



值相等问题


之前我们采取的是return方案,但需求更多的是第二种方案,即覆盖。


向上滑动阅览


// 添加节点方法

public void add(E element) {

    // 判断添加的是否为空

    elementCheckNull(element);

    // 添加的是第一个节点

    if (root == null) {

        root = new Node<>(element, null);

        size++;

        return;

    }

    // 添加的不是第一个节点

    // 定义一个父节点

    Node<E> parent = root;

    Node<E> node = root;

    int compare = 0;

    while (node != null) {

        compare = compare(element, node.element);

        parent = node;

        if (compare > 0) {

            node = node.right;

        } else if (compare < 0) {

            node = node.left;

        }else{

// ---------- 这里是修改代码

            node.element = element;

            return;

        }

    }

    // 判断是左子节点还是右子节点

    Node<E> newNode = new Node<>(element,parent);

    if (compare > 0) {

        parent.right = newNode;

    }else{

        parent.left = newNode;

    }

    size++;

}


这样可覆盖原有内容



向上滑动阅览


public class Student implements Comparable<Student> {

    private int age;

    private String name;

    

    // 添加属性和构造器

    public Student(int age, String name) {

        this.age = age;

        this.name = name;

    }


    public String getName() {

        return name;

    }


    public void setName(String name) {

        this.name = name;

    }


    @Override

    public String toString() {

        return "Student{" +

                "age=" + age +

                ", name='" + name + '\'' +

                '}';

    }


    public Student(int age) {

        this.age = age;

    }


    public int getAge() {

        return age;

    }


    public void setAge(int age) {

        this.age = age;

    }


    @Override

    public int compareTo(Student e) {

        return age - e.age;

    }

}


public class Main {


    private static class StudentComparator implements Comparator<Student> {

        @Override

        public int compare(Student e1, Student e2) {

            return e1.getAge() - e2.getAge();

        }

    }


    private static class StudentComparator2 implements Comparator<Student> {

        @Override

        public int compare(Student e1, Student e2) {

            return e2.getAge() - e1.getAge();

        }

    }


    static void test() {

        BinarySearchTree<Student> bst = new BinarySearchTree<>();

        bst.add(new Student(12,"mike"));

        bst.add(new Student(16,"leo"));

        bst.add(new Student(7,"bob"));

        bst.add(new Student(12,"tom"));

        BinaryTrees.print(bst);

    }


    public static void main(String[] args) {

        test();

    }

}


测试结果

以上,就是关于二分搜索树的知识点,你学会了吗?如果有疑问,请在后台留言给我们哦!



往期精选




点击阅读原文获取免费课程




以上是关于敲黑板!数据结构精讲之二分搜索树的主要内容,如果未能解决你的问题,请参考以下文章

数据结构之PHP二分搜索树

数据结构之二分搜索树(Binary Search Tree)

玩转数据结构5之二分搜索树(学习笔记)

直击网申系列直播:数据结构高频考点之二叉树二分搜索树二叉堆

查找算法之顺序二分二叉搜索树红黑树 详细比较总结

二分搜索树(Binary Search Tree)