当前位置:   article > 正文

C语言实现数据结构的堆(Heap)_c语言 建堆

c语言 建堆

希望这篇文章对你有益,如果你发现哪里有问题,希望您能不吝赐教。

文章目录

  • 前言
  • 一、数据结构的堆是什么
  • 二、逻辑结构的堆
  • 三、建堆
  • 四、堆排序
  • 五、TopK问题
  • 六、总结


前言

例如:堆的逻辑结构是完全二叉树,看后面的知识需要你有一点点二叉树的概念,你需要知道父子结点,二叉树的结构,以及顺序表的基本知识!


一、数据结构的堆是什么?

  数据结构的堆物理结构是数组逻辑结构是完全二叉树(这里可以联想链表的物理、逻辑结构的不同),这里引入一个问题就是:如何让数组变成二叉树,也就是说一个数据如何与另外两个数据产生联系?带着这个问题,我们往下看。


二、逻辑结构的堆

给一个数组{9,7,2,6,3,5,1,4,8},他的逻辑结构:

0->1、0->2、1->3、1->4 反过来列举我们可以通过下标找到数据查找关系

把9当作parent,把7,2当作child1和child2,那么我们总结可知 :

child1=parent*2+1;

child2=parent*2+2;

parent=(child-1)/2;

由这两个不同角度的重要结论,我们可以由此实现物理->逻辑的联系,下面来实现建堆!


三、建堆

  堆分为小堆和大堆,小堆是所有的双亲结点要小于自己的字结点,大堆就是双亲结点要大于自己的子节点。建堆就是将堆建成小堆或者大堆。

下面上代码教你如何建堆~

1.堆的基本框架(顺序表)

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

2.堆的初始化

  1. void HeapInit(Heap* php)
  2. {
  3. php->a = (HeapDataType*)malloc(sizeof(HeapDataType) * 8);
  4. php->capacity = 8;
  5. php->size = 0;
  6. }

3.建堆(以大堆为例)

Way1:向上调整法

  1. //向上排序法(孩子找父亲) 时间复杂度0(N*logN)
  2. void AdjustUp(HeapDataType* a, int child)
  3. {
  4. //1.找父亲
  5. int parent = (child - 1) / 2;
  6. while (child > 0)
  7. {
  8. //2.比较父子结点
  9. if (a[child] > a[parent])
  10. {
  11. //交换
  12. swap(&a[child], &a[parent]);
  13. //迭代
  14. child = parent;
  15. parent = (child - 1) / 2;
  16. }
  17. else {
  18. break;
  19. }
  20. }
  21. }
  22. //建堆
  23. void HeapCreate(Heap* php, HeapDataType* a, int size)
  24. {
  25. for (int i = 0;i < size;i++)
  26. {
  27. HeapPush(php, a[i]);
  28. }
  29. }

方法:通过子节点找到父结点,比较父与子结点大小,子节点大于父结点那么就交换,否则不交换。不交换说明这对父子结点已满足大堆条件,不需要动它们;交换则迭代执行下一次比较!

计算时间复杂度:

Way2:向下调整法(大堆为例)

思路:从第一个非叶结点开始,从子结点中找出最大的结点,比较父子结点的大小,如果子结点大于父节点则交换,然后迭代,否则说明已满足大堆条件,直接结束调整。

  1. //利用向下调整法建大堆 时间复杂度0(N)
  2. int arr[] = { 9,7,2,6,3,5,1,4,8 };
  3. //这里i起始应该是第一个非叶子结点 最后一个结点时n-1,parent=(n-1-1)/2
  4. for (int i = (n-2)/2;i>=0;i--)
  5. {
  6. AdjustDown(a, n, i);
  7. }
  8. void AdjustDown(HeapDataType* a, int size,int parent)
  9. {
  10. 1.父亲找孩子
  11. int minchild = parent * 2 + 1;
  12. while (minchild < size )
  13. {
  14. //2.找出“长子”
  15. if (minchild + 1 < size && a[minchild+1] > a[minchild])
  16. {
  17. minchild = minchild + 1;
  18. }
  19. //3.比较父子
  20. if (a[minchild] > a[parent])
  21. {
  22. //子节点>父节点,交换
  23. swap(&a[minchild], &a[parent]);
  24. //迭代
  25. parent = minchild;
  26. minchild = parent * 2 + 1;
  27. }
  28. else {
  29. break;
  30. }
  31. }
  32. }

计算时间复杂度:

理解了建堆的方法,我们就可以考虑堆的应用->堆排序


四、堆排序

我们直到大堆,小堆的特性以后可以隐约地感觉这种结构可以发挥数据的选择,排序就是一种数据有序的要求,这里强调的是大堆小堆并未完全实现数组有序,由上面的例子可以看出大小堆并非有序,但堆顶元素具有Max,Min的性质!

现在问一个问题:排升序是建大堆,还是小堆?排降序呢?停下来自己思考一下!

很多人会不暇思索地回答:“小堆!”,原因是小堆堆顶正好是最小的!现上图来帮你分析一下为啥小堆排升序是效率很低的。

 现在上代码~

  1. void HeapSort(int*a,int n)
  2. {
  3. //利用向下调整法建大堆 时间复杂度0(N)
  4. for (int i = (n-2)/2;i>=0;i--)
  5. {
  6. AdjustDown(a, n, i);
  7. }
  8. //排序 时间复杂度0(N*logN)
  9. for (int i = 1;i < n;i++)
  10. {
  11. swap(&a[0], &a[n-i]);
  12. //再向下建大堆
  13. AdjustDown(a, n-i, 0);
  14. }
  15. }
  16. void TestHeapSort()
  17. {
  18. int arr[] = { 9,7,2,6,3,5,1,4,8 };
  19. HeapSort(arr, sizeof(arr) / sizeof(arr[0]));
  20. for (int i = 0;i < sizeof(arr) / sizeof(arr[0]);i++)
  21. {
  22. printf("%d ", arr[i]);
  23. }
  24. }

五、TopK问题

 TopK问题:找出N个数里面最大/最小的前K个问题。
 比如:未央区排名前10的泡馍,西安交通大学王者荣耀排名前10的韩信,全国排名前10的李白。等等问题都是Topk问题。

方法1:直接堆排序 时间复杂度O(N*logN)

方法二:建大堆,取堆顶,与最后一个值交换然后继续建堆 时间复杂度O(N+K*logN) 该方法适用于N较小时,当N很大该方法效率不如方法三

方法三:对前K个数建小堆,然后遍历后N-K个数,如果该数比小堆堆顶大就替换,替换完之后重新建小堆,重复直到遍历结束。 时间复杂度O(K+(N-K)*logK) 适用于N很大的情况下

现上法二、方法三代码~

方法二:

  1. void PrintTopK1(int* a, int n, int k)
  2. {
  3. assert(k < n&& k>0);
  4. //建大堆
  5. for (int i = (n - 2) / 2;i >= 0;i--)
  6. {
  7. AdjustDown(a, n, i);
  8. }
  9. //取TopK
  10. int i = 1;
  11. while (i<=k)
  12. {
  13. printf("%d ", a[0]);
  14. swap(&a[0], &a[n - i]);
  15. AdjustDown(a, n - i, 0);
  16. i++;
  17. }
  18. }
  19. void TestTopk()
  20. {
  21. int arr[] = { 9,7,2,6,3,5,1,4,8 };
  22. PrintTopK1(arr, sizeof(arr) / sizeof(int), 3);//建大堆 N较小情况下选择
  23. }

方法三:

  1. //随机生成N个随机数 控制随机数数值范围,然后把文件中几个值手动修改中成几个>随机数最大值的值
  2. void CreateData(int N)
  3. {
  4. srand((unsigned int)time(NULL));
  5. FILE* fout = fopen("Heap.txt", "w");
  6. if (fout == NULL)
  7. {
  8. perror("fopen failed");
  9. exit(-1);
  10. }
  11. for (int i = 0;i < N;i++)
  12. {
  13. fprintf(fout, "%d ", rand()%1000);
  14. }
  15. fclose(fout);
  16. }
  17. void PrintTopK2(const char*filename, int N, int k)
  18. {
  19. FILE* fout = fopen(filename, "r");
  20. //读取k个数据 空间复杂度0(k)
  21. int* a = (int*)malloc(sizeof(int) * k);
  22. for (int i = 0;i < k;i++)
  23. {
  24. fscanf(fout, "%d", &a[i]);
  25. }
  26. //向下调整法建立小堆 时间复杂度0(k)
  27. for (int i = (k - 2) / 2;i >= 0;i--)
  28. {
  29. AdjustDown(a, k, i);
  30. }
  31. //继续读取,寻找大值,然后重新建堆 时间复杂度(N-k)*logk
  32. int val;
  33. while (fscanf(fout, "%d", &val) != EOF)
  34. {
  35. if (val > a[0]) {
  36. a[0] = val;
  37. AdjustDown(a, k, 0);
  38. }
  39. }
  40. for (int i = 0;i < k;i++)
  41. {
  42. printf("%d ", a[i]);
  43. }
  44. free(a);
  45. fclose(fout);
  46. }
  47. void TestTopk()
  48. {
  49. int N = 1000;
  50. CreateData(N);
  51. PrintTopK2("Heap.txt", N, 10);//建小堆 适合N数据比较大的情况
  52. }


总结

  堆是数据结构中比较重要的一部分,它可以应用到排序的算法中。对于堆,我们一定要掌握其建堆的核心思想,这是重中之重!最后,希望这篇文章对你有益,如果你发现哪里有问题,希望您能不吝赐教。

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

闽ICP备14008679号