【七大排序】堆排序详解

简介: 【七大排序】堆排序详解

前言

  常见的排序算法有七种。

  本篇文章将会详细介绍堆排序的实现。


一、什么是堆

  所谓堆,就是一种特殊的完全二叉树。在堆当中,任何一个结点的值都要比其孩子要大或者小,任意结点都比其孩子大的叫做大堆(或大根堆),任意结点都比其孩子小的叫做小堆(或小根堆)。

二、堆在数组中的存储

  由于堆是一个完全二叉树,而完全二叉树从根结点到最后一个结点之间是没有空值的,是一种非常连续的结构。所以我们大多数情况下都是使用顺序存储在数组当中。

  堆,或者说二叉树在数组中都是一层一层、从左往右存储的。根结点的下标为 0 。

  需要注意的是,根据上图,我们可以找到一个规律,这个规律在堆当中是蛮重要的。假设某一个结点的下标是 i ,那么它的父结点(假如存在)的下标就是 (i - 1) / 2 ,它的左孩子(假如存在)的下标就是 i * 2 + 1 ,它的右孩子(假如存在)的下标就是 i * 2 + 2。所以在数组当中,我们可以根据以上规律很快的找到任意结点的父结点或者子结点。

三、堆排序思想

  想要理解堆排序只需要理解两个部分即可。1.求出当前最值2.调整,剩下就不断重复以上两个步骤,直到排好序即可。

1.求出当前最值

  当我们了解堆的概念以及性质后,我们会很轻松的知道,堆的任意一个结点都是以该结点为根结点的子树中的最值,而整个堆的根结点,也就是堆顶元素,是整个堆的最值

  真的是太明显了,这个堆的当前最值是什么?当然是根结点啦。问题是我们该如何获取到这个最值?直接取吗?然后删除这个根结点?那谁做新的根结点?左孩子?右孩子?所以说这是行不通的,我们在上面已经讲了,堆在内存中一般以数组的方式存储,你让所有数据整体左移一个单位,那堆的结构就全乱套了。

  正确做法是,将根结点与堆的尾结点进行交换,然后再删除已经是最值的尾结点,最后新的根结点向下调整,直到符合堆的结构为止。

2.调整

  在取得了当前的最值后,原本的尾结点已经被移动到了根结点,此时根结点的位置不一定是它应该在的地方,它应该不断与其孩子比较,使其符合堆的结构,当它并没有与孩子交换时,说明已经调整完成,一个新的堆出现了。

  当完成这一步后,就可以接着重复以上两个步骤,直到排好序。

  需要注意的是,我们每次都能将当前最值排好序到数组末尾,如果是大堆,则最大值在末尾,如果是小堆,则最小值在末尾。也就是说 建大堆会排成升序,建小堆会排成降序

四、代码实现

1.库函数头文件及声明

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
// 宏定义数据类型
typedef int HPDataType;
// 堆结构体
typedef struct Heap
{
    // 指向数组的指针
  HPDataType* a;
  // 堆的有效数据个数
  int size;
  // 堆的容量
  int capacity;
}HP;
// 堆的初始化
void HeapInit(HP* php);
// 堆的销毁
void HeapDestroy(HP* php);
// 向下调整
void AdjustDown(HPDataType* a, int size, int parent);
// 交换函数
void Swap(HPDataType* a, HPDataType* b);

2.其他函数实现

// 堆的初始化
void HeapInit(HP* php)
{
    // php指向堆结构体,为空说明堆没有创建,传参错误
  assert(php);
    // 指向数组的指针初始指向空
  php->a = NULL;
  // 堆的有效数据初始为0
  php->size = 0;
  // 堆的容量初始为0
  php->capacity = 0;
}
// 堆的销毁
void HeapDestroy(HP* php)
{
  assert(php);
    // 释放数组空间
  free(php->a);
  // 释放空间后指针置空
  php->a = NULL;
  // 回归默认状态
  php->size = 0;
  // 回归默认状态
  php->capacity = 0;
}
// 交换函数
void Swap(HPDataType* a, HPDataType* b)
{
  HPDataType tmp = *a;
  *a = *b;
  *b = tmp;
}
// 向下调整函数
void AdjustDown(HPDataType* a, int size, int parent)
{
  // 父结点的左孩子
  int child = parent * 2 + 1;
  // 左孩子存在(父结点不是叶子结点)
  while (child < size)
  {
    // 找出左右孩子中的小值
    if (child + 1 < size && a[child + 1] < a[child])
      ++child;
    // 左右孩子的最小值比父亲要小,则跟父结点交换
    if (a[child] < a[parent])
    {
      Swap(&a[child], &a[parent]);
      // 更新父结点的下标
      parent = child;
      // 更新左孩子的下标
      child = parent * 2 + 1;
    }
    // 不需要交换则说明堆的结构已成,直接退出
    else
      break;
  }
}

3.堆排序函数

void HeapSort(HPDataType* arr, int len)
{
  // 创建堆
  HP hp;
  // 初始化堆
  HeapInit(&hp);
  // 建堆,当前是小堆
  for (int i = len; i >= 0; --i)
  {
    AdjustDown(arr, len, i);
  }
  // end是末尾元素的下标
  int end = len - 1;
  while (end > 0)
  {
    // 将最小值交换到堆的末尾
    Swap(&arr[0], &arr[end]);
    // 堆顶元素重新向下调整
    AdjustDown(arr, end, 0);
    // 每排好一个数据让end-1
    --end;
  }
}

4.主函数

int main()
{
    // 测试数组
    int arr[] = { 416,684,654,516,156,83,941,3584,164,168,51,53,49,849,764 };
    // 数组大小
    int i = sizeof(arr) / sizeof(arr[0]);
    // 堆排序
    HeapSort(arr, i);
    // 打印排好序的数组
    for (int j = 0; j < i; ++j)
      printf("%d ", arr[j]);
  printf("\n");
    return 0;
}

5.结果演示

五、复杂度分析

1.时间复杂度

  在堆排序中,排好一个数据只需要两步,交换和向下调整。交换花费的时间及其少,忽略不计。向下调整中,我们发现,每次调整都会让结点往下移动一层,最多移动 总层数 - 1 次,我们知道,二叉树的层数与数据个数是 log2 n 级别的。所以每排好一个数据花费的时间是 log2 n 级别的,当有 n 个数据时,堆排序排好的时间复杂度就是 O(N * log2 N)

2.空间复杂度

  是的,我们没有开辟额外数组空间,所以空间复杂度是 O(1)

六、提一嘴

  在建堆的时候,我使用了向下调整建堆,我没有做详细说明,这个读者可以下去自行画图理解理解。不仅仅是向下调整可以建堆,向上调整也可以建堆,这两种方法读者都可以尝试。再多就不是堆排序的内容了,感兴趣的可以看看堆的实现。


总结

  本篇文章详细介绍了堆排序的实现,作为排序速度最快的那一档,还是非常有必要学习掌握的。制作不易,还请读者三连支持一下,您的鼓励是我创作的动力,谢谢。

目录
相关文章
|
7月前
|
机器学习/深度学习 算法 搜索推荐
八大排序(二)--------冒泡排序
八大排序(二)--------冒泡排序
24 1
|
3天前
|
机器学习/深度学习 搜索推荐
【七大排序】最基础的排序——冒泡排序
【七大排序】最基础的排序——冒泡排序
17 4
|
1月前
|
算法 搜索推荐
【六大排序详解】中篇 :选择排序 与 堆排序
选择排序可以用扑克牌理解,眼睛看一遍所有牌,选择最小的放在最左边。然后略过刚才排完的那张,继续进行至扑克牌有序。这样一次一次的挑选,思路很顺畅。总结为: 每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。
24 6
|
1月前
|
算法
【六大排序详解】终篇 :冒泡排序 与 快速排序
冒泡排序如同泡泡上升一样,逐个逐个向上冒,一个接一个的冒上去。两两比较,较大者(较小者)向后挪动。全部遍历一遍即可完成排序
26 2
|
1月前
|
搜索推荐 测试技术
【六大排序详解】开篇 :插入排序 与 希尔排序
排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。 排序存在稳定性,稳定性是评估排序的重要标准。
23 1
|
7月前
|
算法 搜索推荐
八大排序--------(五)堆排序
八大排序--------(五)堆排序
20 0
|
7月前
|
机器学习/深度学习 搜索推荐
八大排序(三)--------简单选择排序
八大排序(三)--------简单选择排序
31 0
|
7月前
|
机器学习/深度学习 搜索推荐 算法
八大排序(四)--------直接插入排序
八大排序(四)--------直接插入排序
18 0
|
9月前
|
算法
八大排序——快速排序
八大排序——快速排序
|
9月前
|
算法 搜索推荐 测试技术
【数据结构】 七大排序详解(壹)——直接插入排序、希尔排序、选择排序、堆排序
【数据结构】 七大排序详解(壹)——直接插入排序、希尔排序、选择排序、堆排序