敲黑板!数据结构精讲之二分搜索树
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();
}
}
测试结果
以上,就是关于二分搜索树的知识点,你学会了吗?如果有疑问,请在后台留言给我们哦!
往期精选
点击阅读原文获取免费课程
以上是关于敲黑板!数据结构精讲之二分搜索树的主要内容,如果未能解决你的问题,请参考以下文章