当前位置:   article > 正文

揭秘数据结构的魔力:用堆(Heap)打造高效排序

揭秘数据结构的魔力:用堆(Heap)打造高效排序

个人主页一代... 

个人专栏:数据结构

1. 定义:

  • 堆通常是一个可以被看做一棵树的数组对象,在物理层面上表现为顺序存储结构,但在逻辑层面上是完全二叉树的形态,这意味着除了最后一层外,每一层都被完全填满,且最后一层从左到右填充。
  • 堆中某个结点的值总是不大于或不小于其父结点的值,这一性质使得堆成为最高效的优先级队列。

注意:大堆和小堆的实现都是用数组来实现的,其物理结构是数组 但其逻辑结构是一颗完全二叉树

用数组实现堆,假设其双亲节点的下标为i,则其左孩子节点的下标为2*i+1,右孩子节点的下标为2*i+2

若孩子节点的坐标为i(这里的孩子节点无论是左孩子还是右孩子),其双亲节点的坐标都为(i-1)/2;

如果想要一个数组变为堆,首先要把其看做完全二叉树的结构

如数组a[4]={2,1,3,4}

将其理解为树的结构就变为:(理解为树的结构后就可以对其进行操作,将其变为大堆或小堆)

2. 堆的分类

堆分为大堆和小堆

  1. 大堆:大堆的双亲节点都大于其孩子节点

  1. 小堆:小堆的双亲节点都小于其孩子节点

3. 堆的实现

a. 堆的结构体定义:

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

b. 堆的初始化

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

c. 堆的建立

ⅰ. 向上调整法

向上调整法要从第一个元素开始,向上调整法建小堆时,当孩子节点小于双亲节点时,孩子节点的值就和父亲节点的值交换,孩子节点坐标等于双亲节点的坐标,双亲节点的坐标变为(双亲节点的坐标-1)/ 2,当孩子节点==0时循环结束。

  1. void Swap(HPDataType* a, HPDataType* b)
  2. {
  3. HPDataType tmp = *a;
  4. *a = *b;
  5. *b = tmp;
  6. }
  7. void AdjustUp(HPDataType* a, int child)
  8. //不传结构体,为了后面排序做准备,原因后面会解释
  9. {
  10. assert(a);
  11. int parent = (child - 1) / 2;
  12. while (child > 0)
  13. //parent>=0可以作为结束条件,但是一个巧合,
  14. //因为到最后是依靠当child==0,parent=(child-1/2=-1/2=0
  15. //a[parent]==a[child]结束的
  16. {
  17. if (a[child] < a[parent])//建小堆,若a[child]>a[parent]就建大堆
  18. {
  19. Swap(&a[child], &a[parent]);
  20. child = parent;
  21. parent = (parent - 1) / 2;
  22. }
  23. else
  24. {
  25. break;
  26. }
  27. }
  28. }
1. 向上调整法建堆
  1. void Swap(HPDataType* a, HPDataType* b)
  2. {
  3. HPDataType tmp = *a;
  4. *a = *b;
  5. *b = tmp;
  6. }
  7. void AdjustUp(HPDataType* a, int child)
  8. //不传结构体,为了后面排序做准备,原因后面会解释
  9. {
  10. assert(a);
  11. int parent = (child - 1) / 2;
  12. while (child > 0)
  13. //parent>=0可以作为结束条件,但是一个巧合,
  14. //因为到最后是依靠当child==0,parent=(child-1/2=-1/2=0
  15. //a[parent]==a[child]结束的
  16. {
  17. if (a[child] < a[parent])//建小堆,若a[child]>a[parent]就建大堆
  18. {
  19. Swap(&a[child], &a[parent]);
  20. child = parent;
  21. parent = (parent - 1) / 2;
  22. }
  23. else
  24. {
  25. break;
  26. }
  27. }
  28. }
  29. void HPInitArray(HP* php, HPDataType* a, int n)
  30. {
  31. assert(php);
  32. php->a = (HPDataType*)malloc(sizeof(HPDataType) * n);
  33. if (php->a == NULL)
  34. {
  35. perror("malloc fail!");
  36. return;
  37. }
  38. memcpy(php->a, a, n * sizeof(HPDataType));
  39. php->capacity = php->size = n;
  40. //向上调整
  41. for (int i = 1; i < n; i++)//这里遍历要从下标为1的元素开始,即从第一个孩子节点开始
  42. {
  43. AdjustUp(php->a, i);
  44. }
  45. }
  46. int main()
  47. {
  48. HPDataType a[] = { 50,100,480,76,31,31,226 };
  49. HPSort(a, sizeof(a) / sizeof(a[0]));//之前向上调整和向下调整就是为了这里
  50. //直接传a数组,而不是
  51. for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
  52. {
  53. printf("%d ", a[i]);//排升序
  54. }
  55. HP hp;
  56. HPInit(&hp);
  57. HPInitArray(&hp,a,sizeof(a)/sizeof(HPDataType));
  58. return 0;
  59. }

2. 向上调整法建堆的时间复杂度

所以向上调整建堆的时间复杂度为O(N*logN),向上调整法的时间复杂度为O(logN)(即调整高度次)

ⅱ. 向下调整法

转换之后就变为小堆

向下调整法时要从最后一个元素的双亲节点开始,在向下调整建小堆的过程中,用假设法选出左右孩子中最小的一个,当孩子节点大于双亲节点时,孩子节点就和双亲节点交换,双亲节点等于孩子节点,孩子节点等于双亲节点*2+1(不可以为双亲节点*2+2,必须为有孩子·,以便后面比较左右孩子的大小),当孩子节点的坐标大于等于总数组的大小时,就跳出循环

  1. void Swap(HPDataType* a, HPDataType* b)
  2. {
  3. HPDataType tmp = *a;
  4. *a = *b;
  5. *b = tmp;
  6. }
  7. void AdjustDown(HPDataType* a, int n, int parent)
  8. //不传结构体,为了后面排序做准备,原因后面会解释
  9. {
  10. assert(a);
  11. int child = parent * 2 + 1;
  12. while (child < n)
  13. {
  14. //假设法
  15. if (child + 1 < n && a[child] < a[child + 1])
  16. {
  17. child++;
  18. }
  19. if (a[parent] < a[child])
  20. {
  21. Swap(&a[parent], &a[child]);
  22. parent = child;
  23. child = parent * 2 + 1;//右孩子,不可以为左孩子,因为上面要比较
  24. }
  25. else
  26. {
  27. break;
  28. }
  29. }
  30. }
1. 向下调整法建堆
  1. void Swap(HPDataType* a, HPDataType* b)
  2. {
  3. HPDataType tmp = *a;
  4. *a = *b;
  5. *b = tmp;
  6. }
  7. void AdjustDown(HPDataType* a, int n, int parent)
  8. //不传结构体,为了后面排序做准备,原因后面会解释
  9. {
  10. assert(a);
  11. int child = parent * 2 + 1;
  12. while (child < n)
  13. {
  14. //假设法
  15. if (child + 1 < n && a[child] < a[child + 1])
  16. {
  17. child++;
  18. }
  19. if (a[parent] < a[child])//建大堆
  20. {
  21. Swap(&a[parent], &a[child]);
  22. parent = child;
  23. child = parent * 2 + 1;//右孩子,不可以为左孩子,因为上面要比较
  24. }
  25. else
  26. {
  27. break;
  28. }
  29. }
  30. }
  31. void HPInitArray(HP* php, HPDataType* a, int n)
  32. {
  33. assert(php);
  34. php->a = (HPDataType*)malloc(sizeof(HPDataType) * n);
  35. if (php->a == NULL)
  36. {
  37. perror("malloc fail!");
  38. return;
  39. }
  40. memcpy(php->a, a, n * sizeof(HPDataType));
  41. php->capacity = php->size = n;
  42. //向下调整
  43. for (int i = (php->size - 1 - 1) / 2; i >= 0; i--)
  44. //数组的大小为size,对应下标为size-1,其数组最后一个元素
  45. //的双亲为(size-1-1)/2;
  46. {
  47. AdjustDown(php->a, n, i);
  48. }
  49. }
  50. int main()
  51. {
  52. HPDataType a[] = { 50,100,480,76,31,31,226 };
  53. HPSort(a, sizeof(a) / sizeof(a[0]));//之前向上调整和向下调整就是为了这里
  54. //直接传a数组,而不是
  55. for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
  56. {
  57. printf("%d ", a[i]);//排升序
  58. }
  59. HP hp;
  60. HPInit(&hp);
  61. HPInitArray(&hp,a,sizeof(a)/sizeof(HPDataType));
  62. return 0;
  63. }

2. 向下调整建堆的时间复杂度

所以向下调整法建堆的时间复杂度为O(N),向下调整法的时间复杂度为logN

d. 堆的插入

  1. void HPPush(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. perror("realloc fail!");
  11. return;
  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. }

ⅰ. 时间复杂度

运用了一次向上调整法时间复杂度为O(logN)

e. 堆的删除

堆的删除要交换堆顶元素和最后一个元素,把size--,在用一次向下调整法就可以了,不可以直接删除堆顶元素再建堆,一来这样时间复杂度增大了,二是删除堆顶元素,元素之间关系错乱了,父子变叔侄,兄弟变父子

  1. void HPPop(HP* php)
  2. {
  3. assert(php);
  4. assert(php->size > 0);
  5. Swap(&php->a[0], &php->a[php->size - 1]);
  6. php->size--;
  7. AdjustDown(php->a, php->size, 0);
  8. }

ⅰ. 时间复杂度

运用了一次向下调整法,时间复杂度为O(logN)

f. 堆的判空

  1. bool HPEmpty(HP* php)
  2. {
  3. assert(php);
  4. return php->size == 0;
  5. }

ⅰ. 时间复杂度

时间复杂度为O(1)

g. 取堆元素

  1. HPDataType HPTop(HP* php)
  2. {
  3. assert(php);
  4. return php->a[0];
  5. }

ⅰ. 时间复杂度

时间复杂度为O(1)

h. 堆的摧毁

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

i. 堆的排序(堆的向上调整法和向下调整法不传结构体的原因)

  1. void HPSort(HPDataType* a, int n)
  2. {
  3. //这里不用建大堆选出最大数,建堆的时间复杂度为O(N)
  4. // 取出最大数后再N-1个数进行建堆,执行次数为N-1
  5. //n个数,执行次数为N+N-1+N-2.....+1;时间复杂度为O(N^2)
  6. //,和冒泡排序时间复杂度为一样,没有时间上的优势
  7. for(int i=(n-1-1)/2;i>=0;i--)
  8. {
  9. AdjustDown(a, n, i);
  10. }
  11. //对数组进行升序排列
  12. //需要建大堆
  13. //收尾交换
  14. //把最后一个值不放在堆中,建大堆选出最大数,将其放在最后
  15. int end = n-1;
  16. while (end > 0)
  17. {
  18. Swap(&a[end], &a[0]);
  19. AdjustDown(a, end, 0);
  20. end--;
  21. }
  22. }

原因:由于向上调整法和向下调整法传结构体每次都需要建一个堆,太耗费时间,但如果传数组,在用调整法时就不需要写一个堆的结构体,比如堆的排序

ⅰ. 时间复杂度

向下调整法建堆的时间复杂度为O(N),排序的时间复杂度为和向上调整时间一样,计算方式一样,为O(NlogN),总的时间复杂度为O(NlogN)。

习题:在很大的数据个数中找出最大的k个数,借用文件的形式实现

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include<stdio.h>
  3. #include<assert.h>
  4. #include<time.h>
  5. #include<stdlib.h>
  6. void Swap(int *a,int *b)
  7. {
  8. int tmp = *a;
  9. *a = *b;
  10. *b = tmp;
  11. }
  12. void createNData()
  13. {
  14. srand((unsigned int)time(NULL));
  15. const char* file = "data.txt";//要打开的文件
  16. FILE* fin = fopen(file, "w");
  17. if (fin == NULL)
  18. {
  19. perror("fopen fail");
  20. return;
  21. }
  22. for (int i = 1; i <= 20; i++)
  23. {
  24. int x = rand() % 10000 + 1;
  25. fprintf(fin, "%d\n", x);
  26. }
  27. fclose(fin);
  28. }
  29. void AdjustDown(int* a, int n, int parent)
  30. {
  31. int child = parent * 2 + 1;
  32. while (child < n)
  33. {
  34. if (child + 1 < n && a[child] > a[child + 1])
  35. {
  36. child++;
  37. }
  38. if (a[parent] > a[child])
  39. {
  40. Swap(&a[parent], &a[child]);
  41. parent = child;
  42. child = parent * 2 + 1;
  43. }
  44. else
  45. {
  46. break;
  47. }
  48. }
  49. }
  50. void topk()
  51. {
  52. int k;
  53. int x;
  54. const char* file = "data.txt";//要打开的文件
  55. printf("要找出的最大数字的个数:>");
  56. scanf("%d", &k);
  57. FILE* fout = fopen("data.txt", "r");
  58. if (fout == NULL)
  59. {
  60. perror("fopen fail");
  61. return;
  62. }
  63. int* minheap = (int*)malloc(sizeof(int) * k);
  64. if (minheap == NULL)
  65. {
  66. perror("malloc fail");
  67. return;
  68. }
  69. for (int i = (k - 1 - 1)/2 ; i >= 0; i--)
  70. {
  71. AdjustDown(minheap, k, i);
  72. }
  73. while (fscanf(fout, "%d", &x) != EOF)
  74. {
  75. if (x > minheap[0])
  76. {
  77. minheap[0] = x;
  78. AdjustDown(minheap, k, 0);
  79. }
  80. }
  81. for (int i = 0; i < k; i++)
  82. {
  83. printf("%d\n", minheap[i]);
  84. }
  85. fclose(fout);
  86. }
  87. int main()
  88. {
  89. createNData();
  90. topk();
  91. return 0;
  92. }

堆的实现全部代码

test.c

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include"Heap.h"
  3. int main()
  4. {
  5. HPDataType a[] = { 50,100,480,76,31,31,226 };
  6. HPSort(a, sizeof(a) / sizeof(a[0]));//之前向上调整和向下调整就是为了这里
  7. //直接传a数组,而不是
  8. for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
  9. {
  10. printf("%d ", a[i]);//排升序
  11. }
  12. HP hp;
  13. //HPInitArray(&hp,a,sizeof(a)/sizeof(HPDataType));
  14. //HPInit(&hp);
  15. //HPInitArray(&hp, a, sizeof(a) / sizeof(HPDataType));
  16. //for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
  17. //{
  18. // HPPush(&hp, a[i]);
  19. //}
  20. //printf("%d", HPTop(&hp));
  21. //HPPop(&hp);
  22. //for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
  23. //{
  24. // printf("%d ", (&hp)->a[i]);
  25. //}
  26. return 0;
  27. }

Heap.c

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include"Heap.h"
  3. void HPInit(HP* php)
  4. {
  5. php->a = NULL;
  6. php->capacity = php->size = 0;
  7. }
  8. void Swap(HPDataType* a, HPDataType* b)
  9. {
  10. HPDataType tmp = *a;
  11. *a = *b;
  12. *b = tmp;
  13. }
  14. void AdjustUp(HPDataType* a, int child)
  15. //不传结构体,为了后面排序做准备,原因后面会解释
  16. {
  17. assert(a);
  18. int parent = (child - 1) / 2;
  19. while (child>0)
  20. //parent>=0可以作为结束条件,但是一个巧合,
  21. //因为到最后是依靠当child==0,parent=(child-1/2=-1/2=0
  22. //a[parent]==a[child]结束的
  23. {
  24. if (a[child] < a[parent])
  25. {
  26. Swap(&a[child], &a[parent]);
  27. child = parent;
  28. parent = (parent - 1) / 2;
  29. }
  30. else
  31. {
  32. break;
  33. }
  34. }
  35. }
  36. void HPPush(HP* php, HPDataType x)
  37. {
  38. assert(php);
  39. if (php->size == php->capacity)
  40. {
  41. int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
  42. HPDataType* tmp = (HPDataType*)realloc(php->a,sizeof(HPDataType) * newcapacity);
  43. if (tmp == NULL)
  44. {
  45. perror("realloc fail!");
  46. return;
  47. }
  48. php->a = tmp;
  49. php->capacity = newcapacity;
  50. }
  51. php->a[php->size] = x;
  52. php->size++;
  53. AdjustUp(php->a, php->size - 1);
  54. }
  55. HPDataType HPTop(HP* php)
  56. {
  57. assert(php);
  58. return php->a[0];
  59. }
  60. void AdjustDown(HPDataType* a, int n, int parent)
  61. //不传结构体,为了后面排序做准备,原因后面会解释
  62. {
  63. assert(a);
  64. int child = parent * 2 + 1;
  65. while (child < n)
  66. {
  67. //假设法
  68. if (child + 1 < n && a[child] < a[child + 1])
  69. {
  70. child++;
  71. }
  72. if (a[parent] < a[child])
  73. {
  74. Swap(&a[parent], &a[child]);
  75. parent = child;
  76. child = parent * 2 + 1;//右孩子,不可以为左孩子,因为上面要比较
  77. }
  78. else
  79. {
  80. break;
  81. }
  82. }
  83. }
  84. void HPInitArray(HP* php, HPDataType* a, int n)
  85. {
  86. assert(php);
  87. php->a = (HPDataType*)malloc(sizeof(HPDataType) * n);
  88. if (php->a == NULL)
  89. {
  90. perror("malloc fail!");
  91. return;
  92. }
  93. memcpy(php->a, a, n * sizeof(HPDataType));
  94. php->capacity = php->size = n;
  95. //向上调整
  96. //for (int i = 1; i < n; i++)
  97. //{
  98. // AdjustUp(php->a, i);
  99. // HPPush(php, php->size)//不可以,因为调用函数
  100. // 时php->size在函数内还会增加
  101. //}
  102. //向下调整
  103. for (int i = (php->size - 1 - 1) / 2; i >= 0; i--)
  104. //数组的大小为size,对应下标为size-1,其数组最后一个元素
  105. //的双亲为(size-1-1)/2;
  106. {
  107. AdjustDown(php->a, n, i);
  108. }
  109. }
  110. //时间复杂度为logN
  111. void HPPop(HP* php)
  112. {
  113. assert(php);
  114. assert(php->size > 0);
  115. Swap(&php->a[0], &php->a[php->size - 1]);
  116. php->size--;
  117. AdjustDown(php->a, php->size, 0);
  118. }
  119. bool HPEmpty(HP* php)
  120. {
  121. assert(php);
  122. return php->size == 0;
  123. }
  124. void HPDestroy(HP* php)
  125. {
  126. assert(php);
  127. free(php->a);
  128. php->a = NULL;
  129. php->size = php->capacity = 0;
  130. }
  131. void HPSort(HPDataType* a, int n)
  132. {
  133. //这里不用建大堆选出最大数,建堆的时间复杂度为O(N)
  134. // 取出最大数后再N-1个数进行建堆,执行次数为N-1
  135. //n个数,执行次数为N+N-1+N-2.....+1;时间复杂度为O(N^2)
  136. //,和冒泡排序时间复杂度为一样,没有时间上的优势
  137. for(int i=(n-1-1)/2;i>=0;i--)
  138. {
  139. AdjustDown(a, n, i);
  140. }
  141. //对数组进行升序排列
  142. //需要建大堆
  143. //收尾交换
  144. //把最后一个值不放在堆中,建大堆选出最大数,将其放在最后
  145. int end = n-1;
  146. while (end > 0)
  147. {
  148. Swap(&a[end], &a[0]);
  149. AdjustDown(a, end, 0);
  150. end--;
  151. }
  152. }

Heap.h

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include<stdio.h>
  3. #include<stdlib.h>
  4. #include<assert.h>
  5. #include<stdbool.h>
  6. #include<string.h>
  7. #include<time.h>
  8. typedef int HPDataType;
  9. typedef struct Heap
  10. {
  11. HPDataType* a;
  12. int size;
  13. int capacity;
  14. }HP;
  15. void HPInit(HP* php);
  16. void HPInitArray(HP* php, HPDataType* a, int n);
  17. void HPDestroy(HP* php);
  18. // 插入后保持数据是堆
  19. void HPPush(HP* php, HPDataType x);
  20. HPDataType HPTop(HP* php);
  21. // 删除堆顶的数据
  22. void HPPop(HP* php);
  23. void HPSort(HPDataType* a, int n);
  24. bool HPEmpty(HP* php);
  25. void AdjustUp(HPDataType* a, int child);
  26. void AdjustDown(HPDataType* a, int n, int parent);
  27. void Swap(HPDataType* px, HPDataType* py);

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

闽ICP备14008679号