优先级队列:返回最高优先级对象、添加新的对象
堆:将一个给定的集合或数组里面的元素按照完全二叉树层序遍历的顺序存储在一个一维数组中
堆的存储方式:大根堆、小根堆
大根堆:每个节点都大于左右子节点
小根堆:每个节点都小于左右子节点
一.模拟实现堆的创建:
思路:利用数组或集合创建——堆的向下调整
1.从最后一颗子树开始调整
2.每颗子树调整的时候都是向下调整
主要解决问题:
(1)确定最后一课子树的根节点p = [(len-1)-1]/2
(2)p--
(3)堆向下调整,确保左右子节点<len
import java.util.Arrays;
/**
* 根据给定的数组创建大根堆或者小根堆(堆的向下调整)
* 1.从最后一颗子树开始调整
* 2.每颗子树调整的时候,都是向下调整
* 解决问题:
* 1.确定最后一颗子树的根节点p = ((usedsize-1)-1)/2
* 2.每课子树都向下调整p--
* 3.堆向下调整时,确保左、右子节点<len
*/
public class MyPriorityQueue {
public int[] elem;
public int usedsize;
public MyPriorityQueue(){//这里在初始化的时候不要形参
this.elem = new int[10];
}
public boolean justFull(){
return usedsize== elem.length;
}
//创建大根堆
public int[] createBigHeap(int[] array){
int temp = usedsize;
usedsize = 0;
for (int i = 0; i < array.length; i++) {//完成对堆中数据的初始化
if(justFull()){//判断堆是否满,满了就扩容
elem = Arrays.copyOf(elem,2*elem.length);
}
elem[i] = array[i];
usedsize++;
if(usedsize==temp)break;
}
for(int p = (usedsize-1-1)/2;p>=0;p--){//完成堆的向下调整
shiftDown1(p,usedsize);
}
return this.elem;
}
/**
* root:每颗子树的根节点的下标
* len:每颗子树调整结束的结束条件
*/
private void shiftDown1(int root,int len){
int parent = root;
int child = 2*parent+1;
while(child<len){//进入这个循环,说明一定有左孩子
if(child+1<len &&elem[child]<elem[child+1]){//如果有右孩子,找到孩子中较大的一个
child++;//child下标一定保存的是左右孩子最大值的下标
}
if(elem[child]>elem[parent]){//将较大的孩子与parent节点交换
int temp = elem[child];
elem[child] = elem[parent];
elem[parent] = temp;
parent = child;//开始更新下标,继续看下面的子树是不是大根堆
child = 2*parent+1;
}else{//说明这颗子树已经是大根堆
break;
}
}
}
//创建小跟堆
public int[] createSmallHeap(int[] array){
int temp = usedsize;
usedsize = 0;
for (int i = 0; i < array.length; i++) {//完成对堆中数据的初始化
if(justFull()){//判断堆是否满,满了就扩容
elem = Arrays.copyOf(elem,2*elem.length);
}
elem[i] = array[i];
usedsize++;
if(usedsize==temp)break;
}
for(int p = (usedsize-1-1)/2;p>=0;p--){//完成堆的向下调整
shiftDown2(p,usedsize);
}
return this.elem;
}
private void shiftDown2(int root,int len){
int parent = root;
int child = 2*parent+1;
while(child<len){//进入这个循环,说明一定有左孩子
if(child+1<len &&elem[child+1]<elem[child]){//如果有右孩子,找到孩子中较小的一个
child++;//child下标一定保存的是左右孩子最小值的下标
}
if(elem[child]<elem[parent]){//将较小的孩子与parent节点交换
int temp = elem[child];
elem[child] = elem[parent];
elem[parent] = temp;
parent = child;//开始更新下标,继续看下面的子树是不是小根堆
child = 2*parent+1;
}else{//说明这颗子树已经是小根堆
break;
}
}
}
//打印堆中的数据
public void printHeap(){
for(int i = 0;i<usedsize;i++){
System.out.print(elem[i]+" ");
}
}
}
主函数里面实现一下
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
MyPriorityQueue heap = new MyPriorityQueue();
int[] elem = new int[]{10,2,1,4,7,3,5,15,11};
System.out.println("创建的大根堆为:");//创建大根堆
heap.createBigHeap(elem);
heap.printHeap();
System.out.println();
System.out.println("=====================");
System.out.println("创建的小根堆为:");//创建小跟堆
heap.createSmallHeap(elem);
heap.printHeap();
System.out.println();
System.out.println("=====================");
}
}
结果
实现向堆中插入元素
思路:
* 将插入的数据放在最后一个节点的后面,然后将插入的节点向上调整直到满足堆的性质 * 1.先判断堆是否满,满了就扩容 * 2.找到最后一个孩子,将新插入的节点放在最后一个孩子后面 * 3.向上调整
/**
* 堆的插入
* 思路:
* 将插入的数据放在最后一个节点的后面,然后将插入的节点向上调整直到满足堆的性质
* 1.先判断堆是否满,满了就扩容
* 2.找到最后一个孩子,将新插入的节点放在最后一个孩子后面
* 3.向上调整
*/
public int[] insertHeap(int data){
if(justFull()){//判断堆是否满,满了就扩容
elem = Arrays.copyOf(elem,2*elem.length);
}
elem[usedsize] = data;
int child = usedsize;
usedsize++;
int parent = (child-1)/2;
while(parent>=0){
if(elem[child]>elem[parent]){
int temp = elem[child];
elem[child] = elem[parent];
elem[parent] = temp;
child = parent;
parent = (child-1)/2;
}else{
break;
}
}
return this.elem;
}
主函数里面实现一下
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
MyPriorityQueue heap = new MyPriorityQueue();
int[] elem = new int[]{10,2,1,4,7,3,5,15,11};
System.out.println("创建的大根堆为:");//创建大根堆
heap.createBigHeap(elem);
heap.printHeap();
System.out.println();
System.out.println("=====================");
System.out.println("创建的小根堆为:");//创建小跟堆
heap.createSmallHeap(elem);
heap.printHeap();
System.out.println();
System.out.println("=====================");
System.out.println("向堆中插入数据并用大堆创建");
heap.createBigHeap(heap.insertHeap(100));
heap.printHeap();
System.out.println();
}
}
结果
删除堆中的元素(一定是删除堆顶元素)
* 堆元素的删除(删除的一定是优先级高的元素): * 1.将堆顶元素与堆中最后一个元素交换 * 2.将堆中的元素个数减少一个 * 3.将堆顶元素进行向下调整
/**
* 堆元素的删除(删除的一定是优先级高的元素):
* 1.将堆顶元素与堆中最后一个元素交换
* 2.将堆中的元素个数减少一个
* 3.将堆顶元素进行向下调整
*/
public int[] deleteHeap(){
if(isEmpty()){//先判断堆中是否有元素,没有就返回
return elem;
}
int parent = 0;
int child = 2*parent+1;
int temp0 = elem[0];
elem[0] = elem[usedsize-1];
elem[usedsize-1] = temp0;
shiftDown2(0,--usedsize);
return elem;
}
结果
获取堆顶元素
public int getHeaphead(){
if(isEmpty()){
System.out.println("优先级队列为空");
return -1;
}
return elem[0];
}
模拟实现优先级队列
package MyPriorityQueue;
import java.util.AbstractQueue;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
/**
* 模拟实现PriorityQueue
* 主要模拟有以下类容:
* 1.利用Comparable接口和Comparactor接口两种方式创建大小大小根堆
* 2.模拟实现offer(E e)、peek()、poll()、size()、isEmpty()这些功能
*/
public class MyPriorityQueue<E> {
private Object[] hp;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 11;
/**
*内部定义比较器对象,用来接收用户实例化PriorityQueue对象时提供的比较器对象
*如果用户没有提供比较器对象,使用默认的内部比较,将comparator置为null
*如果用户提供了比较器对象,按照用户提供的比较器进行比较
*/
private Comparator<? super E> comparator;
public MyPriorityQueue(){//无参构造器
this(DEFAULT_INITIAL_CAPACITY,null);
}
public MyPriorityQueue(int initialCapacity){//用户自定义容量的构造器
this(initialCapacity,null);
}
public MyPriorityQueue(Comparator<? super E> comparator){//用户没有自定义容量,但自定义了一个比较器
this(DEFAULT_INITIAL_CAPACITY,comparator);
}
public MyPriorityQueue(int initialCapacity,Comparator<? super E> comparator){//用户自定义容量,且自定义了一个比较器
if(initialCapacity<1)throw new IllegalArgumentException();
this.hp = new Object[initialCapacity];
this.comparator = comparator;
}
public MyPriorityQueue(MyPriorityQueue<? extends E> c){//将一个集合元素直接放在优先级队列中
//将数组元素放在优先级队列底层的容器中
this.comparator =(Comparator<? super E>) c.comparator;
this.hp = c.toArray();
this.size = c.size;
}
public MyPriorityQueue(E[] array){
this.hp = Arrays.copyOf(array,array.length);
this.size = hp.length;
//对hp中的元素进行向下调整
for(int root = (size-2)>>1;root>=0;root--){
shiftDown(root);
}
}
//插入元素,然后进行向上调整
public boolean offer(E e){
if(e==null)throw new NullPointerException();
if(size>=hp.length){
grow();
}
int i = size;
hp[size++] = e;
if(i==0)
{
hp[i] = e;
}else{
shiftUp(i);
}
return true;
}
//删除堆顶元素
public E poll(){
if(isEmpty())return null;
E result = (E)hp[0];
swap((E[])hp,0,size-1);
size--;
shiftDown(0);
return result;
}
//获取堆顶元素
public E peek(){
return size==0?null:(E)hp[0];
}
//获取优先级队列的长度
public int size(){
return size;
}
/**
* 当用户没有给比较器在向上调整时,就使用系统自带比较方法
* 当用户给了比较器在向上调整时,就用用户所给的比较器进行比较
* @param x
*/
public void shiftUp(int x){
if(comparator!=null){
shiftUpWhiscomparator(x);
}else{
shiftUpWhiscomparaTo(x);
}
}
public void shiftUpWhiscomparator(int child){
int parent = (child-1)>>1;
while(parent>=0){
if(comparator.compare((E)hp[child],(E)hp[parent])<0){
swap((E[])hp,child,parent);
child = parent;
parent = (child-1)>>1;
}else{
break;
}
}
}
public void shiftUpWhiscomparaTo(int child){
int parent = (child-1)>>1;
while(parent>=0){
if(((Comparable<? super E>)hp[child]).compareTo((E)hp[parent])<0){
swap((E[])hp,child,parent);
child = parent;
parent = (child-1)>>1;
}else{
break;
}
}
}
/**
* 当用户没有给比较器在向下调整时,就使用系统自带比较方法
* 当用户给了比较器在向下调整时,就用用户所给的比较器进行比较
* @param x
*/
public void shiftDown(int x){
if(comparator!=null){
shiftDownWithcomparator(x);
}else{
shiftDownWithcompareTo(x);
}
}
//使用比较器比较
public void shiftDownWithcomparator(int parent){
int child = 2*parent+1;
while(child<size){
if (child+1<size&&comparator.compare((E)hp[child+1],(E)hp[child])<0){
child++;
}
if(comparator.compare((E)hp[child],(E)hp[parent])<0){
swap((E[])hp,child,parent);
parent = child;
child = 2*parent+1;
}else{
break;
}
}
}
//使用comparaTo比较
public void shiftDownWithcompareTo(int parent){
int child = 2*parent+1;
while(child<size){
if(child+1<size && ((Comparable<? super E>)hp[child+1]).compareTo((E)hp[child])<0){
child++;
}
if(((Comparable<? super E>)hp[child]).compareTo((E)hp[parent])<0){
swap((E[] )hp,child,parent);
parent = child;
child = 2*parent+1;
}else{
break;
}
}
}
public void swap(E[] hp,int x,int y){
E temp = hp[x];
hp[x] = hp[y];
hp[y] = temp;
}
public void grow(){
int oldCapacity = hp.length;
int newCapacity = oldCapacity+oldCapacity<64?oldCapacity+2:oldCapacity>>1;
hp = Arrays.copyOf(hp,newCapacity);
}
public Comparator<? super E> comparator(){
return comparator;
}
public Object[] toArray(){
return Arrays.copyOf(hp,size);
}
public void print(){
for (int i = 0; i < size; i++) {
System.out.print(hp[i]+" ");
}
System.out.println();
}
public boolean isEmpty(){
return size==0;
}
}
import java.util.Comparator;
public class Test {
public static void main(String[] args) {
MyPriorityQueue<Integer> queue = new MyPriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});
queue.offer(12);
queue.offer(0);
queue.offer(21);
queue.offer(34);
queue.offer(100);
queue.print();
// System.out.println(queue.poll());
// queue.print();
//
// System.out.println(queue.peek());
//
// System.out.println(queue.size());
// System.out.println(queue.isEmpty());
}
}
堆的应用
1.用堆实现优先级队列
2.堆排序
二:优先级队列
优先级队列按照优先级来处理,先出优先级高的元素,要选出优先级最高的元素要借助堆来完成。优先级队列中的添加数据(offer())操作,对应着堆中的添加函数,优先级队列中的删除数据(poll())操作,对应着堆中的删除函数。优先级队列就是将堆进行一次封装,调用堆中的函数。
java集合框架中提供了PriorityQueue和PriorityBlockingQueue两种类型的优先级队列,PriorityQueue是线程不安全的,PriorityBlockingQueue是线程安全的。
1.系统提供给我们优先级队列PriorityQueue中一些常用方法
boolean offer(E e) 插入元素e,成功返回true,如果插入null返回空指针异常
peek() 获取优先级最高的元素,如果优先级队列为空,返回null
poll() 移除优先级最高的元素并返回,如果优先级队列为空,返回null
size() 获取有效元素的个数
isEmpty() 检测优先级队列是否为空,空返回true
2.注意:
使用PriorityQueue的一些注意事项
1.使用时必须导入PriorityQueue所在的包,即import java.util.PriorityQueue;
2.PriorityQueue中放置的元素必须能够比较大小,不能插入无法比较大小的对象,否者会抛出类型转换异常
import java.util.PriorityQueue;
class Dog{
private int age;
public Dog(int age) {
this.age = age;
}
@Override
public String toString() {
return "Dog{" +
"age=" + age +
'}';
}
}
public class SystemPriorityQueue {
public static void main(String[] args) {
PriorityQueue<Dog> priorityQueue = new PriorityQueue<>();
priorityQueue.offer(new Dog(3));
System.out.println(priorityQueue);
}
}
上面的代码看是没有问题,但是当再创建一个新对象时就会报类型转换异常
import java.util.PriorityQueue;
class Dog{
private int age;
public Dog(int age) {
this.age = age;
}
@Override
public String toString() {
return "Dog{" +
"age=" + age +
'}';
}
}
public class SystemPriorityQueue {
public static void main(String[] args) {
PriorityQueue<Dog> priorityQueue = new PriorityQueue<>();
priorityQueue.offer(new Dog(3));
priorityQueue.offer(new Dog(5));
System.out.println(priorityQueue);
}
}
为什么会出现这样的原因,这就要看PriorityQueue中的offer底层源码了
(1).在offer源码中,有一个向上调整的代码。当i==0时,不会进入该代码,也就是说第一次添加数据不会进行向上调整。
(2).当i!=0,就会进入向上调整代码,追进去可以看到有两个方法。看第二个
(3).向上调整比较,追进去
可以看到它的底层是通过比较器来实现数据的向上调整的。而在写Dog类的时候并没有实现Comparable接口,这里也就无法强转。所以在向优先级队列中插入数据的时候就会报类型转化异常。
解决办法就是在Dog类里面重写 Comparable接口
import java.util.PriorityQueue;
class Dog implements Comparable<Dog>{
private int age;
public Dog(int age) {
this.age = age;
}
@Override
public String toString() {
return "Dog{" +
"age=" + age +
'}';
}
@Override
public int compareTo(Dog o) {//这里可以调节比较顺序
return this.age-o.age;
}
}
public class SystemPriorityQueue {
public static void main(String[] args) {
PriorityQueue<Dog> priorityQueue = new PriorityQueue<>();
priorityQueue.offer(new Dog(3));
priorityQueue.offer(new Dog(5));
System.out.println(priorityQueue);
}
}
结果
3.不能插入null对象,否者会抛出空指针异常
4.我们知道优先级队列里面是默认以小堆的方式来排序的,但通过上面的知识我们知道,只要实现Comparable接口重写comparaTo方法即可实现以大堆的方式来排序
下面我们改变整数的排序方式
方法一:
public class SystemPriorityQueue {
public static void main(String[] args) {
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});
priorityQueue.offer(12);
priorityQueue.offer(32);
priorityQueue.offer(123);
System.out.println(priorityQueue);
}
}
方法二:
class IntCom implements Comparator<Integer>{
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
}
public class SystemPriorityQueue {
public static void main3(String[] args) {
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(new IntCom());
priorityQueue.offer(12);
priorityQueue.offer(32);
priorityQueue.offer(123);
System.out.println(priorityQueue);
}
}
结果:
3.优先级队列常用的构造方法
PriorityQueue() 创建一个空的优先级队列,默认容量是11
PriorityQueue(int initialCapacity) 创建一个初始容量为initialCapacity的优先级队列,initialCapacity不能小于1,否者会抛出IllegalArgumentException异常
PriorityQueue(Collection<? extends E> c) 用一个集合来创建优先级队列
4.优先级队列的应用
top-k问题:最大或最小的前k个数据