1.优先级队列(概念)
队列是一种先进先出(FIFO)的数据结构,但有些情况下,操作的数据可能带有优先级,一般出队列时,可能需要优先级高的元素先出队列,该中场景下,使用队列显然不合适,比如:在手机上玩游戏的时候,如果有来电,那么系统应该优先处理打进来的电话。在这种情况下,我们的数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象。这种数据结构就是优先级队列(Priority Queue)。
1.1java中的优先级队列(PriorityQueue)
常用方法:
通过代码来感受一下:
import java.util.PriorityQueue;
public class Test0606_1 {
public static void main(String[] args) {
int[] arr={4,3,6,1,7,8};
//构建一个优先级队列对象
PriorityQueue<Integer> queue =new PriorityQueue<>();
for(int x:arr){
queue.offer(x);
}
while (!queue.isEmpty()){
Integer a= queue.poll();
System.out.println(a);
}
}
}
运行结果:
从结果可以看出,它内部已经按照一定的优先级给元素排好序了。
再来看一段代码:
import java.util.PriorityQueue;
class girl {
String name;
int age;
int money;
int face;
public girl(String name, int age, int money, int face) {
this.name = name;
this.age = age;
this.money = money;
this.face = face;
}
@Override
public String toString() {
return "girl{" +
"name='" + name + '\'' +
", age=" + age +
", money=" + money +
", face=" + face +
'}';
}
}
public class Test0606_1 {
public static void main(String[] args) {
girl[] girls={
new girl("静静",18,15,100),
new girl("甜甜",25,200,50),
new girl("富婆",40,150,5)
};
PriorityQueue<girl> queue=new PriorityQueue<>();
for (girl g:girls){
queue.offer(g);
}
while (!queue.isEmpty()){
girl g= queue.poll();
System.out.println(g);
}
}
}
运行结果:
出现异常了,原因其实也不难发现,它内部是无法直接你传入的对象来进行比较的,可以看源码的出结论:
在第二次插入对象时,它会进行向上调整,此时可以从源代码中看出,插入的对象需要实现它的Comparable接口的,由于刚才没有实现,所以才会抛出异常!
修改改后的代码为:
import java.util.PriorityQueue;
class girl implements Comparable<girl>{
String name;
int age;
int money;
int face;
public girl(String name, int age, int money, int face) {
this.name = name;
this.age = age;
this.money = money;
this.face = face;
}
@Override
public String toString() {
return "girl{" +
"name='" + name + '\'' +
", age=" + age +
", money=" + money +
", face=" + face +
'}';
}
@Override
public int compareTo(girl o) { //指定的排序规则
return o.money-this.money;
}
}
public class Test0606_1 {
public static void main(String[] args) {
girl[] girls={
new girl("静静",18,15,100),
new girl("甜甜",25,200,50),
new girl("富婆",40,150,5)
};
PriorityQueue<girl> queue=new PriorityQueue<>();
for (girl g:girls){
queue.offer(g);
}
System.out.println("通过钱来排序:");
while (!queue.isEmpty()){
girl g= queue.poll();
System.out.println(g);
}
}
}
运行结果:
补充:
除了实现compareble接口之外,还可以通过实现comparator接口来实现一些复杂的排序,在构造优先级队列时传入比较器实例对象即可(comparator的实现类!)代码如下:
import java.util.Comparator;
import java.util.PriorityQueue;
class girl {
String name;
int age;
int money;
int face;
public girl(String name, int age, int money, int face) {
this.name = name;
this.age = age;
this.money = money;
this.face = face;
}
@Override
public String toString() {
return "girl{" +
"name='" + name + '\'' +
", age=" + age +
", money=" + money +
", face=" + face +
'}';
}
}
class compareTest implements Comparator<girl>{
@Override
public int compare(girl o1, girl o2) { //性价比复合比较规则
return (o2.face*6+o2.money*2+o2.age*2) -(o1.face*6+o2.age*2+o2.money*2);
}
}
public class Test0606_1 {
public static void main(String[] args) {
girl[] girls={
new girl("静静",18,15,100),
new girl("甜甜",25,200,50),
new girl("富婆",40,150,5)
};
PriorityQueue<girl> queue=new PriorityQueue<>(new compareTest());
for (girl g:girls){
queue.offer(g);
}
System.out.println("性价比最高的女朋友排序:");
while (!queue.isEmpty()){
girl g= queue.poll();
System.out.println(g);
}
}
}
运行结果为:
1.2堆
JDK1.8中的PriorityQueue底层使用了堆的数据结构,而堆实际就是在完全二叉树的基础之上进行了一些元素的调整。
如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式
存储在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
性质:
1.堆中某个节点的值总是不大于或不小于其父节点的值。
2.堆总是一棵完全二叉树。
1.3堆的创建
import java.util.Arrays;
public class HeapTest {
//向下调整(大堆)
private static void shiftDown(int[] arr,int size,int index){
int parent =index;
int child=2*parent+1; //左孩子下标
while (child<size){ //循环调整
if(child+1<size && arr[child+1]>arr[child]){
child=child+1;
}
if(arr[parent]<arr[child]){
int tem=arr[parent];
arr[parent]=arr[child];
arr[child]=tem;
}else {
break;
}
parent=child; //更新引用的位置,进行下次循环
child=2*parent+1;
}
}
//向上调整(大堆)
public static void shiftUp(int[] arr, int size, int index) {
int child = index;
int parent = (child - 1) / 2;
// 如果 child 为 0, 说明已经调整到最上面了
while (child > 0) {
if (arr[parent] < arr[child]) {
// 不符合大堆的要求
// 交换两个元素
int tmp = arr[parent];
arr[parent] = arr[child];
arr[child] = tmp;
} else {
break;
}
child = parent;
parent = (child - 1) / 2;
}
}
public static void main(String[] args) {
int[] arr={4,2,5,7,8,100};
// 向下调整建堆,只能从后往前遍历
// for(int i=arr.length-1;i>=0;i--){
// shiftDown(arr,arr.length,i);
//
// }
//
//向上调整建堆,需要从前往后遍历
for(int i=0;i<arr.length;i++){
shiftUp(arr,arr.length,i);
}
System.out.println(Arrays.toString(arr));
}
}
运行结果:
从运行结果可知,完全符合建大堆的预期结果!
向下调整原理图:
2.使用堆模拟优先级队列的实现(MyPriorityQueue)
class girl1 implements Comparable<girl1>{
String name;
int age;
int money;
int face;
public girl1(String name, int age, int money, int face) {
this.name = name;
this.age = age;
this.money = money;
this.face = face;
}
@Override
public String toString() {
return "girl{" +
"name='" + name + '\'' +
", age=" + age +
", money=" + money +
", face=" + face +
'}';
}
@Override
public int compareTo(girl1 o) {
return this.money-o.money;
}
}
public class MyPriorityQueue<T> {
private T[] arr=null;
private int size;
private int capacity=10; //默认的最大容量
public MyPriorityQueue() {
arr=(T[])new Object[capacity];
size=0;
}
//向下调整维护堆(大堆)
private void shiftDown(int k, T x){
Comparable<? super T> key = (Comparable<? super T>)x;
int parent=k;
int child=2*parent+1; //左孩子下标
while (child<size){ //循环调整
Object c=arr[child]; //取左孩子
int right=child+1; //右孩子坐标
//如果右孩子的大小小于队列的大小,说明有右孩子,否就没有
//左右孩子取大的一个,移动下标
if(right<size && ((Comparable<? super T>) c).compareTo((T)arr[right])<0){
child=child+1;
}
//若父亲的大小比孩子小,不满足大堆的要求,就交换
if(key.compareTo((arr[child]))<0){
Object tem=arr[parent];
arr[parent]=arr[child];
arr[child]=(T)tem;
}else {
break; //堆调整完毕
}
parent=child; //更新引用的位置,进行下次循环
child=2*parent+1;
}
}
//向上调整建堆(大堆)
public void shiftUp(int k, T x) {
Comparable<? super T> key = (Comparable<? super T>) x;
int child = k;
int parent = (child - 1) / 2;
// 如果 child 为 0, 说明已经调整到最上面了
while (child > 0) {
Object e = arr[parent];
//如果比父亲还大,交换父亲与孩子
if (key.compareTo((T)e)>=0) {
// 不符合大堆的要求
// 交换两个元素
Object tmp = e;
arr[parent] = arr[child];
arr[child] = (T)tmp;
} else {
break;
}
//更新引用的位置,进行下次循环
child = parent;
parent = (child - 1) / 2;
}
}
//入优先级队列
public void offer(T var){
if(var==null){
throw new NullPointerException();
}
if(size>=capacity){ //扩容
T[] newArr=(T[]) new Object[capacity*2];
for(int i=0;i<arr.length;i++){
newArr[i]=arr[i];
}
arr=newArr; //更新引用的位置,维护原来的数组
arr[size++]=var;
shiftUp(size-1,arr[size-1]);
return;
}
arr[size++]=var;
shiftUp(size-1,arr[size-1]);
return;
}
//出优先级队列
public T poll(){
if(size==0){
return null;
}
Object result=arr[0];
// 交换 0 号元素和 size - 1 号元素
Object tem=arr[0];
arr[0]=arr[size-1];
arr[size-1]=(T) tem;
// size--, 把最后的元素干掉
size--;
// 从 0 号元素开始, 往下进行向下调整
shiftDown(0,arr[0]);
return (T)result;
}
//查看优先级队列的队首元素
public T peek(){
if(size==0){
return null;
}
return arr[0];
}
//判断优先级队列是否为空
public boolean isEmpty(){
return size<=0;
}
public static void main(String[] args) {
MyPriorityQueue<girl1> queue = new MyPriorityQueue<>();
girl1[] girls={
new girl1("静静",18,15,100),
new girl1("甜甜",25,200,50),
new girl1("富婆",40,150,5)
};
for (girl1 g:girls){
queue.offer(g);
}
System.out.println("向钱看起:");
while (!queue.isEmpty()){
girl1 g= queue.poll();
System.out.println(g);
}
System.out.println("分界线=====================");
MyPriorityQueue<Integer> queue1 = new MyPriorityQueue<>();
int[] arr1={5,2,6,8,1,9,10,16,12,11,30,90};
for(int i=0;i<arr1.length;i++){
queue1.offer(arr1[i]);
}
System.out.println("Integer底层自己的排序规则:");
while (!queue1.isEmpty()){
int result =queue1.poll();
System.out.print(result+" ");
}
}
}
运行结果:
3.top-K问题
假设有一亿个数据(int),==》400M要求找出其中1000个最大的数字?
方案一:在内存够的情况下,我们可以直接针对这一亿个数据建大堆,接下来循环1000次取堆顶元素和删除堆顶元素操作。
方案二:假设内存不够的话,我们就在内存中建立一个1000的小堆,将数据从磁盘中读取出来和堆顶元素比较,若比堆顶元素还小,直接抛弃,若 比堆顶元素大,就替换堆顶元素,重复遍历完一亿数据后,内存里的数据就是其中1000个最大的数字。