堆这种数据结构的一种典型应用——优先队列(Priority Queue)
普通队列:先进先出;后进后出。
优先队列:出队顺序和入队顺序无关;和优先级相关。
优先队列最典型的应用就是在计算机的操作系统中执行任务,当操作系统执行多个任务时,操作系统是将cpu的执行周期划成了多个时间片,在每个时间片里只能执行一个任务,那么究竟先执行哪个任务呢?答案是每个任务都有一个优先级,操作系统将每次动态地选择一个优先级最高的任务开始执行,这个时候就需要使用优先队列。也就是说,所有的任务都进入优先队列,每一次由优先队列调度决定cpu执行哪个任务。
为什么是“动态地”?
因为每一次cpu处理任务的同时,还会有其他新的任务的进入。
既然优先队列对于动态的数据处理有很好的使用效果,那么对于静态的数据处理效果怎么样?
答案是在处理静态的数据上,优先队列也是很有优势的。
例如:在100000000个元素中选出前100名?
抽象一下就是:在n个元素中选出前m个元素?
容易想到的解决办法就是,对这n个元素进行排序,然后选出前m个元素。这时的时间复杂度为O(nlogn)。
但是如果我们使用优先队列,那么我们的时间复杂度将可以达到O(nlogm)。
如果要处理的数据基数庞大的话,那么优先队列的使用将使得整个处理过程快十几倍,甚至更明显。
优先队列的主要操作:
入队
出队(取出优先级最高的元素)
优先队列的实现(普通数组、顺序数组、堆):
虽然看起来,使用堆这种数据结构所产生的时间复杂度在入队时比不上普通数组,在出队时比不上顺序数组。可是平均来讲,使用堆来实现优先队列来处理一个系统任务的时间复杂度要大大地低于使用数组来实现。
最极端的情况下,对于总共有N个请求:
使用普通数组或者顺序数组,最差的情况:O(n2)
使用堆,时间复杂度可以稳定在:O(nlgn)
什么是堆?
堆是一种树形结构,其中最经典的就是二叉堆(Binary Heap)。它所对应的就是二叉树。
但是这个二叉树有几个特点(对于大根堆(或者叫最大堆)):
1.在该二叉树上任何一个节点总是不大于它的父亲节点;
2.堆对应的二叉树是一颗完全二叉树。
例:
注意,在上面列出的特点1中所讲,并不意味着层数越大数字越小(在最大堆中),例如上图中19和13的大小比较。
最大堆的实现:
实现堆可以使用一个节点附带两个指针的方式实现(树的实现方式)。
经典的实现方式是使用数组去存储堆。我们之所以可以使用数组去实现一个堆,原因在于,堆是一个完全二叉树。
如果我们给一个堆的每个元素标一下号的话。像这样:
我们可以看到,每一个节点的左边子节点是父节点的二倍,例如2==1*2;4==2*2;6==3*2。
每个节点的右边子节点是父节点的二倍加一,例如3==1*2+1;5==2*2+1;7==3*2+1。
注意,0号索引是不使用的。
即得到公式:
注意,除法为计算机除法,除不尽则取整。
代码(搭建最大堆大体框架):
package com.heap; public class MaxHeap { private int[] arr; private int count;//堆中元素个数 public MaxHeap(int capacity){ arr=new int[capacity];//由用户指定该最大堆的容量 count=0; } //返回该最大堆的元素个数 public int size(){ return count; } public static void main(String[] args) { MaxHeap heap=new MaxHeap(100); System.out.println(heap.size()); } }
如何将一个元素加入到最大堆?
例如将一个元素(52)加入到之前的最大堆中:
这时候,新插入的元素位于我们存储该最大堆的数组的末尾,在即是示意图中的位置。
我们可以看到,在新插入元素所在的最小子树(即16、15、52元素所在的树)中,并不符合最大堆的定义。
那么我们要做的是将新插入元素和其父结点进行比较大小,若新插入结点比其父结点大,则交换两者之间的位置,然后再比较新插入结点交换后的位置和它当前的父结点比较大小,若还是比其父结点大则继续交换,直到新插入的结点在其当前位置上不大于其当前父结点。
代码:
package com.heap; public class MaxHeap { private int[] arr; private int count;//堆中元素个数 private int capacity; public MaxHeap(int capacity){ arr=new int[capacity];//由用户指定该最大堆的容量 this.capacity=capacity; count=0;//初始化为0 } //判断当前堆是否为空 public Boolean isEmpty(){ return count==0; } //返回该最大堆的元素个数 public int size(){ return count; } //插入元素到最大堆 public void insert(int num){ if(count+1>capacity) System.out.println("容量已满,不能插入"); else shifUp(count++); } //将堆中元素调整到合适位置 private void shifUp(int k) { //如果当前元素比其父结点大,则交换一下两者的位置 if(k>1&&arr[k/2]<arr[k]){ int temp=arr[k/2]; arr[k/2]=arr[k]; arr[k]=temp; } else return;//循环总结条件 shifUp(k/=2); } public static void main(String[] args) { MaxHeap heap=new MaxHeap(100); heap.insert(3); System.out.println(heap.size()); } }