首先堆的本质是一个棵完全二叉树;
什么是二叉树、满二叉树、完全二叉树?见链接
树、二叉树(满二叉树、完全二叉树)概念:https://blog.csdn.net/sinat_41144773/article/details/89530403
最大堆(大顶堆):顶结点最大,父亲结点比孩子结点大;
最小堆(小顶堆):顶结点最小,父亲结点比孩子结点小;
堆一般都是vecotor或者数组作为实际存储的结构;
由于是完全二叉树,所以父亲结点和孩子结点的索引存在一些关系。
假设我们的堆是用数组存放的;i表示数组索引;
为了方便计算,i=0下标不存放任何数据。从i=1下标开始存放数据。
parent(i) = i/2; //加入孩子结点的索引是i,父亲结点索引是i/2;
leftchild(i) = i*2; // i是父亲结点的索引
rightchild(i)=i*2+1; //i*2+1<=count; count是总结点数;
如果是i=0也存放数据的话。处理起来会麻烦一些。
parent(i) = (i-1)/2;
leftchild(i) = 2*i+1;
rightchild(i) = 2*i+2; //i*2+2<=count-1; count是总结点数;
上面这些规律完全不用记,需要的时候画一个完全二叉树,简单推算一下即可。
接下来讲讲核心思路:
堆有三个核心操作: //方便起见,这里以最大堆为例
1、堆排序:给定一个数组或vector,如何把这个数组或vector变成一个最大堆?
这个过程是堆化的过程,或者是建堆的过程;
2、取出堆顶元素:
堆顶元素取出来后,如何继续保证这个堆是一个最大堆?
思路就是将堆的最后一个节点的值(即完全二叉树的最后一层,从左往右数最后一个节点)与堆顶节点的值进行交换;
该节点跑到的堆顶,之后就是不断向下,使用shiftdown操作。将该节点元素不断往下交换,直到寻找到合适的位置,以维持最大堆的定义;
3、插入一个新元素:
一般插入到队尾,然后不断向上,即使用shiftup操作。往上进行交换操作,也是直到寻找到合适的位置,以维持最大堆的定义;
实际上核心过程就是shiftdown、shiftup、和建堆;
建堆(堆排序)时间复杂度,可参考链接:
建堆的时间复杂度计算:https://blog.csdn.net/wangqing_199054/article/details/20461877
自下而上建堆的时间复杂度:https://www.cnblogs.com/zuotongbin/p/10702001.html
【算导】建堆过程时间复杂度为O(n):https://www.jianshu.com/p/f2ab13f951c9
1 #include <iostream> 2 #include <cassert> 3 #include <queue> 4 #include <stack> 5 6 using namespace std; 7 8 template <typename Item> 9 class MaxHeap{ 10 private: 11 Item* data; 12 int count; 13 int capacity; 14 15 //往堆中插入新元素,一般插入到队尾 16 //需要shiftUp来维持堆的定义 17 void shiftUp(int k){ 18 while(k>1 && data[k/2]<data[k]){ 19 swap(data[k/2],data[k]); 20 k = k/2; 21 } 22 } 23 24 //优化后 25 void shiftUp_Opt(int k){ 26 Item e = data[k]; 27 while(k>1 && data[k/2]<data[k]){ 28 data[k] = data[k/2]; 29 k /= 2; 30 } 31 data[k] = e; 32 } 33 34 //取出最大堆,堆顶元素后,需要shiftdown维持堆的定义。 35 //将一个元素不断往下,直至找到合适的位置,维持堆的定义。 36 void shiftDown(int k){ 37 while(2*k<=count){ 38 //j为要与k交换的元素编号 39 int j = 2*k; 40 //右孩子存在,且比左孩子大 41 if(j+1<=count && data[j+1]>data[j]) 42 j++; 43 if(data[j]<data[k]) break; //如果交换到头了,就直接break 44 swap(data[j],data[k]); 45 k = j; //更新k,继续往下一轮的交换 46 } 47 } 48 49 //优化后 50 void shiftDown_Opt(int k){ 51 Item e = data[k]; 52 while(2*k<=count){ 53 int j = 2*k; 54 if(j+1<=count && data[j+1]>data[j]) j++; 55 if( e > data[j]) break; 56 data[k] = data[j]; 57 k = j; 58 } 59 data[k] = e; 60 } 61 62 63 public: 64 //构造一个空堆 65 MaxHeap(int capacity){ 66 data = new Item[capacity+1]; 67 count = 0; 68 this->capacity = capacity; 69 } 70 71 //构造函数,给定一个数组构造一个最大堆 72 MaxHeap(Item arr[], int n){ 73 data = new Item[n+1]; 74 capacity = n; 75 76 for(int i = 0; i<n; i++) 77 data[i+1] = arr[i]; 78 count = n; 79 80 //堆化核心代码 81 for(int i = count/2; i>=1; i--) 82 shiftDown(i); 83 84 } 85 86 ~MaxHeap(){ 87 delete[] data; 88 } 89 90 //往堆中插入元素 91 void insert(Item item){ 92 assert(count+1<=capacity); 93 data[count+1] = item; 94 shiftUp(count+1); 95 count++; 96 } 97 98 //取出最大元素 99 Item extractMax(){ 100 assert(count >0); 101 Item ret = data[1]; 102 swap(data[1], data[count]); 103 count --; 104 shiftDown(1); 105 return ret; 106 } 107 108 //堆是否为空 109 bool isEmpty(){ 110 return count == 0; 111 } 112 113 //堆中元素个数 114 int size(){ 115 return count; 116 } 117 118 119 }; 120 121 //普通版本的堆排序,将所有元素依次添加到堆中,然后再将所有元素依次从堆中取出,即完成了排序; 122 //无论是创建堆的过程还是从堆中依次取出元素的过程,时间复杂度均为O(nlogn) 123 //整个堆排序的整体时间复杂度为(nlogn) 124 template <typename T> 125 void heapSort1(T arr[], int n){ 126 MaxHeap<T> maxHeap = MaxHeap<T>(n); 127 for(int i = 0; i<n; i++) 128 maxHeap.insert(arr[i]); 129 for(int i = n-1; i>=0; i--) 130 arr[i] = maxHeap.extractMax(); 131 } 132 133 //借助heapify进行堆排序 134 template <typename T> 135 void heapSort2(T arr[],int n){ 136 MaxHeap<T> maxHeap = MaxHeap<T>(arr,n); 137 for(int i = n-1; i>=0; i--) 138 arr[i] = maxHeap.extractMax(); 139 }