高效排序算法——堆排序
  pYQ3D3NPYfjH 2023年11月02日 33 0

堆排序是一种高效的排序算法,通过构建最大堆或最小堆来实现排序。它的时间复杂度为O(nlogn),适用于大规模数据的排序。


一、堆排序的原理

堆是一种特殊的完全二叉树,它有以下特点:

最大堆:任意节点的值都大于或等于其子节点的值。

最小堆:任意节点的值都小于或等于其子节点的值。

堆排序的基本思想是利用堆的性质,将待排序的序列构建成一个堆,然后每次从堆顶取出最大(或最小)的元素放到已排序的序列中,再将剩余元素重新构建成一个堆,循环执行这个过程,直到排序完成。

堆排序的具体步骤如下:

构建最大堆或最小堆:从最后一个非叶子节点开始,依次向上调整每个节点,使其满足堆的性质。

取出堆顶元素:将堆顶元素与最后一个元素交换,然后将堆的大小减1。

重新构建堆:对交换后的堆顶元素进行向下调整操作,使其满足堆的性质。

重复执行步骤2和步骤3,直到堆的大小为1。


二、堆排序的实现

堆排序的实现可以分为两个部分:堆的构建和堆的调整


堆的构建

堆的构建是将一个无序的序列构建成一个堆的过程。从最后一个非叶子节点开始,对每个节点进行向下调整操作,使其满足堆的性质。

以构建最大堆为例,具体实现步骤如下:

从最后一个非叶子节点开始,依次向上调整每个节点。

对于每个节点,比较其与左右子节点的大小,如果大于左右子节点的值,则不需要调整;否则,将该节点与左右子节点中较大的节点交换位置,并继续向下调整交换后的节点,直到满足堆的性质。


构造堆

在构造有序堆时,我们开始只需要扫描一半的元素(n/2-1 ~ 0)即可,因为(n/2-1)~0的节点才有子节点,n=8,(n/2-1) = 3  即3 2 1 0这个四个节点才有子节点


高效排序算法——堆排序_子节点


所以代码4~6行for循环的作用就是将3 2 1 0这四个节点从下到上,从右到左的与它自己的子节点比较并调整最终形成大顶堆,过程如下:

第一次for循环将节点3和它的子节点7 8的元素进行比较,最大者作为父节点(即元素60作为父节点)

 

高效排序算法——堆排序_算法_02


 

第二次for循环将节点2和它的子节点5 6的元素进行比较,最大者为父节点(元素80作为父节点)


 

高效排序算法——堆排序_堆排序_03

 

第三次for循环将节点1和它的子节点3 4的元素进行比较,最大者为父节点(元素70作为父节点)

高效排序算法——堆排序_排序算法_04

 

第四次for循环将节点0和它的子节点1 2的元素进行比较,最大者为父节点(元素80作为父节点)


高效排序算法——堆排序_子节点_05


注意,元素20和元素80交换后,20所在的节点还有子节点,所以还要再和它的子节点5 6的元素进行比较,这就是28行代码 i = j 的原因

 

有序堆已构造好

高效排序算法——堆排序_子节点_06

 

调整堆

下面进行while循环

堆顶元素80和尾40交换后-->调整堆

高效排序算法——堆排序_算法_07


堆顶元素70和尾30交换后-->调整堆

高效排序算法——堆排序_堆排序_08


堆顶元素60尾元素20交换后-->调整堆

高效排序算法——堆排序_子节点_09


其他依次类推,最终已排好序的元素如下:


高效排序算法——堆排序_父节点_10

 

堆的调整是将堆顶元素与最后一个元素交换后,对堆顶元素进行向下调整操作的过程。

以调整最大堆为例,具体实现步骤如下:

将堆顶元素与最后一个元素交换位置。

对交换后的堆顶元素进行向下调整操作,与左右子节点中较大的节点交换位置,直到满足堆的性质。


动图演示

高效排序算法——堆排序_排序算法_11

高效排序算法——堆排序_堆排序_12


算法步骤

  1. 创建一个堆 H[0……n-1];
  2. 把堆首(最大值)和堆尾互换;
  3. 把堆的尺寸缩小 1,并调用 shift_down(0),目的是把新的数组顶端数据调整到相应位置;
  4. 重复步骤 2,直到堆的尺寸为 1。


代码实现

#include <stdio.h>
#include <stdlib.h>

void swap(int *a, int *b) {
 int temp = *b;
 *b = *a;
 *a = temp;
}

void max_heapify(int arr[], int start, int end) {
 // 建立父節點指標和子節點指標
 int dad = start;
 int son = dad * 2 + 1;
 while (son <= end) { // 若子節點指標在範圍內才做比較
 if (son + 1 <= end && arr[son] < arr[son + 1]) // 先比較兩個子節點大小,選擇最大的
            son++;
 if (arr[dad] > arr[son]) //如果父節點大於子節點代表調整完畢,直接跳出函數
 return;
 else { // 否則交換父子內容再繼續子節點和孫節點比較
            swap(&arr[dad], &arr[son]);
 = son;
 = dad * 2 + 1;
 }
 }
}

void heap_sort(int arr[], int len) {
 int i;
 // 初始化,i從最後一個父節點開始調整
 for (i = len / 2 - 1; i >= 0; i--)
        max_heapify(arr, i, len - 1);
 // 先將第一個元素和已排好元素前一位做交換,再重新調整,直到排序完畢
 for (i = len - 1; i > 0; i--) {
        swap(&arr[0], &arr[i]);
        max_heapify(arr, 0, i - 1);
 }
}

int main() {
 int arr[] = { 3, 5, 3, 0, 8, 6, 1, 5, 8, 6, 2, 4, 9, 4, 7, 0, 1, 8, 9, 7, 3, 1, 2, 5, 9, 7, 4, 0, 2, 6 };
 int len = (int) sizeof(arr) / sizeof(*arr);
    heap_sort(arr, len);
 int i;
 for (i = 0; i < len; i++)
 printf("%d ", arr[i]);
 printf("\n");
 return 0;
}


三、堆排序的优化

堆排序的性能主要受到两个因素的影响:构建堆的过程和调整堆的过程。

构建堆的优化:

构建堆的过程中,可以选择从最后一个非叶子节点开始,直接进行向下调整操作,而不是每个节点都向上调整。这样可以减少调整的次数,提高构建堆的效率。

调整堆的优化:

调整堆的过程中,可以使用堆的性质进行剪枝。即,在每次向下调整时,只与左右子节点中较大的节点进行比较交换,而不是与每个子节点都进行比较。这样可以减少比较和交换的次数,提高调整堆的效率。

同时,可以使用大顶堆和小顶堆相结合的方式进行排序。即,先构建一个大顶堆,然后将堆顶元素与最后一个元素交换,然后对交换后的堆顶元素进行向下调整操作得到小顶堆,再将堆顶元素与倒数第二个元素交换,重复执行这个过程,直到排序完成。这样可以减少每次调整堆的时间。


四、总结

堆排序是一种高效的排序算法,通过构建最大堆或最小堆来实现排序。它的时间复杂度为O(nlogn),适用于大规模数据的排序。实现堆排序主要包括堆的构建和堆的调整两个过程,可以根据具体情况进行优化,提高排序的效率。同时,堆排序也可以与其他排序算法相结合,以达到更好的排序效果。






部分引用参考:https://www.runoob.com/

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
pYQ3D3NPYfjH