当前位置:   article > 正文

二叉树的顺序实现-堆

二叉树的顺序实现-堆

目录

一、什么是堆

二、堆的实现

2.1 向上调整算法

2.1.1 思路

2.1.2 代码

2.2 向下调整算法

2.2.1 思路

2.2.2 代码

2.3 插入

2.3.1 思路

2.3.2 代码

2.4 删除

2.4.1 思路

2.4.2 代码

三、C语言源码汇总

3.1 heap.h

3.2 heap.c

四、堆的应用-TopK问题

4.1 分析

4.2 C语言源码


一、什么是堆

在数据结构中,堆(Heap)是一种特殊的树形数据结构,用数组存储,通常被用来实现优先队列

堆具有以下特点:

  1. 堆是一棵完全二叉树(Complete Binary Tree),即除了最后一层外,其他层都是满的,且最后一层的节点从左到右排列。
  2. 堆中的每个节点都满足堆的性质,即父节点的值不小于(或不大于)其子节点的值,这种性质被称为堆序性(Heap Property)。
    • 最大堆(Max Heap):父节点的值不小于其子节点的值。
    • 最小堆(Min Heap):父节点的值不大于其子节点的值。
  3. 堆中的根节点(通常是位于最顶层的节点)是堆中的最大(或最小)元素。在最大堆中,根节点的值大于等于其子节点的值;在最小堆中,根节点的值小于等于其子节点的值。
  4. 堆不保存节点之间的具体顺序,只保证堆序性。
  5. 堆可以用数组来表示,根据节点的索引和父子节点的关系可以计算出节点之间的关系。

堆的常见操作有插入(Insert)、删除根节点(Delete Max/Min)和查找最大(或最小)元素。

堆的应用非常广泛,常见的应用包括优先队列、堆排序、图算法(如最短路径算法中的Dijkstra算法)等。通过使用堆,可以高效地在大量数据中插入、删除和获取最大(或最小)元素,时间复杂度为O(log n)。

二、堆的实现

2.1 向上调整算法

2.1.1 思路

以大堆举例:目的是要实现叶子节点要比所有的祖先节点小

  • 考虑单次:如果父节点比孩子结点小,则二者交换
  • 考虑循环:
  1. 循环体:交换之后先前的父亲节点与孩子结点的下标值互换,继续进行单次比较交换
  2. 结束条件:
    一般的情况:如果符合大堆的条件(父节点大于子节点),则可以跳出循环。
    最坏的情况:一直交换到了根节点,如果在进行下去数组就会越界,所以下标值应该>=0

2.1.2 代码

  1. void AdjustUp(int* a, int child)
  2. {
  3. int parent = (child - 1) / 2;
  4. while (child > 0)
  5. {
  6. if (a[child] > a[parent])
  7. {
  8. Swap(&a[child], &a[parent]);
  9. //下标值互换
  10. child = parent;
  11. //重新计算父亲结点的值
  12. parent = (child - 1) / 2;
  13. }
  14. else
  15. {
  16. break;
  17. }
  18. }
  19. }

2.2 向下调整算法

2.2.1 思路

以小堆举例目的是要实现叶子节点要比所有的祖先节点大

  • 考虑单次:
  1. 先找到孩子节点中较小的结点
  2. 如果父节点比孩子结点大,则二者交换
  • 考虑循环:
  1. 循环体:交换之后先前的父亲节点与孩子结点的下标值互换,继续进行单次比较交换
  2. 结束条件:
    一般的情况:如果符合小堆的条件(父节点小于子节点),则可以跳出循环。
    最坏的情况:一直交换到了叶子节点,如果在进行下去数组就会越界,所以下标值应该<=n-1

2.2.2 代码

  1. void AdjustDown(int* a, int n, int parent)
  2. {
  3. //左孩子的下标
  4. int child = parent * 2 + 1;
  5. while (child<n)
  6. {
  7. //找到两个孩子中较小的孩子-假设法
  8. if (child + 1 < n && a[child + 1] < a[child])
  9. {
  10. child++;
  11. }
  12. if (a[parent] > a[child])
  13. {
  14. Swap(&a[parent], &a[child]);
  15. parent = child;
  16. child = parent * 2 + 1;
  17. }
  18. else
  19. {
  20. break;
  21. }
  22. }
  23. }

2.3 插入

2.3.1 思路

从物理结构上讲,插入到数组的最后一个位置,然后用向上调整算法调整即可

2.3.2 代码

  1. void HPPush(HP* php, HPDataType x)
  2. {
  3. assert(php);
  4. //检测数组是否扩容
  5. if (php->size == php->capacity)
  6. {
  7. int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
  8. HPDataType* tmp = (HPDataType*)realloc(php->a, newcapacity * sizeof(HPDataType));
  9. if (tmp == NULL)
  10. {
  11. perror("realloc fail");
  12. return;
  13. }
  14. php->a = tmp;
  15. php->capacity = newcapacity;
  16. }
  17. //插入
  18. php->a[php->size] = x;
  19. php->size++;
  20. //调整
  21. AdjustUp(php->a, php->size - 1);
  22. }

2.4 删除

2.4.1 思路

删除一般删除的是堆顶的元素

如果直接删除,然后用向下调整算法调整:原来的子节点会变成父节点,父节点会变成子节点。所以不可以采取此做法。

正确的做法:将堆顶元素与堆底元素交换,删除掉数组尾部元素,向下调整原数组。这样就可以规避原堆父子关系全乱的问题

2.4.2 代码

  1. void HPPop(HP* php)
  2. {
  3. assert(php);
  4. assert(php->size > 0);
  5. //交换
  6. Swap(&php->a[0], &php->a[php->size - 1]);
  7. //删除
  8. php->size--;
  9. //调整
  10. AdjustDown(php->a, php->size, 0);
  11. }

三、C语言源码汇总

3.1 heap.h

  1. #pragma once
  2. #include<stdio.h>
  3. #include<assert.h>
  4. #include<stdlib.h>
  5. #include<stdbool.h>
  6. //结构体定义
  7. typedef int HPDataType;
  8. typedef struct Heap
  9. {
  10. HPDataType* a;
  11. int size;
  12. int capacity;
  13. }HP;
  14. //交换
  15. void Swap(HPDataType* p1, HPDataType* p2);
  16. //向上调整
  17. void AdjustUp(HPDataType* a, int child);
  18. //向下调整
  19. void AdjustDown(HPDataType* a, int n, int parent);
  20. //初始化堆
  21. void HPInit(HP* php);
  22. //销毁堆
  23. void HPDestroy(HP* php);
  24. //插入
  25. void HPPush(HP* php, HPDataType x);
  26. //删除
  27. void HPPop(HP* php);
  28. //返回堆顶元素
  29. HPDataType HPTop(HP* php);
  30. //判断堆是否为空
  31. bool HPEmpty(HP* php);

3.2 heap.c

  1. #include"heap.h"
  2. void HPInit(HP* php)
  3. {
  4. assert(php);
  5. php->a = NULL;
  6. php->size = php->capacity = 0;
  7. }
  8. void HPDestroy(HP* php)
  9. {
  10. assert(php);
  11. free(php->a);
  12. php->a = NULL;
  13. php->size = php->capacity = 0;
  14. }
  15. void Swap(HPDataType* p1, HPDataType* p2)
  16. {
  17. HPDataType tmp = *p1;
  18. *p1 = *p2;
  19. *p2 = tmp;
  20. }
  21. void AdjustUp(HPDataType* a, int child)
  22. {
  23. // 初始条件
  24. // 中间过程
  25. // 结束条件
  26. int parent = (child - 1) / 2;
  27. //while (parent >= 0)
  28. while (child > 0)
  29. {
  30. if (a[child] < a[parent])
  31. {
  32. Swap(&a[child], &a[parent]);
  33. child = parent;
  34. parent = (child - 1) / 2;
  35. }
  36. else
  37. {
  38. break;
  39. }
  40. }
  41. }
  42. void HPPush(HP* php, HPDataType x)
  43. {
  44. assert(php);
  45. if (php->size == php->capacity)
  46. {
  47. int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
  48. HPDataType* tmp = (HPDataType*)realloc(php->a, newcapacity * sizeof(HPDataType));
  49. if (tmp == NULL)
  50. {
  51. perror("realloc fail");
  52. return;
  53. }
  54. php->a = tmp;
  55. php->capacity = newcapacity;
  56. }
  57. php->a[php->size] = x;
  58. php->size++;
  59. AdjustUp(php->a, php->size - 1);
  60. }
  61. void AdjustDown(HPDataType* a, int n, int parent)
  62. {
  63. // 先假设左孩子小
  64. int child = parent * 2 + 1;
  65. while (child < n) // child >= n说明孩子不存在,调整到叶子了
  66. {
  67. // 找出小的那个孩子
  68. if (child + 1 < n && a[child + 1] < a[child])
  69. {
  70. ++child;
  71. }
  72. if (a[child] < a[parent])
  73. {
  74. Swap(&a[child], &a[parent]);
  75. parent = child;
  76. child = parent * 2 + 1;
  77. }
  78. else
  79. {
  80. break;
  81. }
  82. }
  83. }
  84. // logN
  85. void HPPop(HP* php)
  86. {
  87. assert(php);
  88. assert(php->size > 0);
  89. Swap(&php->a[0], &php->a[php->size - 1]);
  90. php->size--;
  91. AdjustDown(php->a, php->size, 0);
  92. }
  93. HPDataType HPTop(HP* php)
  94. {
  95. assert(php);
  96. assert(php->size > 0);
  97. return php->a[0];
  98. }
  99. bool HPEmpty(HP* php)
  100. {
  101. assert(php);
  102. return php->size == 0;
  103. }

四、堆的应用-TopK问题

4.1 分析

处理的是数据量非常大的情况下,需要知道最大/最小的某几个数的问题。

由于建堆的空间复杂度是O(N),所以建堆的方式不可行,需要直接在数组上操作。

正确的思路:用前K个数,建一个小堆,剩下的数据跟堆顶数据比较,如果比堆顶的数据大,就替代堆顶进堆(覆盖根位置,然后向下调整)

4.2 C语言源码

  1. void CreateNDate()
  2. {
  3. // 造数据
  4. int n = 100000;
  5. srand(time(0));
  6. const char* file = "data.txt";
  7. FILE* fin = fopen(file, "w");
  8. if (fin == NULL)
  9. {
  10. perror("fopen error");
  11. return;
  12. }
  13. for (int i = 0; i < n; ++i)
  14. {
  15. int x = (rand() + i) % 10000000;
  16. fprintf(fin, "%d\n", x);
  17. }
  18. fclose(fin);
  19. }
  20. void TopK()
  21. {
  22. int k;
  23. printf("请输入k>:");
  24. scanf("%d", &k);
  25. int* heap = (int*)malloc(sizeof(int) * k);
  26. if (heap == NULL)
  27. {
  28. perror("malloc fail");
  29. return;
  30. }
  31. const char* file = "data.txt";
  32. FILE* num = fopen(file, "r");
  33. if (num == NULL)
  34. {
  35. perror("fopen error");
  36. return;
  37. }
  38. // 读取文件中前k个数
  39. for (int i = 0; i < k; i++)
  40. {
  41. fscanf(num, "%d", &heap[i]);
  42. }
  43. // 建K个数的小堆
  44. for (int i = (k - 1 - 1) / 2; i >= 0; i--)
  45. {
  46. AdjustDown(heap, k, i);
  47. }
  48. // 读取剩下的N-K个数
  49. int x = 0;
  50. while (fscanf(num, "%d", &x) > 0)
  51. {
  52. //更新小堆的数据并进行算法排序
  53. if (x > heap[0])
  54. {
  55. heap[0] = x;
  56. AdjustDown(heap, k, 0);
  57. }
  58. }
  59. printf("最大前%d个数: ", k);
  60. for (int i = 0; i < k; i++)
  61. {
  62. printf("%d ", heap[i]);
  63. }
  64. printf("\n");
  65. }
  66. int main()
  67. {
  68. CreateNDate();
  69. TopK();
  70. return 0;
  71. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/黑客灵魂/article/detail/744411
推荐阅读
相关标签
  

闽ICP备14008679号