数据结构与算法-堆排序

188 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第18天,点击查看活动详情

前言

在此之前已经了解冒泡、选择、插入、希尔、快速、归并排序算法。还有最后一个排序算法需要了解学习,那就是堆排序。

堆排序

堆排序并不和堆栈有关,它是一种特殊二叉树(二叉堆)。特点如下:

  • 完全二叉树
  • 堆中节点数据值不大于下节点以及不小于上节点数据值
  • 大根堆就是每个根节点都大于等于它的左右子节点(下节点)
  • 小根堆就是每个根节点都小于等于它的左右子节点(下节点)
二叉树满二叉树完全二叉树
image.pngimage.pngimage.png
节点的子节点不超过2的有序树叶子节点外其他每个节点都是2叶子结点只能出现再最下层或次下层,结点必须是在树左边

大根堆

大根堆如下所示,最大的数值在最前面依次减小。

1097421

image.png

大根堆完全二叉树可以推导出: 对应下标位置为K的节点,子节点的下标位置分别为:

  • 左子节点下标 = 2*K + 1

  • 右子节点下标 = 2*(K + 1) 比如下标位置K = 1,对应节点数值是9。左子节点的下标是3,对应节点数值是4;右子节点的下标是4,对应节点数值是2。

  • 最后一个非叶子节点的下标推导公式(N / 2) - 1,N是数组长度。 比如例子的数组长度是6,非叶子节点下标=2,数组是7。

原理

整理大根堆

随机给定一个完全二叉树数组,但不符合大根堆定义。通过堆排序算法通过调整使数组变成大根堆。从最后一个非叶子节点开始,从下到上,从右往左调整。

235530410010449

image.png

因为最后一个非叶子节点通过公式可以得出下标是3,值是4。接着将最后一个非叶子节点和它的叶子节点进行比较交换

image.png

从右到左分别是下标1下标2下标0执行比较交换操作。

image.png

最终得到大根堆结果

image.png

排序过程

实现最大堆最后就开始做排序操作,然二叉树调整成有序。实现有序的规则如下:

  • 堆顶部数据和堆尾部数据交换(交换后的尾部数据可以看作是排序完成的最大值)
  • 交换之后再重新调整二叉树,重新调整成为最大堆(步骤如上一样的过程)
  • 不断重复堆顶部数据和堆尾部数据交换,直到交换结束

image.png

第一次交换以后下标7数值为100排序完成,不在参与接下来的操作,剩余数据在进行大根堆调整。

image.png

调整成为大根堆之后在进行堆顶部数据和堆尾部数据交换

image.png

接着继续重复步骤【堆尾部数据不参与操作】【调整成为大根堆】【堆顶部数据和堆尾部数据交换】.....

最终获取到的二叉树如下所示对应下标的数值就是有序数组。

image.png

算法代码

算法部分实现过程会比较多可以将步骤拆分开:调整成大根堆部分、排序部分。


void adjustHeap(int[] datas,int i,int length){
    int maxIndex = i; // 根节点下标
    int left = 2 * i +1; // 根节点的左节点下标
    int right = 2 *( i +1); // 根节点的右节点下标
     if(left < length && datas[left] > datas[maxIndex]){//左节点大于父节点
         maxIndex = left;
     }
     if(right < length && datas[right] > datas[maxIndex]){ //右节点大于父节点
         maxIndex = left;
     }
     if(maxIndex != i){ //下标发生变化
         swap(datas,maxIndex,i);
         adjustHeap(datas,maxIndex,length); //递归处理
     }
}
void swap(int datas,int new,int old){
    int temp = datas[old];
    datas[old] = datas[new];
    datas[new] = temp
}

void createMaxHeap(int[] datas){
    for(int i = datas.length / 2 - 1;i>=0;i--){
        adjustHeap(datas,i,datas.length);
    }
}

void main(){
    int[] datas;
    int length = datas.length;
    createMaxHeap(datas); // 先调整为大根堆
    while(length > 0){ // 直到数组所有数值都移除
        swap(datas,0,length - 1);// 堆顶部数据和堆尾部数据交换
        length--; // 移除最大值
        ajustHeap(datas,0,length); 再调节成大根堆
    }
}

快速排序平均是O(NlogNNlogN),堆排序同样也是O(NlogNNlogN)。

总结

堆排序利用二叉树中最大堆的性质来实现排序算法。利用固定公式:已知下标位置为K的节点推算出子节点的下标位置;已知数组长度推算出最后非叶子节点的下标等。堆排序过程就是从N/2的节点和其子节点选择最大最小值,然后是n/2-1,n/2-2...直到1,在这个过程中选择比较交换操作很有可能会影响破坏原来最大堆的性质,然后再进行数据调整变为最大堆再进行排序,这也得出堆排序的不稳定性。但堆排序在时间复杂度上还是有一定优势,例如在获取到数组最大值的使用还是比较快的。