算法导论中的堆排序详解(参考别人画的图)

本文详细介绍了堆排序算法的工作原理,包括如何构建大顶堆或小顶堆,并通过具体实例展示了堆排序的过程。此外还分析了堆排序的时间复杂度为O(nlogn),空间复杂度低,是一种高效的排序方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 我们先研究一下几种常见算法的复杂度和稳定性以及是否需要额外的空间开销


堆排序快速排序归并排序一样都是时间复杂度为O(N*logN)的几种常见排序方法。学习堆排序前,先讲解下什么是数据结构中的二叉堆。

堆排序是利用堆的特性对记录序列进行排序的一种排序方法。

即小根堆:父结点的值小于左右孩子结点的值,大顶堆则相反



假设我们要排序的序列是{50,10,90,30,70,40,80,60,20}

堆排序分为两个步骤:

1.构造大顶堆(或者小顶堆)

建立大根堆的方法是从n/2的编号处开始到根节点,不断通过比较父节点和左右子节点来调整从而保证父节点大于两个子节点。

如图所示:将输入的数组不断调整,使得其构成大顶堆


  1. void HeapAjust(int* arr,int start,int end)
  2. {
  3. int temp,j;
  4. temp = arr[start];
  5. for (j = 2*s;j <= end;j*= 2)
  6. {
  7. if(j < m && arr[j] < arr[j+ 1]) //左孩子小于右孩子
  8. j++;
  9. if(temp >= arr[j])
  10. break;
  11. arr[start] = arr[j];
  12. start = j;
  13. }
  14. arr[start] = temp;
  15. }


在这里我们使用大顶堆,依次输出元素如下图:

依次类推输出所有元素


程序如下:

  1. void HeapSort(int* arr,int len)
  2. {
  3. int i;
  4. for(i = len/ 2;i>= 0;i--) //构造大顶堆
  5. HeapAjust(arr,i,len -1);
  6. for (i = len -1;i > 0;i--)
  7. {
  8. swap(arr[ 0],arr[i]); //取堆顶的记录和当前未经排序子序列的最后一个记录交换
  9. HeapAjust(arr, 1,i -1);
  10. }
  11. }

这里给出完整代码(仅供参考):

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. void swap(int& data1,int &data2)
  4. {
  5. int temp = data1;
  6. data1 = data2;
  7. data2 = temp;
  8. }
  9. //返回i的父结点
  10. int parent(int i)
  11. {
  12. return (i -1)/ 2;
  13. }
  14. //返回i的左孩子结点
  15. int leftchild(int i)
  16. {
  17. return 2*i+ 1;
  18. }
  19. //返回i的右孩子结点
  20. int rightchild(int i)
  21. {
  22. return 2*i+ 2;
  23. }
  24. void MaxHeapify(int *arr,int len,int i)
  25. {
  26. int lchild = leftchild(i);
  27. int rchild = rightchild(i);
  28. int nmax;
  29. if(lchild < len && arr[lchild] > arr[i])
  30. nmax = lchild;
  31. else
  32. nmax = i;
  33. if(rchild < len && arr[rchild] > arr[nmax])
  34. nmax = rchild;
  35. if (nmax != i)
  36. {
  37. swap(arr[nmax],arr[i]);
  38. MaxHeapify(arr,len,nmax);
  39. }
  40. }
  41. //堆排序
  42. void HeapSort(int* arr,int len)
  43. {
  44. for ( int i = parent(len - 1);i >= 0;i--)
  45. MaxHeapify(arr,len,i);
  46. for ( int j = len -1;j > 0;j--)
  47. {
  48. swap(arr[j],arr[ 0]);
  49. len--;
  50. MaxHeapify(arr,len, 0);
  51. }
  52. }
  53. void print(int* arr,int len)
  54. {
  55. int i = 0;
  56. while (i < len)
  57. printf( "%d ",arr[i++]);
  58. // for (int i = 0;i < len;i++)
  59. // {
  60. // printf("%d ",arr[i]);
  61. // }
  62. }
  63. int main()
  64. {
  65. int nArr[ 10] = { 4, 1, 3, 2, 16, 9, 10, 14, 8, 7};
  66. printf( "排序前:");
  67. print(nArr, 10);
  68. HeapSort(nArr, 10);
  69. printf( "\n排序后:");
  70. print(nArr, 10);
  71. system( "pause");
  72. return 0;
  73. }


复杂度分析:

运行时间主要是消耗在初始建堆和重建堆时的反复筛选上,在构建堆的过程中,因为是完全二叉树最下层最右边的非终端结点开始构建,将它与其孩子比较若有必要进行交换,对于每个非终端结点来说,其实最多进行两次比较和呼唤操作,因此整个构建过程的时间复杂度为O(n),在排序时,第i次取堆顶记录重建时需要使用O(logi)的时间,并需要取 n-1 次堆顶记录,因此重建堆时的时间复杂度为 O(nlogn).因此总体来说堆排序的时间复杂度为  O(nlogn)。由于堆排序对原始记录的排序状态并不敏感,因此他无论是最好、最坏和平均时间复杂度均为  O(nlogn)。这在性能是要超过 冒泡排序、简单选择排序、直接插入排序的 O(n^2) 的时间复杂度。空间复杂度上,他只有一个用来交换的暂存单元,也非常不错。但是由于记录的比较与交换是跳跃式进行,因此堆排序是一种不稳定的排序方法


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值