从大根堆说到对象的比较
大小根堆
大小根堆是堆相关的知识,堆这种数据结构总结起来就是:堆顶元素是最大的就是大根堆,而每个堆顶元素以下的又可以看成一个堆:
要注意的是,堆底层是用数组实现的:
但是对这种数据结构具体化之后与二叉树是高度类似的.
PriorityQueue<Integer> priorityQueue=new PriorityQueue<>();
priorityQueue.offer(1);
priorityQueue.offer(2);
System.out.println(priorityQueue.peek());
代码告诉了我们,我们创建出来的堆默认都是小根堆,实际上我们也可以去源码里看看:
认识一个类,首先就要认识类的构造方法:可以看到,不带任何参数的PriorityQueue( ),会调用带两个参数的构造方法,第一个参数是常量,也是意味着堆底层数组的默认容量是11(这是从源码里面点进去看到的),第二个参数人家本来是想让我们传一个比较器的,这里不传的话就给个null.
我觉得比较重要就是这个offer方法:
public boolean offer(E e) {//打个比方,这是个Integer的泛型类,但是我们
传参数的话给个1,2是没问题的,这个会自动装箱
if (e == null)
throw new NullPointerException();//第一个注意点:给堆添加
元素的时候,不可以给null,否则会抛异常
modCount++;//这个暂时不要管,之前在哪里也看到过这个变量名的
int i = size;
if (i >= queue.length)
grow(i + 1);//扩容方法grow,当然这个也不可能给你无限扩容下去
最大好像就是int的最大值来着
size = i + 1;
if (i == 0)
queue[0] = e;
else
siftUp(i, e);//在这里我们之前第二个比较器参数就要派上用场了
return true;
}
private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x);//这就是比较器
else
siftUpComparable(k, x);//这是堆默认的比较
}
private void siftUpComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>) x;
//看好,这边把我们本来要插入的数据强转了:
//(Comparable<? super E>) x 这个Comparable很显然就是一个接口,那么
//既然我们敢对X进行强转,说明这个x背后的类一定要实现Comparable这个
//接口,也正因如此我斗胆得出一个局部结论:我们要往堆插入的元素一定要是
//可以比较的!!
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = queue[parent];
if (key.compareTo((E) e) >= 0)
break;
queue[k] = e;
k = parent;
}
queue[k] = key;
}
//比较器这个也是大同小异
private void siftUpUsingComparator(int k, E x) {
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = queue[parent];
if (comparator.compare(x, (E) e) >= 0)
break;
queue[k] = e;
k = parent;
}
queue[k] = x;
}
对象的比较
只是站在使用堆的角度上,我们只要简单了解一下底层的原理就行,当然,我们也可以点进堆中,详细走一遍.
在刚才的讲解中,有一个重大的漏洞我还没有补上,甚至于说,这篇博客前面关于堆的知识,其实就是我为了引出这个知识点而做出的铺垫:
在Java中我们该如何实现对象的比较呢?
equals
如果是部分简单的基本数据类型,我们直接大于等于小于,那就OK了.
但是对于大部分引用类型,我们是不可以大于小于的,并且我们使用等于比较的仅仅是两个引用的地址.
可曾听闻equals?
class Person{
int id;
String name;
public Person(int id, String name) {
this.id = id;
this.name = name;
}
public Person() {
}
}
Person A=new Person(1,"A");
Person a=new Person(1,"A");
boolean flag=A.equals(a);
System.out.println(flag);
按照我们的设想,A和a这兄弟两身份证一样,就应该是同一个人了,但事实呢:
所有类都默认继承了Object类,这个类中的equals方法定睛一看,原来比较的也是地址啊…为了让故事的走向合乎于理,我们必须要自己手写剧本:
我们要自己重写equals方法!!
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return id == person.id;
}
然后就一路点下去就可以了,这下子故事的走向就应该和我们设想的一样了:
Person A=new Person(1,"A");
Person a=new Person(1,"a");
boolean flag=A.equals(a);
System.out.println(flag);
即使这两个人名字不一样,只要身份证一致那你俩就是一个人!!
其实官方重写的这个equals方法看似繁琐,但是设计的还是非常之巧妙的,看官老爷们不妨三连之后再去细品!!
Comparable
刚才讲的equals是很方便,但是光光只是一个孤零零的方法并不具有面向对象的特性,接下来我们要讲的两个接口才是我们在底层代码中常见的:
Comparable
就拿我们刚才讲的堆举例子吧,我们要往堆里面插入Person类对象的话可以吗?
看似没问题哈,我还告诉你们,就是运行一下子都不会有任何报错,我们再来一个试试:
来个特写:
类型转换错误,这就是我们之前跑底层的时候说的x被强制转化成了Comparable,这也就意味着我们x背后的类一定要实现这个接口,不然就会发生上面的错误,我们说的,实现接口然后要重写接口中的方法:
@Override
public int compareTo(Person o) {
return this.age-o.age;
}
从宏观上我们确实达到了我们的目的,那么从微观上究竟是如何实现这个操作的呢?其实之前跑代码的时候我们就说过了:
细心的看官老爷会发现其实之前我们的equals方法返回的是布尔类型,但是这里我们的compareTo包括后面的compare返回的都应该是int类型,谁调用compareTo方法谁就是源码里面的this,( )里面的参数就是o.
Comparator
刚刚我们讲的那么Comparable接口啊,虽然好使,但是有一个弊端,这个方法对于代码的侵入性太强,意思就是,如果有一天我不想使用年龄作为参考进行比较了,然后我还自己偷摸给它改了,那么麻烦就大了,整个程序肯定是到处见红啊!
那么我们还有一招可以使:Comparator
class AgeComparator implements Comparator<Person>{
@Override
public int compare(Person o1, Person o2) {
return o1.age- o2.age;
}
}
AgeComparator ageComparator=new AgeComparator();
PriorityQueue<Person>priorityQueue2
=new PriorityQueue<>(ageComparator);
Person C=new Person(1,6);
Person D=new Person(2,2);
priorityQueue2.offer(C);
priorityQueue2.offer(D);
这招看似麻烦一点,但是无论是效果还是后续的影响上来看,都不输给Comparable!
而且使用Comparator还有一点好处,就是哪一天我不高兴了,我可以直接另外写一个比较器,我比较名字:
@Override
public int compare(Person o1, Person o2) {
return o1.name.compareTo(o2.name);
}
当然了这个compareTo呢也不是天上掉下来了,也是Object里面的.
说到这里,兄弟们,现在大家应该有一点感悟了吧,那我请问一下兄弟们:为什么即使默认为小根堆呢?如果兄弟们看源码的话,应该会发现无论是无论是哪个接口的方法下面,必须要是满足了接口下面比较方法的条件是才会调整,所以如果我们改变比较方法的正负,那么自然而然的也就会变成大根堆喽!!!
emmm相信读到这里兄弟们关于堆和对象的比较应该也就是拨开云雾见天明了吧.我相信这篇博客能够切实地帮到一些道友!
给各位看官老爷拜个晚年,祝大家兔年一帆风顺!
百年大道,你我共勉!!