:::info 堆结构逻辑上是一个完全二叉树 :::

🚚引子-完全二叉树与堆结构;

完全二叉树

:::info

  • 每层要么是满的,要么是左边先满右面再满
  • 数组从0位置出发的连续一段,可以组成完全二叉树; ::: image.pngimage.png
    数组<=>完全二叉树 :::info 位置i:

  • left child:2*i+1

  • right child:2*i+2
  • father: (i-1)/2

和满二叉树的性质有关;N节点的满二叉树,最后一行有(N)/2个节点; :::

堆是一种完全二叉树,可以分为大根堆和小根堆; :::info 大根堆:对于每一个子堆,根节点的值都是最大的;
小根堆:与大根堆相反; ::: 堆结构中最重要的操作是heapinsert和heapify;通过这两种操作可以将数组转化成大根堆或者小根堆;

heapinsert

:::info

  • 每次加入一个数,要先和其父节点比较,
  • 若是比父节点大,则交换,并且将当前节点变成此节点;
  • 直到遇到比插入的数要大的父节点或是到根节点; :::
    1. void heapInsert(vector<int> &vec, int index){
    2. //插入的数在index位置;假设index之前的数已经形成堆了;
    3. while(vec[index]>vec[(index-1)>>1]){
    4. swap(vec,index,(index-1)>>1);
    5. index=(index-1)>>1;
    6. }
    7. }
    :::tips 可以通过上述操作看出大根堆的最大值在堆顶
    heapinsert操作主要是对堆某节点的上游节点操作; :::

heapify

若是要求删除堆顶,并将最后位置的数放在堆顶位置;
若是改变了某位置的数,如何重新变成堆?

  1. void heapify(vector<int> &vec,int index,int heapSize ){
  2. //index 表示从哪里开始做heapify
  3. int left=index*2+1;
  4. while(left<heapSize){
  5. //当仍然有child时
  6. int largest=left+1<heapSize&&vec[left+1]>vec[left]?left+1:left;
  7. //判定right child 存,largest取left和right中较大的那个
  8. largest=vec[largest]>vec[index]?largest:index;
  9. //largest取father和child中较大的那个
  10. if(largest==index)
  11. break;
  12. swap(vec,largest,index);
  13. index=largest;
  14. left=index*2+1;
  15. }
  16. }

:::info heapify操作后,该数组会变成合法的堆吗?
不一定,若是之前已经成堆了,可以;否则不一定。 ::: :::tips heapify操作主要是对堆的下游节点进行比对
至此,堆结构中两个最重要的操作已经建立

  • 想要将一个数组变成堆:对于所有位置依次进行heapInsert

heapinsert是向上进行的,和上层的数据油管
heapify是向下进行的,和下层的数据油管;
若是将堆中任意位置的数据改变了;

  • 变小了->向下经历一个heapify,将child中的大值顶上来
  • 变大了->向上经历一个heapinsert,把此值顶替到根节点; :::

:::success 调整的复杂度:
因为高度是log N级别;而调整一般都是沿着高度进行的,所以时间复杂度是:
🗼03_堆排序及总结 - 图3 :::


堆排序

:::info 解析:

  1. 将数组变成堆 heapinsert
  2. 堆顶与堆尾部的数交换,数组末尾即为当前堆的最大值
  3. heapsize—
  4. heapify
  5. 重复2-4知道heapsize=0; :::
    1. void heapSort(vector<int> &vec){
    2. if(vec.size()<2) return;
    3. for(int i=0;i<vec.size();i++){
    4. \\变成heap;
    5. heapInsert(vec,i);
    6. }
    7. int heapsize=vec.size();
    8. while(heapsize>0){
    9. swap(vec,0,heapsize);
    10. heapify(vec,0,--heapsize);
    11. }
    12. }
    时间复杂度🗼03_堆排序及总结 - 图4
    额外的空间复杂度🗼03_堆排序及总结 - 图5

复杂度更低的heapinsert

若是一开始给出了所有的数据,可以不按照从上往下做heapinsert,而是从下往上做heapify
复杂度🗼03_堆排序及总结 - 图6

  1. for(int i=vec.size()-1;i>=0;i--){
  2. heapify(vec,i,vec.size());
  3. }

:::tips 堆结构比堆排序更重要的多 :::

题目

image.png :::info 解析:
若是采用暴力搜索,复杂度必然是O(N^2)
每个元素移动距离不超过k,假设是从小到大排序,从0到6的7个元素生成一个小根堆,那么堆顶必然是整个数组中最小的元素。之后将下一个数加入,对1-7生成堆(heapinsert) ::: image.png :::tips

  • 0-K生成一个小根堆(heapify)
  • 弹出堆顶,将其换成下一个数,然后heapify
  • 将数组中所有数加入后,依次弹出堆顶,并在生成堆; :::

    1. void heapInsert(vector<int> &vec, int index);
    2. void heapify(vector<int> &vec,int index,int heapSize);
    3. void KheapSort(vector<int> &vec,int K){
    4. vector<int> temp;
    5. int index=0;
    6. for(;index<min(temp.size(),K+1);index++){
    7. heapinsert(temp,index);
    8. }
    9. int i=0;
    10. for(;i<temp.size(),index<temp.size();i++,index++)
    11. {
    12. vec[i]=temp[0];
    13. temp[0]=temp[index];
    14. heapify(temp,0,K+1)
    15. }
    16. for(;i<temp.size();i++){
    17. vec[i]=temp[0];
    18. temp[0]=temp[K--];
    19. heapify(temp,0;K+1);
    20. }
    21. }

    实际上C++中自带堆结构:
    (11条消息) c++重拾 STL之heap(堆)_元气满满晨-CSDN博客_c++ heap

    1. #include<algorithm>
    2. #include<vector>
    3. void KheapSort(vector<int> &vec,int K){
    4. K=K>vec.size()?vec.size()-1:K;
    5. vector<int> heap(vec.begin(),&vec[K]);
    6. make_heap(heap.begin(),heap.end(),less<int>());
    7. int index=K+1;
    8. int i=0;
    9. for(;index<vec.size();index++,i++)
    10. {
    11. vec[i]=heap[0];
    12. pop_heap(heap.begin(),heap.end(),less<int>());
    13. heap.pop_back();
    14. heap.push_back(vec[index]);
    15. push_heap(heap.begin(),heap.end(),less<int>());
    16. }
    17. for(;i<vec.size();i++){
    18. vec[i]=heap[0];
    19. pop_heap(heap.begin(),heap.end(),less<int>());
    20. heap.pop_back();
    21. }
    22. }

    桶排序

    :::info 之前涉及的插入、归并、冒泡、快排序、堆排序等都是基于比较的排序 ::: 不基于比较的排序: :::success

  • e.g.统计员工的年龄

  • 指定一个18-100的词频数组
  • 出现一个相应年龄的便在相应位置++
  • 最后将词频数组还原

复杂度🗼03_堆排序及总结 - 图9

  • 缺陷是词频表范围是有限or较小的 ::: 不基于比较的排序都是基于数据状况进行的,应用范围应该比基于比较的排序小

基数排序/桶排序

排序算法稳定性汇总

:::info 同样值的个体之间,若是不因为排序而改变相对次序,那么这个排序就是稳定的; ::: :::tips 不具备稳定性的排序:

  • 选择排序
  • 快速排序
  • 堆排序

具备稳定性的排序:

  • 冒泡排序
  • 插入排序
  • 归并排序
  • 一切桶排序思想下的排序 :::

🗼03_堆排序及总结 - 图10 :::tips

  • 基于比较的排序能否做到时间复杂度小于🗼03_堆排序及总结 - 图11?不能!
  • 能否在此时间复杂度下,达到空间复杂度在🗼03_堆排序及总结 - 图12以下且稳定?不能! :::

常见的坑

image.png

常见改进

:::info

  • 充分利用两种时间复杂度的排序的优势
  • 稳定性的考虑 ::: 综合排序

  • 小样本量时用插入排序

  • 整体调度时用快排序

(动态的调用)

基础类型用快排,非基础类型用归并?why?稳定性问题