java实现,二叉搜索树(过程非常详细)

本文详细介绍了二叉搜索树的基本概念、接口设计、添加步骤、比较规则定义、遍历方法及其实现,包括前序、中序、后序和层序遍历。此外还讲解了如何设计遍历接口,增强其灵活性。

思维导图:

image-20220314201110450

1. 接口设计

int size();//元素的数量
boolean isEmpty();//是否为空
void clear();//清除所有的元素
void add(E element);//添加元素
void remove(E element);//删除元素
boolean contains();//是否包含某元素

2. 添加步骤

  • 找到父结点parent
  • 创建新结点node
  • parent.left = node 或者 parent.right= node
  • 遇到值相同的直接返回或者覆盖该元素(推荐覆盖该元素,因为比较的可能是对象,对象中的某个值相等的话需要覆盖)

3. 比较规则定义

在二叉搜索树中,有一个非常重要的特性,就是二叉搜索树存储的元素必须具备可比性不允许为null。如果传入的是一个类我们必须要指定比较的规则,我们需要告诉保存我们类的集合什么是大,什么是小,在Java中一般我们有两种做法

3.1 第一种:定义一个比较接口(给类添加比较规则)

public interface Comparable<E> {
   
   
    int compareTo(E e);//定义比较规则
}

然后每个需要比较的对象都必须要实现这个比较接口并且需要指定比较规则,例如我这里定义一个Person对象

public class Person implements Comparable<Person>{
   
   
    private int age;
    public Person(int age){
   
   
        this.age = age;
    }

    public int getAge(){
   
   
        return age;
    }
	/**
     * 比较规则,我们可以指定返回值大于1表示当前类在比较中是大的,小于0表示是小的,0表示相等
     */
    @Override
    public int compareTo(Person person) {
   
   
//        if(age > person.age) return 1; //可以简化成下面的写法
//        if(age < person.age) return -1;
//        return 0;
        return age - person.age;
    }
}

这种比较规则的缺点也显而易见,就是比较规则都写在待比较的类里面了,不够灵活,如果我们对两个集合的比较规则不一样的话就不能进行合适的比较了。例如想在我有两个Person集合同时进行比较,第一个集合的比较规则是年纪大的大,而第二个集合比较规则是年纪小的大,这样的话就不能同时进行比较。

3.2 第二种:定义一个比较器(给集合添加比较规则)

我们可以指定一个比较器,并当做参数传入到集合BinarySearchTree

/**
 * 比较器,可以在类外面自定义比较规则,这种方式是给集合指定比较规则
 * @param <E>
 */
public interface Comparator<E> {
   
   
    int compare(E e1,E e2);
}

我们在BinarySearchTree中通过构造器就可以将我们自定义的比较规则传入

private Comparator<E> comparator;//比较器
public BinarySearchTree(Comparator<E> comparator){
   
   this.comparator = comparator;}

然后就可以非常灵活的每次创建集合的时候都指定比较规则,也不用每个类都去实现Comparable<E>接口了

/**
 * 自定义比较规则1
 */
private static class PersonComparator implements Comparator<Person>{
   
   
    @Override
    public int compare(Person e1, Person e2) {
   
   
        return e1.getAge() - e2.getAge();
    }
}
/**
 * 自定义比较规则2
 */
private static class PersonComparator2 implements Comparator<Person>{
   
   
    @Override
    public int compare(Person e1, Person e2) {
   
   
        return e2.getAge() - e1.getAge();
    }
}
@Test
public void test01(){
   
   
    BinarySearchTree<Person> bst1 = new BinarySearchTree<>(new PersonComparator());
    bst1.add(new Person(11));
    bst1.add(new Person(12));
    BinarySearchTree<Person> bst2 = new BinarySearchTree<>(new PersonComparator2());
    bst2.add(new Person(11));
    bst2.add(new Person(12));
}

当然我们也可以通过匿名内部类进行简化

@Test
public void test01(){
   
   
    BinarySearchTree<Person> bst1 = new BinarySearchTree<>(new Comparator<Person>() {
   
   
        @Override
        public int compare(Person e1, Person e2) {
   
   
            return e1.getAge() - e2.getAge();
        }
    });
    bst1.add(new Person(11));
    bst1.add(new Person(12));
    BinarySearchTree<Person> bst2 = new BinarySearchTree<>(new Comparator<Person>() {
   
   
        @Override
        public int compare(Person e1, Person e2) {
   
   
            return e2.getAge() - e1.getAge();
        }
    });
    bst2.add(new Person(11));
    bst2.add(new Person(12));
}

不过我们一般开发都是会使用Lambda语法进行进一步的简化

@Test
public void test01(){
   
   
    BinarySearchTree<Person> bst1 = new BinarySearchTree<>((e1, e2) -> e1.getAge() - e2.getAge());
    bst1.add(new Person(11));
    bst1.add(new Person(12));
    BinarySearchTree<Person> bst2 = new BinarySearchTree<>((e1, e2) -> e2.getAge() - e1.getAge());
    bst2.add(new Person(11));
    bst2.add(new Person(12));
}

这两种方式都很常见,第一种方法我们在给List<E>进行排序的时候经常用到Collections.sort()对其进行排序,只需要在需要排序的类E中实现java.lang.Comparable<E>比较接口就可以了,比较规则也是在每一次的比较中返回大于1的数表示当前类大,返回小于0的数表示当前类小,返回0表示一样大。当然list本身自己也有sort()方法用来对其中的对象进行排序

通过Collections.sort()的方式

@Test
public void test02(){
   
   
    List<Person> list = new ArrayList<>();
    list.add(new Person(20));
    list.add(new Person(19));
    Collections.sort(list,Person::compareTo);
    list.forEach(e -> System.out.println(e.getAge()));
}

通过list.sort()进行排序

@Test
public void test02(){
   
   
    List<Person> list = new ArrayList<>();
    list.add(new Person(20));
    list.add(new Person(19));
    list.sort(Person::compareTo);
    list.forEach(e -> System.out.println(e.getAge()));
}

还可以用stream API

@Test
public void test02(){
   
   
    List<Person> list = new ArrayList<>();
    list.add(new Person(20));
    list.add(new Person(19));
    list.stream()
            .sorted()
            .forEach(e -> System.out.println(e.getAge()));
}

第二种方式使用Comparator<E>比较器,给每一个集合定制比较规则,其实这种方式更加常用,一般开发中我们都是用的这种方法,给集合定制比较规则,而不是去给类实现比较接口

通过Collections.sort()的方式

@Test
public void test02(){
   
   
    List<Person> list = new ArrayList<>();
    list.add(new Person(20));
    list.add(new Person(19));
    Collections.sort(list, new java.util.Comparator<Person>() {
   
   
        @Override
        public int compare(Person o1, Person o2) {
   
   
            return o2.getAge() - o1.getAge();
        }
    });
}

//Lambda简化
@Test
public void test02(){
   
   
    List<Person> list = new ArrayList<>();
    list.add(new Person(20));
    list.add(new Person(19));
    Collections.sort(list, (o1, o2) -> o2.getAge() - o1.getAge());
}

通过list.sort()进行排序

@Test
public void test02(){
   
   
    List<Person> list = new ArrayList<>();
    list.add(new Person(20));
    list.add(new Person(19));
    list.sort(new java.util.Comparator<Person>() {
   
   
        @Override
        public int compare(Person o1, Person o2) {
   
   
            return o1.getAge() - o2.getAge();
        }
    });
}

//Lambda简化
@Test
public void test02(){
   
   
    List<Person> list = new ArrayList<>();
    list.add(new Person(20));
    list.add(new Person(19));
    list.sort((o1, o2) -> o2.getAge() - o1.getAge());
}

stream API

@Test
public void test02(){
   
   
    List<Person> list = new ArrayList<>();
    list.add(new Person(20));
    list.add(new Person(19));
    list.stream()
            .sorted((o1, o2) -> o2.getAge() - o1.getAge())
            .forEach(e -> System.out.println(e.getAge()));
}

3.3 最终解决方案

第一种方法让类去实现Comparable<E>并自定义compareTo方法不够灵活,无法对每个集合对定制比较规则

第二种方法比较灵活,但是每次都需要传入比较器

综上所述:我们可以结合两者使用,我们可以传入给集合定制的比较器,也可以不传,不传就默认使用类实现比较接口的比较器,但是如果我们在集合中写成这样的话其实也有问题,就是待比较的类我们都必须实现比较接口,不实现就报错

public class BinarySearchTree<E extends Comparable<E>> {
   
   }

为了解决这个问题,我们可以在集合中具体的比较中这样写,这样的话既不用强制让类实现比较接口,又不用强制给集合传入比较器,当然Java其实已经实现了comparatorComparable

/**
 * 规定传入对象的比较规则
 * @param e1 第一个对象
 * @param e2 第二个对象
 * @return 0表示相等,大于0表示 e1 > e2,小于0表示 e2 < e1
 */
@SuppressWarnings("unchecked")//表示不进行强制类型转换提示,即下面类型转换的时候没有类型装换提示
private int compare(E e1,E e2){
   
   
    if(comparator != null){
   
   
        return comparator.compare(e1,e2);
    }
    return ((Comparable<E>
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值