• 数据结构系列-堆排序


    🌈个人主页:羽晨同学 

    💫个人格言:“成为自己未来的主人~”  

     今天我们开始讲解一下堆排序和T-TOK问题,这个也是堆排序相对于qsort排序和冒泡排序来说最大的竞争力,首先我们回顾一下之前我们学过的qsort排序和冒泡排序。

    回顾

    首先是qsort排序

    qsort排序是包含在#include这个库文件当中,由于qsort排序属于快速排序,所以它的时间复杂度是NlogN,空间复杂度N

    然后是冒泡排序

    冒泡排序的时间复杂度是N^2,空间复杂度是O(1)

    在我们已知的排序算法当中,我们可以看到的就是,冒泡排序所花的时间复杂度太高,只是涉及单纯的遍历,所以,这个也是堆排序的优点,在堆排序当中,我们会用到两个算法,一个是向上调整算法,一个是向下调整算法,这两个算法的时间复杂度是不同的,具体的我们稍后再说。

    若是想实现堆排序,我们需要手凹一个堆,堆的搭建我们再前面讲过,它的底层逻辑就是一个数组

    首先,我们按照惯例,先对Int进行另外命名,

    typedef int HPDataType;

    接下来,我们对堆的基本信息进行完善,里面有两个指标,一个是size,检测有效空间,一个是capacity,检测开发空间

    1. typedef struct Heap
    2. {
    3. HPDataType* a;
    4. int size;
    5. int capacity;
    6. }HP;

    然后我们在头文件当中,先做几个声明,声明一下我们要实现的基本功能

    1. void HPInit(HP* php);
    2. void HPInitArray(HP* php, HPDataType* a, int n);
    3. void HPDestroy(HP* php);
    4. // 插入后保持数据是堆
    5. void HPPush(HP* php, HPDataType x);
    6. HPDataType HPTop(HP* php);
    7. // 删除堆顶的数据
    8. void HPPop(HP* php);
    9. bool HPEmpty(HP* php);
    10. void AdjustUp(HPDataType* a, int child);
    11. void AdjustDown(HPDataType* a, int n, int parent);
    12. void Swap(HPDataType* px, HPDataType* py);

    然后我们就开始在C文件当中实现这些功能

    首先,按照常规,我们先对堆进行初始化。

    1. void HPInit(HP* php)
    2. {
    3. assert(php);
    4. php->a = NULL;
    5. php->size = 0;
    6. php->capacity = 0;
    7. }

    检测传入的php是否为空,将php中的a置为空,将有效长度和有效空间均赋值为0

    接下的的操作中,我们对堆的搭建方式有两种,一种是之前的malloc开辟空间,但是这样造成的时空间浪费比较大,另外一种我们先开辟空间,然后进行copy,给到另一块空间,这样子会最大程度的避免空间的浪费

    1. void HPInitArray(HP* php, HPDataType* a, int n)
    2. {
    3. assert(php);
    4. php->a = (HPDataType*)malloc(sizeof(HPDataType) * n);
    5. if (php->a == NULL)
    6. {
    7. perror("malloc fail");
    8. return;
    9. }
    10. memcpy(php->a, a, sizeof(HPDataType) * n);
    11. php->capacity = php->size = n;
    12. // 向上调整,建堆 O(N*logN)
    13. //for (int i = 1; i < php->size; i++)
    14. //{
    15. // AdjustUp(php->a, i);
    16. //}
    17. // 向下调整,建堆 O(N)
    18. for (int i = (php->size-1 - 1)/2; i >= 0; i--)
    19. {
    20. AdjustDown(php->a, php->size, i);
    21. }
    22. }

     在这里面最关键的就是将a里面的内容放到了php->a里面,由于堆的逻辑结构是二叉树,所以我们对它进行调整,调整的方式有两种,一种是向上调整,它的时间复杂度是NlogN,另外一种是向下调整算法,它的时间复杂度是N,

    1. void HPDestroy(HP* php)
    2. {
    3. assert(php);
    4. free(php->a);
    5. php->a = NULL;
    6. php->capacity = 0;
    7. php->size = 0;
    8. }
    9. void Swap(HPDataType* px, HPDataType* py)
    10. {
    11. HPDataType tmp = *px;
    12. *px = *py;
    13. *py = tmp;
    14. }
    15. void AdjustUp(HPDataType* a, int child)
    16. {
    17. int parent = (child - 1) / 2;
    18. //while (parent >= 0)
    19. while(child > 0)
    20. {
    21. if (a[child] > a[parent])
    22. {
    23. Swap(&a[child], &a[parent]);
    24. child = parent;
    25. parent = (parent - 1) / 2;
    26. }
    27. else
    28. {
    29. break;
    30. }
    31. }
    32. }

    这些向上调整算法,交换,删除,均为之前强掉的内容 ,我们在这里就不详细展开

    和前面的copy的堆的搭建构成区别的是,我们之前使用的那种HPPush的做法,大家可以想象这种做法的时间复杂度是什么

    1. // 时间复杂度:
    2. void HPPush(HP* php, HPDataType x)
    3. {
    4. assert(php);
    5. if (php->size == php->capacity)
    6. {
    7. size_t newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
    8. HPDataType* tmp = realloc(php->a, sizeof(HPDataType) * newCapacity);
    9. if (tmp == NULL)
    10. {
    11. perror("realloc fail");
    12. return;
    13. }
    14. php->a = tmp;
    15. php->capacity = newCapacity;
    16. }
    17. php->a[php->size] = x;
    18. php->size++;
    19. AdjustUp(php->a, php->size-1);
    20. }

    这个的时间复杂度是o(N)

    1. HPDataType HPTop(HP* php)
    2. {
    3. assert(php);
    4. return php->a[0];
    5. }
    6. void AdjustDown(HPDataType* a, int n, int parent)
    7. {
    8. int child = parent * 2 + 1;
    9. while (child < n)
    10. {
    11. // 假设法,选出左右孩子中小的那个孩子
    12. if (child+1 < n && a[child + 1] < a[child])
    13. {
    14. ++child;
    15. }
    16. if (a[child] < a[parent])
    17. {
    18. Swap(&a[child], &a[parent]);
    19. parent = child;
    20. child = parent * 2 + 1;
    21. }
    22. else
    23. {
    24. break;
    25. }
    26. }
    27. }

    这个就是我们要实现的另外几个功能,这个在之前的文章中也有讲到,所以我们就快速过了

    接下来讲的是Pop操作

    1. // 时间复杂度:logN
    2. void HPPop(HP* php)
    3. {
    4. assert(php);
    5. assert(php->size > 0);
    6. Swap(&php->a[0], &php->a[php->size - 1]);
    7. php->size--;
    8. AdjustDown(php->a, php->size, 0);
    9. }

    在这个操作当中,我们的实现逻辑是先头尾交换,删除尾,然后进行调整,此时的时间复杂度是logN

    这个就是我们实现的大致逻辑,然后再下一章当中,我们将会给大家讲解T-TOK问题

  • 相关阅读:
    登上抖音同城热搜榜:如何让你的短视频成为焦点?
    Python中的元类(metaclass)
    php-面向对象OOP
    [MyBatisPlus]DQL编程控制①(条件查询)
    PMP®|对如何做好项目管理的几点建议
    嵌入式STM32 单片机 GPIO 的工作原理详解
    【C++】红黑树
    Qt Creator 使用技巧
    【开源】基于Vue+SpringBoot的音乐平台
    git仓库浏览代码插件
  • 原文地址:https://blog.csdn.net/in_seattle/article/details/138168156