深入浅出理解堆排序

179 阅读3分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

前言

 每天一小步,成功一大步。大家好,我是程序猿小白 gw_GW,很高兴能和大家一起学习每天小知识。

以下内容部分来自于网络,如有侵权,请联系我删除,本文仅用于学习交流,不用作任何商业用途。

摘要

 本文主要讲诉堆排序。内容有堆排序的应用,以及堆排序的插入,删除,修改操作。

1. 堆排序简介

堆排序是利用堆这种数据结构实现的一种排序算法。都是近似完全二叉树的结构。堆排序的平均时间复杂度为 Ο(nlogn)。分为大顶堆和小顶堆。

  1. 大顶堆。每个节点的值都大于等于其孩子节点的值。
  2. 小顶堆。每个节点的值都小于等于其孩子节点的值。

2. 堆排序的应用

在N个元素中取TOPK个小(大)的元素,例如:有一万个数据,但我们只需要前100个数据。那我们就可以使用堆排序。建立一个一位数组,大小为K+1,从一万个元素中逐个取出元素,如果比key小,就放到数组中。(根据具体需求而定),建立堆。

3. 堆排序的操作

堆排序的主要操作就是插入,删除,更新元素。以下就以小顶堆为例讲诉三个主要操作。

因为二叉树的特点,第i个节点其左孩子节点为2i,右孩子节点为2i+1,父亲节点为2*i。为了方便计算我们就把数组从下标1开始存取,并把下标为0的位置放置元素的个数。

3.1 插入元素

主要思想:

  1. 把该元素放到数组末尾
  1. 把元素逐渐上移,即如果小于父节点就和父节点进行交换。直到上移不动为止,上移不动有两种情况:已经到了顶 或者 碰到了更小的父元素。
  2. 插入元素后,把长度+1
 void insert( int x, int heap[]){
     int child = heap[0] + 1;
     int father = (heap[0] + 1) / 2;
     heap[child] = x;//插入新元素 
     while(father && x < heap[father]){//如果没超过顶,并且x一直比父元素小,就让x一直上移 
         swap(&heap[father],&heap[child]);       
         child = father;
         father /= 2;
     }
     heap[0]++;
 }

3.2 删除元素

删除元素就是删除堆顶元素。主要思想是:

  1. 把堆顶元素保存下来,方便后面返回。
  2. 把最后一个元素覆盖掉堆顶元素,并把长度-1
  3. 重新调整堆为最小堆。即把替换后的堆顶元素元素逐渐下移。直到移不动为止。下移不动有两种情况:已经移动到了叶子节点 或者 孩子节点比该节点要大。
 int del( int heap[]){
     int key = heap[1];
     heap[1] = heap[heap[0]];
     heap[0]--;
     int i = 1;
     int leftChild = 2*i;
     int rightChild = 2*i + 1;
     if(heap[0]){//如果堆不为空 
         while(1){//一共有两种情况可以退出循环,一种是当前节点已经没有孩子了,即碰到了叶子节点
                 //一种是还当前节点有孩子,但是当前节点的值已经小于了孩子节点的值 
             int min;
             if((2*i + 1) <= heap[0]){//如果有两个孩子 ,找两个孩子的最小值 
                 min = heap[2*i] < heap[2*i + 1]?2*i:(2*i + 1);
             }
             else if(2*i <= heap[0]){//如果只有一个孩子,最小值就是左孩子 
                 min = 2*i;
             }
             else{//如果没有孩子,就不需要往下比较 
                 break; 
             }
             if(heap[i] > heap[min]){//如果父节点的值比孩子节点的值大就交换父节点和孩子节点。 
                 swap(&heap[i],&heap[min]); 
                 i = min;
             }
             else{
                 break;
             }           
         }
     }   
     return key; 
 }

3.3 更新元素

更新元素的主要思想是:

  1. 更新元素

  2. 调整跟新后的堆为最小堆。即把更新后的元素上移或下移。

    这里需要注意的是,应该先判断元素是否上移,然后再判断元素是否下移。因为要判断元素是否下移需要判断该节点是否含有孩子节点。

 void update( int x, int pos, int heap[]){
     heap[pos] = x;
     int father = pos/2;
     while(father && pos*2 <= heap[0]){//终止条件是上移到了顶,或者到达了叶子节点 
         //先判断能不能上移 
         if(heap[father] > heap[pos]){
             swap(&heap[father],&heap[pos]);
             pos = father;
             father /= 2;
         }
         else{//判断能不能下移 
             int min; 
             if( pos*2+1 <= heap[0]){//如果有两个孩子,找两个孩子的最小值 ,判断能不能下移 
                 min = heap[pos*2] < heap[pos*2+1]?pos*2:(pos*2+1); 
             }
             else if(pos*2 <= heap[0]){//如果只有一个孩子,最小值就是左孩子 
                 min = pos*2;
             }
             else{//如果没有孩子
                 break; 
             }
             if(heap[pos] > heap[min]){//如果父节点的值比孩子节点的值大就交换父节点和孩子节点。 
                 swap(&heap[pos],&heap[min]); 
                 pos = min;
             }
             else{
                 break;
             }   
         }
             
     }    
 }

结语

以上就是我对推排序的一些浅见,如有不正之处,欢迎掘友们批评指正。