0


深入浅出堆—C语言版【数据结构】

二叉树概念博客http://t.csdn.cn/XIW84

1. 了解堆

1.1 堆的概念

1.2 堆的性质:

堆中某个节点的值总是不大于或不小于其父节点的值;

堆总是一棵完全二叉树。

1.3 堆的结构图片

1.3.1 小堆

满足下面条件的是小堆

1.3.2 大堆

满足下面条件的是大堆

注意不一定是从大到小、从小到大存储的!!!


堆有什么作用呢?

下面来细讲,别走开!!!



2. 堆的实现

2.1 插入数据进堆

  1. void HeapPush(HP* php, HPDataType x)
  2. {
  3. assert(php);
  4. if (php->size == php->capacity)
  5. {
  6. int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
  7. HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType)*newcapacity);
  8. if (tmp == NULL)
  9. {
  10. printf("realloc fail\n");
  11. exit(-1);
  12. }
  13. php->a = tmp;
  14. php->capacity = newcapacity;
  15. }
  16. php->a[php->size] = x;
  17. php->size++;
  18. AdjustUp(php->a, php->size - 1);
  19. }

注意点!!!

假如一开始我们的堆是小堆,但是在插入数据以后要保持还是小堆,要将插入的数据的大小和它的父亲进行比较,比较的两种情况:

  1. 如果插入的数据比父亲还要大,那就不需要调整

  2. 如果插入的数据比父亲还要小,那就需要调整

如果需要调整,我们就要使用向上调整算法,保持插入数据后的堆还是小堆


2.2 向上调整函数

  1. void AdjustUp(HPDataType* 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. child = parent;//将父亲的下标给孩子,向上调整
  10. parent = (child - 1) / 2;//再算出此时插入数据的父亲下标
  11. }
  12. else
  13. {
  14. break;
  15. }
  16. }
  17. }

2.3 堆的删除

能不能使用覆盖删除呢—不能!!!

使用覆盖删除,会打乱父子之间的下标关系,父子关系就会全部乱掉,因此我们使用下面的方法来删除数据


  1. 先将下标为0位置的数据和下标最大的数据进行交换

  2. 然后直接size--

  3. 然后还需要使用向下调整算法,把堆再调整为小堆

  1. void HeapPop(HP* php)
  2. {
  3. assert(php);
  4. assert(php->size > 0);
  5. Swap(&(php->a[0]), &(php->a[php->size - 1]));1.交换
  6. php->size--;//2. 删除堆顶元素
  7. AdjustDwon(php->a, php->size, 0);//向下调整,保证还是小堆
  8. }

2.4 向下调整

  1. void AdjustDwon(HPDataType* a, int size, int parent)
  2. {
  3. int child = parent * 2 + 1;
  4. while (child < size)
  5. {
  6. // 选出左右孩子中小那个
  7. //这里的if里面的判断大小尽量写成小于是小堆,大于是大堆
  8. if (child+1 < size && a[child+1] < a[child])
  9. {
  10. ++child;
  11. }
  12. // 孩子跟父亲比较
  13. if (a[child] < a[parent])
  14. {
  15. Swap(&a[child], &a[parent]);
  16. parent = child;
  17. child = parent * 2 + 1;
  18. }
  19. else
  20. {
  21. break;
  22. }
  23. }
  24. }


3. 堆的应用

3.1 建堆(两种方式)

3.1.1 建堆方式1

利用插入元素的方式,向上调整建堆

  1. void AdjustUp(HPDataType* a, int child)
  2. {
  3. int parent = (child - 1) / 2;
  4. while (child > 0)
  5. {
  6. //if (a[child] < a[parent])
  7. if (a[child] > a[parent])
  8. {
  9. Swap(&a[child], &a[parent]);
  10. child = parent;
  11. parent = (child - 1) / 2;
  12. }
  13. else
  14. {
  15. break;
  16. }
  17. }
  18. }
  19. /
  20. void HeapSort(int* a, int n)//传一个数组过来,还有元素个数
  21. {
  22. // 建堆方式1:O(N*logN)
  23. for (int i = 1; i < n; ++i)
  24. {
  25. AdjustUp(a, i);//从插入的第二个元素开始
  26. }
  27. }

建堆方式1的时间复杂度 ——错位相减法


3.1.2 建堆方式2

利用向下调整建堆

方法:找到最后一个元素的父亲,并从这个位置开始向下调整

  1. void HeapSort(int* a, int n)
  2. {
  3. // 建堆方式2:O(N)
  4. for (int i = (n-1-1)/2; i >= 0; --i)
  5. {
  6. AdjustDwon(a, n, i);
  7. }
  8. // O(N*logN)
  9. int end = n - 1;
  10. while (end > 0)
  11. {
  12. Swap(&a[0], &a[end]);
  13. AdjustDwon(a, end, 0);
  14. --end;
  15. }
  16. }

建堆方式2的时间复杂度——错位相减法



3.2 堆排序

排升序,建大堆,再向下调整

为什么建大堆呢?

建大堆,堆顶元素是最大的数,让堆顶元素和最后一个元素交换,再向下调整,注意:这里向下调整时是调整的数组大小-1个,也就是调整刚刚交换下来前面的数据


排降序,建小堆,再向下调整

  1. void HeapSort(int* a, int n)
  2. {
  3. // 建堆方式2:O(N)
  4. for (int i = (n-1-1)/2; i >= 0; --i)
  5. {
  6. AdjustDwon(a, n, i);
  7. }
  8. // O(N*logN)
  9. int end = n - 1;
  10. while (end > 0)
  11. {
  12. Swap(&a[0], &a[end]);//这里的end是9,传过去向下调整的元素个数也是9,
  13. //就不会调整刚刚从堆顶传下来的数据
  14. AdjustDwon(a, end, 0);
  15. --end;
  16. }

3.3 堆的TOP—K问题

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。

比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。

实现思路:

这样空间复杂度非常小


注意:

  1. ** 求前k个最大的数,是建小堆**
  2. 解释:由于建立的前k个数是小堆,后面n-k个数都可能比对顶的数值大,比堆顶的元素大,就替换堆顶的元素,然后再向下调整,保持前k个数是小堆,然后再比较····
  3. ** 求前k个最小的数,是建大堆(同上**)

代码实现:

  1. void PrintTopK(int* a, int n, int k)
  2. {
  3. // 1. 建堆--用a中前k个元素建堆
  4. int* kMinHeap = (int*)malloc(sizeof(int)*k);
  5. assert(kMinHeap);
  6. for (int i = 0; i < k; ++i)//将a数组里面前10个数赋值给KMinHeap
  7. {
  8. kMinHeap[i] = a[i];
  9. }
  10. for (int i = (k - 1 - 1) / 2; i >= 0; --i)//向下调整建堆,建立k个数的小堆
  11. {
  12. AdjustDwon(kMinHeap, k, i);
  13. }
  14. // 2. 将剩余n-k个元素依次与堆顶元素交换,不满则则替换
  15. for (int j = k; j < n; ++j)
  16. {
  17. if (a[j] > kMinHeap[0])
  18. {
  19. kMinHeap[0] = a[j];
  20. AdjustDwon(kMinHeap, k, 0);//再向下调整,保持前k个数是小堆
  21. }
  22. }
  23. for (int i = 0; i < k; ++i)
  24. {
  25. printf("%d ", kMinHeap[i]);
  26. }
  27. printf("\n");
  28. }
  29. void TestTopk()
  30. {
  31. //随机生成一万个数字,每个数字%1百万,这一万都是比一百万小的数字,
  32. //我们将其中的10个数改为比一百万大的值
  33. int n = 10000;
  34. int* a = (int*)malloc(sizeof(int)*n);
  35. srand(time(0));
  36. for (int i = 0; i < n; ++i)
  37. {
  38. a[i] = rand() % 1000000;
  39. }
  40. a[5] = 1000000 + 1;
  41. a[1231] = 1000000 + 2;
  42. a[531] = 1000000 + 3;
  43. a[5121] = 1000000 + 4;
  44. a[120] = 1000000 + 5;
  45. a[99] = 1000000 + 6;
  46. a[0] = 1000000 + 7;
  47. a[76] = 1000000 + 8;
  48. a[423] = 1000000 + 9;
  49. a[3144] = 1000000 + 10;
  50. PrintTopK(a, n, 10);
  51. }


本文讲的是二叉树的顺序存储结构(堆)的实现,下期我们来讲二叉树的链式存储结构,到时候记得来支持小余哦!!!

如果觉得文章不错,期待你的一键三连哦,你个鼓励是我创作的动力之源,让我们一起加油,顶峰相见!!!


本文转载自: https://blog.csdn.net/qq_58286439/article/details/130561804
版权归原作者 小余大牛成长记 所有, 如有侵权,请联系我们删除。

“深入浅出堆—C语言版【数据结构】”的评论:

还没有评论