底层结构 入队 出队(获取最大元素)
普通线性结构 O(1) O(n)
顺序线性结构 O(n) O(1)
O(log n) O(log n)

二叉堆的性质

  • 二叉堆是一棵完全二叉树

image.png

  • 堆中的某个结点值总是不大于其父节点的值

image.png

堆的基本结构

  • 使用数组存储二叉堆,下标从1开始

image.png

  • 使用数组存储二叉堆,下标从0开始

image.png

白板编程最大堆的一些准备工作:

  1. public class MaxHeap<E extends Comparable<E>> {
  2. private E[] data;
  3. private int size;
  4. //记录堆中元素个数
  5. public MaxHeap(int capacity){
  6. data=(E[])new Comparable[capacity];
  7. }
  8. public MaxHeap(){
  9. this(10);
  10. }
  11. //返回堆中元素个数
  12. public int size(){
  13. return size;
  14. }
  15. //判断堆是否为空
  16. public boolean isEmpty(){
  17. return size==0;
  18. }
  19. //返回一个索引的父节点的索引
  20. private int parent(int index){
  21. if(index==0){
  22. throw new IllegalArgumentException("index-0 does not have parment");
  23. }
  24. return (index-1)/2;
  25. }
  26. //返回一个索引的左孩子节点的索引
  27. private int leftChild(int index){
  28. return 2*index+1;
  29. }
  30. //返回一个索引的左孩子节点的索引
  31. private int rightChild(int index){
  32. return 2*index+2;
  33. }
  34. private void swap(int i,int j){
  35. if(i<0 || i>=size || j<0 || j>=size){
  36. throw new IllegalArgumentException("Index is illegal");
  37. }
  38. E tmp=data[i];
  39. data[i]=data[j];
  40. data[j]=tmp;
  41. }
  42. //调整数组大小
  43. private void resize(int newCapacity){
  44. E[] newData=(E[])new Object[newCapacity];
  45. for(int i=0;i<size;i++){
  46. newData[i]=data[i];
  47. }
  48. data=newData;
  49. }
  50. }

添加元素和取出元素

  • 向堆中添加元素和上浮操作
  1. //向堆中添加元素
  2. //时间复杂度 O(log n)
  3. public void add(E e){
  4. if(size==data.length){
  5. resize(data.length*2);
  6. }
  7. data[size]=e;
  8. size++;
  9. swim(size-1);
  10. }
  11. //对索引为k的元素,进行上浮操作,得到一个新的最大堆
  12. private void swim(int k){
  13. while(k>0 && data[k].compareTo(data[parent(k)])>0){
  14. swap(k,parent(k));
  15. k=parent(k);
  16. }
  17. }
  • 向堆中取出元素和下沉操作
  1. //查看堆中最大元素
  2. public E findMax(){
  3. if(size==0){
  4. throw new IllegalArgumentException("Can not find max when maxheap is empty!");
  5. }
  6. return data[0];
  7. }
  8. //从堆中取出元素
  9. //时间复杂度O(log n)
  10. public E extractMax(){
  11. if(size==0){
  12. throw new IllegalArgumentException("MaxHeap is empty");
  13. }
  14. E ret=findMax();
  15. swap(0,size-1);
  16. size--;
  17. sink(0);
  18. return ret;
  19. }
  20. private void sink(int k){
  21. while (leftChild(k)<size){ //leftChild(k)<size 下标为k的元素存在左子树
  22. int j=leftChild(k);
  23. if(j+1<size &&
  24. data[j].compareTo(data[j+1])<0){
  25. j=j+1;
  26. }
  27. //j是data[leftChild(k)]和data[rightChild(k)]的较大值的下标
  28. if(data[k].compareTo(data[j])>=0){
  29. break;
  30. }
  31. swap(k,j);
  32. k=j;
  33. }
  34. }

replace和heapify

  • replace:取出最大元素后,放入新元素

实现一:先extractMax(),再add(),两次O(log n)操作

实现二:直接替换堆顶元素,在进行下沉操作,一次O(log n)操作

  1. //replace:取出最大元素后,放入新元素
  2. public E replace(E e){
  3. E ret=data[0];
  4. data[0]=e;
  5. sink(0);
  6. return ret;
  7. }
  • heapify:将任意数组整理成堆的形状

将数组看成一颗完全二叉树,从该二叉树的最后一个分叶子节点开始,进行下沉操作。

image.png

  1. //heapify:将任意数组整理成堆的形状
  2. public MaxHeap(E[] arr){
  3. data=(E[])new Comparable[arr.length];
  4. for(int i=0;i<arr.length;i++){
  5. data[i]=arr[i];
  6. }
  7. size=arr.length;
  8. for(int i=parent(arr.length-1);i>=0;i--){
  9. sink(i);
  10. }
  11. }

基于堆的优先队列

  1. public class PriorityQueue<E extends Comparable<E>> implements Queue<E>{
  2. private MaxHeap<E> maxHeap;
  3. public PriorityQueue(){
  4. maxHeap=new MaxHeap<>();
  5. }
  6. @Override
  7. public int getSize() {
  8. return maxHeap.size();
  9. }
  10. @Override
  11. public boolean isEmpty() {
  12. return maxHeap.isEmpty();
  13. }
  14. @Override
  15. public void enqueue(E e) {
  16. maxHeap.add(e);
  17. }
  18. @Override
  19. public E dequeue() {
  20. return maxHeap.extractMax();
  21. }
  22. @Override
  23. public E getFront() {
  24. return maxHeap.findMax();
  25. }
  26. }

Java中的PriorityQueue

LeetCode 347 Top K Frequent Elements

  1. private class Pair implements Comparable<Pair> {
  2. int numFreq;
  3. int num;
  4. Pair(int numFreq,int num){
  5. this.numFreq=numFreq;
  6. this.num=num;
  7. }
  8. @Override
  9. public int compareTo(Pair o) {
  10. return this.numFreq-o.numFreq;
  11. }
  12. }
  13. public List<Integer> topKFrequent(int[] nums, int k) {
  14. //统计数字出现的频率
  15. Map<Integer,Integer> map=new HashMap<>();
  16. for(int num:nums){
  17. int freq=map.get(num)==null?0:map.get(num);
  18. map.put(num,++freq);
  19. }
  20. //维护一个优先队列,最小堆,维护当前频率最高的元素
  21. PriorityQueue<Pair> priorityQueue=new PriorityQueue<>();
  22. //pair存的是(频率,元素)的形式
  23. for(Integer num:map.keySet()){
  24. int numFreq=map.get(num);
  25. if(priorityQueue.size()==k){
  26. if(numFreq>priorityQueue.peek().numFreq){
  27. priorityQueue.poll();
  28. priorityQueue.add(new Pair(numFreq,num));
  29. }
  30. }else{
  31. priorityQueue.add(new Pair(numFreq,num));
  32. }
  33. }
  34. List<Integer> ret=new ArrayList<>();
  35. while(!priorityQueue.isEmpty()){
  36. ret.add(priorityQueue.poll().num);
  37. }
  38. return ret;
  39. }

使用匿名比较器对象改进:

  1. public List<Integer> topKFrequent(int[] nums, int k) {
  2. //统计数字出现的频率
  3. Map<Integer,Integer> map=new HashMap<>();
  4. for(int num:nums){
  5. int freq=map.get(num)==null?0:map.get(num);
  6. map.put(num,++freq);
  7. }
  8. //维护一个优先队列,最小堆,维护当前频率最高的元素
  9. PriorityQueue<Integer> priorityQueue=new PriorityQueue<>(new Comparator<Integer>() {
  10. @Override
  11. public int compare(Integer a, Integer b) {
  12. return map.get(a)-map.get(b);
  13. }
  14. });
  15. //pair存的是(频率,元素)的形式
  16. for(Integer num:map.keySet()){
  17. priorityQueue.add(num);
  18. if(priorityQueue.size()>k) {
  19. priorityQueue.poll();
  20. }
  21. }
  22. List<Integer> ret=new ArrayList<>();
  23. while(!priorityQueue.isEmpty()){
  24. ret.add(priorityQueue.poll());
  25. }
  26. Collections.reverse(ret);
  27. return ret;
  28. }