本篇博客将根据现有知识对数据结构堆和Java中对象的比较方法做以小结,以下博客仅作为个人学习过程的小结,如能对各位博友有所帮助不胜荣幸。
本篇博客将简单介绍堆的概念原理应用,以及比较对象的几种方法,只做本人小结,后期随学习深入再做补充修改。
堆及Java对象的比较
堆(Heap)
概念
堆是一种特殊的数据结构,其通常是一个可以被看做一棵完全二叉树的数组对象,这是因为
堆逻辑上是一棵完全二叉树,物理上则是一个保存在一个数组中,其结点在数组中存储的顺序为二叉树的层序遍历顺序
一个堆必须满足的两点
- 堆中的某个结点的值大于(大堆)或小于(小堆)其子树中任意结点的值
- 堆一定是一棵完全二叉树
*此处对完全二叉树不太明白的博友可以参考: 浅谈二叉树与完全二叉树
大堆:任意一结点的值都大于其子树中结点的值
小堆:任意一结点的值都小于其子树中结点的值
原理及实现
向下调整:
前提:整个堆除了该节点(node),其左右子树都已经满足堆条件
过程(小堆为例):
- 判断是否有孩子,如果节点(node)已为叶子结点,则调整结束
判断是否有孩子 —> 判断是否有左孩子(left)(因为是完全二叉树,有右孩子(right)一定有左孩子(left),所以直接判断是否有左孩子(left)) —> 判断左孩子(left)的下标在存储的数组中是否越界 - 确定左子树(left)和右子树(right)的最小值min,如果右子树的值不存在则直接将左子树的值赋给min
- 判断min和结点(node)值的大小,若小于则调整结束,反之交换两结点位置
- 由于交换破坏了node以下的堆结构,故以node为结点再次向下调整,直到node为叶子结点 或 node的值都小于左子树和右子树的值
回过头看每交换一次其实就是将该结点的度+1,则整个过程最差情况执行树的深度次,即时间复杂度为O(log(n))
//向下调整代码演示
public void shiftDown(int[] arr,int size,int index){
int left = index*2 + 1;
while(left< size){
int min = left;
int right = index*2+2;
if(right < size){
if(arr[right] < arr[left]){
min = right;
}
}
if(min > arr[index]){
break;
}
int t = arr[index];
arr[index] = arr[min];
arr[min] = t;
index = min;
left = index*2 + 1;
}
}
建堆
有了以上向下调整的能力,我们就可以将一个将只有一个节点不满足堆特性的完全二叉树调整成堆,以此类推,如果一个任意一个的完全二叉树,则可通过找到其数组中最后一个非叶子结点,以此结点进行向下调整使该结点下的树变为堆,再用同样的方式以此向前调整,直到调整到整个二叉树中的根节点,此时该堆即建好了
//建堆代码示例
public void buildHeap(int[] arr,int size){
for(int i = (size-1-1)/2; i >= 0 ;i--){
shiftDown(arr,size,arr[i]);
}
}
应用(优先级队列)
用于堆这种集合中存放的元素可以保证一个特性,即其堆定(数组的首元素)总能保证为该集合中的最大/最小值,而生活中我们也常常有一些需求如需要得到所有事中优先处理的事,正因这种背景结合堆的特性实现了一种特殊的数据结构——优先级队列(priority queue)
特点
:出队列时总能保证优先级最高的先弹出
官方提供的priorityQueue的方法
操作原理:
入队列(以大堆为例):
- 先按尾插方式插入数组中的index位置
- 将其与其双亲结点( (index-1)/2 位置和 index/2-1 位置)比较,若小于则退出入队成功,否则与双亲中最小的结点交换
- 回到第二步,重复直到没有双亲结点或该值都小于双亲结点
//入队列的向上调整操作
public void shiftUp(int[] arr,int index){
while(index > 0){
int par = (index-1)/2;
if(arr[par] > arr[index]){
break;
}
int t = arr[par];
arr[par] = arr[index];
arr[index] = t;
index = par;
}
}
出队列:为防止破坏堆结构,删除时并不是直接将堆顶元素删除这样重新建堆的代价太大,而是用数组的最后一个元素替换堆顶元素,然后通过向
下调整方式重新调整成堆
TopK问题:
核心:创建k个大小的小堆,遍历集合,每次将遍历到的元素与堆顶元素比较,若大于则与堆顶交换,并对堆进行一次向下调整
public class topK{
public static void main(String[] args) {
System.out.println(topK(new int[]{
5,3,4,8,7,6,1,2,9},3));
System.out.println(topK(new int[]{
},3));
System.out.println(topK(new int[]{
5,3,4,8,7,6,1,2,9},11));
System.out.println(topK(new int[]{
5,3,4,8,7,6,1,2,9},-1));
}
public static String topK(int[] nums,int k){
if(k <= 0 || nums.length == 0){
return "[]";
}
PriorityQueue<Integer> pq = new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});
for(int i = 0; i < nums.length; i++){
if(i < k){
pq.offer(nums[i]);
}else{
if(nums[i] > pq.peek()){
pq.poll();
pq.offer(nums[i]);
}
}
}
return pq.toString();
}
}
对象间常用比较方式
覆写equals()比较
Java中对象的比较不能使用简单的 “>” “=” “<” 比较,因为其为引用类型,如使用 = 进行比较则会比较两个引用的指向地址是否相同,并不是我们所期望的比较值的大小
因此为比较两对象的大小,Java中为我们提供equals()方法,equals()方法为Object定义的方法,所以我们定义的所有对象都继承了该方法,Object类原生的equals()则同样也是使用 = 进行比较
所以我们对自己定义的对象进行比较就必须复写equals()方法,自行定义比较的规则
class People{
String name;
int age;
int high;
int wight;
public People(String name, int age, int high, int wight) {
this.name = name;
this.age = age;
this.high = high;
this.wight = wight;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
People people = (People) o;
return age == people.age;
}
}
public class Test2 {
public static void main(String[] args) {
People people1 = new People("张三",18,175,80);
People people2 = new People("李四",20,178,85);
if(people1.equals(people2)){
System.out.println("张三年纪大");
}else{
System.out.println("李四年纪大");
}
}
}
实现Comparble接口比较
Java中为了对象比较的方便,还提供了一个compatible接口,对象实现该接口的compareTo()方法,即可实现比较功能
//实现Comparable接口
class People implements Comparable{
String name;
int age;
int high;
int wight;
public People(String name, int age, int high, int wight) {
this.name = name;
this.age = age;
this.high = high;
this.wight = wight;
}
@Override
public int compareTo(Object o) {
return this.age - ((People)o).age;
}
}
public class Test2 {
public static void main(String[] args) {
People people1 = new People("张三",18,175,80);
People people2 = new People("李四",20,178,85);
if(people1.compareTo(people2) > 0){
System.out.println("张三年纪大");
}else{
System.out.println("李四年纪大");
}
}
}
equals()规定的是,如果该对象的值(this.val)等于传入对象的值(o.val),则返回true,反之返回false
compatible接口的compareTo()则规定的是,如果该对象的值(this.val) 大于 传入对象的值(o.val),则返回1,等于返回0,小于返回-1
comparator比较器
comparator接口与compatible接口类似,可实现comparator接口的compare()方法,实现对象间的比较功能,而compare()方法则是传入两个参数,对两个对象进行比较
equals、Comparble和 comparator比较
JDK中PriorityQueue底层使用的比较方法
Java集合框架中PriorityQueue底层使用堆结构,因此其添加的元素必须具有比较能力,PriorityQueue中采用Comparble和Comparator两种比较方法
Comparble 是 PriorityQueue 内部的默认比较方法,如果用户插入对象,该创建该对象的类必须实现Comparble 的 ComparTo()方法
Compare 是 用户可选择添加的比较器,如用户插入对象,必须给创建的PriorityQueue 对象提供一个Comparator接口下的Compare()方法
以上便是对堆及Java对象的比较知识点小结,随着后续学习的深入还会同步的对内容进行补充和修改,如能帮助到各位博友将不胜荣幸,敬请斧正