【数据结构】堆-C语言版(三)

简介: 【数据结构】堆-C语言版

堆的实现:

045-heap.h

1. #pragma once
2. #include<stdio.h>
3. #include<stdlib.h>
4. #include<string.h>
5. #include<stdbool.h>
6. #include<assert.h>
7. 
8. 
9. typedef int HPDataType;
10. struct Heap
11. {
12.   HPDataType* a;
13.   int size;
14.   int capacity;
15. };
16. 
17. typedef struct Heap HP;
18. 
19. //交换
20. void Swap(int* p1, int* p2);
21. 
22. //向下调整
23. void AdjustDown(int* a, int n, int parent);
24. 
25. //向上调整
26. void AdjustUp(int* a, int child);
27. 
28. //初始化
29. void HeapInit(HP* php, HPDataType* a, int n);
30. 
31. //销毁
32. void HeapDestroy(HP* php);
33. 
34. //堆的插入
35. void HeapPush(HP* php, HPDataType x);
36. 
37. //堆的删除
38. void HeapPop(HP* php);
39. 
40. //返回堆顶元素
41. HPDataType HeapTop(HP* php);
42. 
43. //堆的大小
44. int HeapSize(HP* php);
45. 
46. //判空
47. bool HeapEmpty(HP* php);
48. 
49. //打印
50. void HeapPrint(HP* php);
1. #define  _CRT_SECURE_NO_WARNINGS  1
2. #include "045-Heap.h"
3. 
4. //打印
5. void HeapPrint(HP* php)
6. {
7.  for (int i = 0; i < php->size; i++)
8.  {
9.    printf("%d ", php->a[i]);
10.   }
11.   printf("\n");
12. 
13.   int num = 0;
14.   int levelSize = 1;
15.   for (int i = 0; i < php->size; i++)
16.   {
17.     printf("%d ", php->a[i]);
18.     num++;
19.     if (num == levelSize)
20.     {
21.       printf("\n");
22.       levelSize *= 2;
23.       num = 0;
24. 
25.     }
26. 
27.   }
28. 
29.   printf("\n\n");
30. }
31. 
32. //交换
33. void Swap(int* p1, int* p2)
34. {
35.   int tmp = *p1;
36.   *p1 = *p2;
37.   *p2 = tmp;
38. }
39. 
40. //向下调整
41. void AdjustDown(int* a, int n, int parent)
42. {
43.   int child = parent * 2 + 1;
44.   while (child < n)
45.   {
46.     //选出左右孩子中的小孩子,建大堆就把第二个<改成>
47.     if (child + 1 < n && a[child + 1] < a[child])
48.     {
49.       child++;
50.     }
51.     //小孩子比父亲小,交换小孩子和父亲,建大堆就把<改成>
52.     if (a[child] < a[parent])
53.     {
54.       Swap(&a[child], &a[parent]);
55.       //交换父亲和孩子后,可能导致不满足堆的定义,需要继续调整
56.       parent = child;
57.       child = parent * 2 + 1;
58.     }
59.     //小孩子比父亲大,跳出while循环,结束这一次的向下调整,否则一直死循环并且什么都不做
60.     else
61.     {
62.       break;
63.     }
64.   }
65. }
66. 
67. //向上调整
68. void AdjustUp(int* a, int child)
69. {
70.   int parent = (child - 1) / 2;
71.   while (child > 0)
72.   {
73.     if (a[child] > a[parent])
74.     {
75.       Swap(&a[child], &a[parent]);
76.       child = parent;
77.       parent = (child - 1) / 2;
78.     }
79.     else
80.     {
81.       break;
82.     }
83.   }
84. }
85. 
86. //初始化
87. void HeapInit(HP* php, HPDataType* a, int n)
88. {
89.   assert(php);
90.   php->a = (HPDataType*)malloc(sizeof(HPDataType) * n);
91.   if (php->a == NULL)
92.   {
93.     printf("malloc fail\n");
94.     exit(-1);
95.   }
96. 
97.   memcpy(php->a, a, sizeof(HPDataType) * n);
98.   php->size = n;
99.   php->capacity = n;
100. 
101.  //建堆
102.  for (int i = (n - 1 - 1) / 2; i >= 0; i--)
103.  {
104.    AdjustDown(php->a, php->size, i);
105.  }
106. }
107. 
108. //销毁
109. void HeapDestroy(HP* php)
110. {
111.  assert(php);
112.  free(php->a);
113.  php->a = NULL;
114.  php->size = 0;
115.  php->capacity = 0;
116. }
117. 
118. //堆的插入
119. void HeapPush(HP* php, HPDataType x)
120. {
121.  assert(php);
122. 
123.  //满了则需要增容
124.  if (php->size == php->capacity)
125.  {
126.    HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * 2 * php->capacity);
127.    if (tmp == NULL)
128.    {
129.      printf("realloc fail\n");
130.      exit(-1);
131.    }
132.    php->a = tmp;
133.    php->capacity *= 2;
134.  }
135. 
136.  php->a[php->size] = x;
137.  php->size++;
138. 
139.  AdjustUp(php->a, php->size - 1);
140. }
141. 
142. //堆的删除
143. void HeapPop(HP* php)
144. {
145.  assert(php);
146.  assert(php->size > 0);
147. 
148.  //将堆顶元素和最后一个叶子结点进行交换
149.  Swap(&php->a[0], &php->a[php->size - 1]);
150.  php->size--;
151. 
152.  //对堆顶元素进行向下调整
153.  AdjustDown(php->a, php->size, 0);
154. 
155. }
156. 
157. //返回堆顶元素
158. HPDataType HeapTop(HP* php)
159. {
160.  assert(php);
161.  return php->a[0];
162. }
163. 
164. //堆的大小
165. int HeapSize(HP* php)
166. {
167.  assert(php);
168.  return php->size;
169. }
170. 
171. //堆判空
172. bool HeapEmpty(HP* php)
173. {
174.  assert(php);
175.  return php->size == 0;
176. }

045-test.c

1. #define  _CRT_SECURE_NO_WARNINGS  1
2. #include "045-Heap.h"
3. 
4. int main()
5. {
6.  int a1[] = { 15,18,28,34,65,19,49,25,37,27 };
7.  int n = sizeof(a1) / sizeof(a1[0]);
8. 
9.  HP hp;
10.   HeapInit(&hp, a1, n);
11.   HeapPrint(&hp);
12. 
13.   HeapPush(&hp, 8);
14.   HeapPrint(&hp);
15. 
16.   HeapPush(&hp, 88);
17.   HeapPrint(&hp);
18. 
19.   HeapDestroy(&hp);
20. 
21.   return 0;
22. }

运行结果如下图所示:

TOP k问题

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

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

对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能 数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:

(1)用数据集合中前K个元素来建堆

        求前k个最大的元素,则把前k个数建小堆

        求前k个最小的元素,则把前k个数建大堆

(2)用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素

        如果建了小堆,剩下N-K个元素和堆顶元素比较,若比堆顶元素大,则替换堆顶元素,调堆             如果建了大堆,剩下N-K个元素和堆顶元素比较,若比堆顶元素小,则替换堆顶元素,调堆

将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。


总结:

建堆:向下调整算法

堆的删除:向下调整

堆的插入:向上调整


堆的应用

最小的k个数   OJ链接

思路一:将堆的实现的代码拷贝过来,建小堆,将前k个元素保存到一个数组中输出

1. typedef int HPDataType;
2. struct Heap
3. {
4.  HPDataType* a;
5.  int size;
6.  int capacity;
7. };
8. 
9. typedef struct Heap HP;
10. 
11. //交换
12. void Swap(int* p1, int* p2);
13. 
14. //向下调整
15. void AdjustDown(int* a, int n, int parent);
16. 
17. //向上调整
18. void AdjustUp(int* a, int child);
19. 
20. //初始化
21. void HeapInit(HP* php, HPDataType* a, int n);
22. 
23. //销毁
24. void HeapDestroy(HP* php);
25. 
26. //堆的插入
27. void HeapPush(HP* php, HPDataType x);
28. 
29. //堆的删除
30. void HeapPop(HP* php);
31. 
32. //返回堆顶元素
33. HPDataType HeapTop(HP* php);
34. 
35. //堆的大小
36. int HeapSize(HP* php);
37. 
38. //判空
39. bool HeapEmpty(HP* php);
40. 
41. //打印
42. void HeapPrint(HP* php);
43. 
44. //打印
45. void HeapPrint(HP* php)
46. {
47.   for (int i = 0; i < php->size; i++)
48.   {
49.     printf("%d ", php->a[i]);
50.   }
51.   printf("\n");
52. 
53.   int num = 0;
54.   int levelSize = 1;
55.   for (int i = 0; i < php->size; i++)
56.   {
57.     printf("%d ", php->a[i]);
58.     num++;
59.     if (num == levelSize)
60.     {
61.       printf("\n");
62.       levelSize *= 2;
63.       num = 0;
64. 
65.     }
66. 
67.   }
68. 
69.   printf("\n\n");
70. }
71. 
72. //交换
73. void Swap(int* p1, int* p2)
74. {
75.   int tmp = *p1;
76.   *p1 = *p2;
77.   *p2 = tmp;
78. }
79. 
80. //向下调整
81. void AdjustDown(int* a, int n, int parent)
82. {
83.   int child = parent * 2 + 1;
84.   while (child < n)
85.   {
86.     //选出左右孩子中的小孩子,建大堆就把第二个<改成>
87.     if (child + 1 < n && a[child + 1] < a[child])
88.     {
89.       child++;
90.     }
91.     //小孩子比父亲小,交换小孩子和父亲,建大堆就把<改成>
92.     if (a[child] < a[parent])
93.     {
94.       Swap(&a[child], &a[parent]);
95.       //交换父亲和孩子后,可能导致不满足堆的定义,需要继续调整
96.       parent = child;
97.       child = parent * 2 + 1;
98.     }
99.     //小孩子比父亲大,跳出while循环,结束这一次的向下调整,否则一直死循环并且什么都不做
100.    else
101.    {
102.      break;
103.    }
104.  }
105. }
106. 
107. //向上调整
108. void AdjustUp(int* a, int child)
109. {
110.  int parent = (child - 1) / 2;
111.  while (child > 0)
112.  {
113.    if (a[child] > a[parent])
114.    {
115.      Swap(&a[child], &a[parent]);
116.      child = parent;
117.      parent = (child - 1) / 2;
118.    }
119.    else
120.    {
121.      break;
122.    }
123.  }
124. }
125. 
126. //初始化
127. void HeapInit(HP* php, HPDataType* a, int n)
128. {
129.  assert(php);
130.  php->a = (HPDataType*)malloc(sizeof(HPDataType) * n);
131.  if (php->a == NULL)
132.  {
133.    printf("malloc fail\n");
134.    exit(-1);
135.  }
136. 
137.  memcpy(php->a, a, sizeof(HPDataType) * n);
138.  php->size = n;
139.  php->capacity = n;
140. 
141.  //建堆
142.  for (int i = (n - 1 - 1) / 2; i >= 0; i--)
143.  {
144.    AdjustDown(php->a, php->size, i);
145.  }
146. }
147. 
148. //销毁
149. void HeapDestroy(HP* php)
150. {
151.  assert(php);
152.  free(php->a);
153.  php->a = NULL;
154.  php->size = 0;
155.  php->capacity = 0;
156. }
157. 
158. //堆的插入
159. void HeapPush(HP* php, HPDataType x)
160. {
161.  assert(php);
162. 
163.  //满了则需要增容
164.  if (php->size == php->capacity)
165.  {
166.    HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * 2 * php->capacity);
167.    if (tmp == NULL)
168.    {
169.      printf("realloc fail\n");
170.      exit(-1);
171.    }
172.    php->a = tmp;
173.    php->capacity *= 2;
174.  }
175. 
176.  php->a[php->size] = x;
177.  php->size++;
178. 
179.  AdjustUp(php->a, php->size - 1);
180. }
181. 
182. //堆的删除
183. void HeapPop(HP* php)
184. {
185.  assert(php);
186.  assert(php->size > 0);
187. 
188.  //将堆顶元素和最后一个叶子结点进行交换
189.  Swap(&php->a[0], &php->a[php->size - 1]);
190.  php->size--;
191. 
192.  //对堆顶元素进行向下调整
193.  AdjustDown(php->a, php->size, 0);
194. 
195. }
196. 
197. //返回堆顶元素
198. HPDataType HeapTop(HP* php)
199. {
200.  assert(php);
201.  return php->a[0];
202. }
203. 
204. //堆的大小
205. int HeapSize(HP* php)
206. {
207.  assert(php);
208.  return php->size;
209. }
210. 
211. //堆判空
212. bool HeapEmpty(HP* php)
213. {
214.  assert(php);
215.  return php->size == 0;
216. }
217. 
218. int* getLeastNumbers(int* arr, int arrSize, int k, int* returnSize){
219.     HP hp;
220.     HeapInit(&hp,arr,arrSize);
221. 
222.     int *retArr = (int*)malloc(sizeof(int)*k);
223.     for(int i = 0;i<k;i++)
224.     {
225.         retArr[i] = HeapTop(&hp);
226.         HeapPop(&hp);
227.     }
228. 
229.     HeapDestroy(&hp);
230.     *returnSize = k;
231.     return retArr;
232. }

思路二:求最小的k个数,即Top k问题,用前k个元素建大堆,用剩余的N-k个元素依次和堆顶元素比较,如果比堆顶元素小,则将该元素和堆顶元素进行交换,调堆。最后堆里的元素就是最小的k个数

1. //交换
2. void Swap(int* p1, int* p2)
3. {
4.    int tmp = *p1;
5.    *p1 = *p2;
6.    *p2 = tmp;
7. }
8. 
9. //向下调整
10. void AdjustDown(int* a, int n, int parent)
11. {
12.   int child = parent * 2 + 1;
13.   while (child < n)
14.     {
15.     //选出左右孩子中的小孩子,建大堆就把第二个<改成>
16.     if (child + 1 < n && a[child + 1] > a[child])
17.     {
18.       child++;
19.     }
20.     //小孩子比父亲小,交换小孩子和父亲,建大堆就把<改成>
21.     if (a[child] > a[parent])
22.     {
23.       Swap(&a[child], &a[parent]);
24.       //交换父亲和孩子后,可能导致不满足堆的定义,需要继续调整
25.       parent = child;
26.       child = parent * 2 + 1;
27.     }
28.     //小孩子比父亲大,跳出while循环,结束这一次的向下调整,否则一直死循环并且什么都不做
29.     else
30.     {
31.       break;
32.     }
33.   }
34. }
35. 
36. int* getLeastNumbers(int* arr, int arrSize, int k, int* returnSize){
37.     
38.     if(k == 0)
39.     {
40.         *returnSize = 0;
41.         return NULL;
42.     }
43.     int *retArr = (int*)malloc(sizeof(int)*k);
44.    
45.     //取出前k个数
46.     for(int i=0;i<k;i++)
47.     {
48.         retArr[i] = arr[i];
49.     }
50. 
51.     //对前k个数向下调整建大堆
52.     for(int j = (k-1-1)/2;j>=0;j--)
53.     {
54.         AdjustDown(retArr,k,j);
55.     }
56. 
57.     //剩下的arrSize - k个数,如果比堆顶的元素小,就将该元素和堆顶元素进行交换,调堆
58.     for(int m =  k;m<arrSize;m++)
59.     {
60.         if(arr[m] < retArr[0])
61.         {
62.             retArr[0] = arr[m];
63.             AdjustDown(retArr,k,0);
64.         }
65.     }
66. 
67.     *returnSize = k;
68.     return retArr;
69. }
相关文章
|
11天前
|
存储 Java
【数据结构】优先级队列(堆)从实现到应用详解
本文介绍了优先级队列的概念及其底层数据结构——堆。优先级队列根据元素的优先级而非插入顺序进行出队操作。JDK1.8中的`PriorityQueue`使用堆实现,堆分为大根堆和小根堆。大根堆中每个节点的值都不小于其子节点的值,小根堆则相反。文章详细讲解了如何通过数组模拟实现堆,并提供了创建、插入、删除以及获取堆顶元素的具体步骤。此外,还介绍了堆排序及解决Top K问题的应用,并展示了Java中`PriorityQueue`的基本用法和注意事项。
21 5
【数据结构】优先级队列(堆)从实现到应用详解
|
17天前
|
存储 人工智能 C语言
数据结构基础详解(C语言): 栈的括号匹配(实战)与栈的表达式求值&&特殊矩阵的压缩存储
本文首先介绍了栈的应用之一——括号匹配,利用栈的特性实现左右括号的匹配检测。接着详细描述了南京理工大学的一道编程题,要求判断输入字符串中的括号是否正确匹配,并给出了完整的代码示例。此外,还探讨了栈在表达式求值中的应用,包括中缀、后缀和前缀表达式的转换与计算方法。最后,文章介绍了矩阵的压缩存储技术,涵盖对称矩阵、三角矩阵及稀疏矩阵的不同压缩存储策略,提高存储效率。
|
17天前
|
C语言
数据结构基础详解(C语言):图的基本概念_无向图_有向图_子图_生成树_生成森林_完全图
本文介绍了图的基本概念,包括图的定义、无向图与有向图、简单图与多重图等,并解释了顶点度、路径、连通性等相关术语。此外还讨论了子图、生成树、带权图及几种特殊形态的图,如完全图和树等。通过这些概念,读者可以更好地理解图论的基础知识。
|
19天前
|
存储 算法 C语言
数据结构基础详解(C语言): 二叉树的遍历_线索二叉树_树的存储结构_树与森林详解
本文从二叉树遍历入手,详细介绍了先序、中序和后序遍历方法,并探讨了如何构建二叉树及线索二叉树的概念。接着,文章讲解了树和森林的存储结构,特别是如何将树与森林转换为二叉树形式,以便利用二叉树的遍历方法。最后,讨论了树和森林的遍历算法,包括先根、后根和层次遍历。通过这些内容,读者可以全面了解二叉树及其相关概念。
|
19天前
|
存储 机器学习/深度学习 C语言
数据结构基础详解(C语言): 树与二叉树的基本类型与存储结构详解
本文介绍了树和二叉树的基本概念及性质。树是由节点组成的层次结构,其中节点的度为其分支数量,树的度为树中最大节点度数。二叉树是一种特殊的树,其节点最多有两个子节点,具有多种性质,如叶子节点数与度为2的节点数之间的关系。此外,还介绍了二叉树的不同形态,包括满二叉树、完全二叉树、二叉排序树和平衡二叉树,并探讨了二叉树的顺序存储和链式存储结构。
|
19天前
|
存储 C语言
数据结构基础详解(C语言): 栈与队列的详解附完整代码
栈是一种仅允许在一端进行插入和删除操作的线性表,常用于解决括号匹配、函数调用等问题。栈分为顺序栈和链栈,顺序栈使用数组存储,链栈基于单链表实现。栈的主要操作包括初始化、销毁、入栈、出栈等。栈的应用广泛,如表达式求值、递归等场景。栈的顺序存储结构由数组和栈顶指针构成,链栈则基于单链表的头插法实现。
121 3
|
19天前
|
存储 C语言
数据结构基础详解(C语言): 树与二叉树的应用_哈夫曼树与哈夫曼曼编码_并查集_二叉排序树_平衡二叉树
本文详细介绍了树与二叉树的应用,涵盖哈夫曼树与哈夫曼编码、并查集以及二叉排序树等内容。首先讲解了哈夫曼树的构造方法及其在数据压缩中的应用;接着介绍了并查集的基本概念、存储结构及优化方法;随后探讨了二叉排序树的定义、查找、插入和删除操作;最后阐述了平衡二叉树的概念及其在保证树平衡状态下的插入和删除操作。通过本文,读者可以全面了解树与二叉树在实际问题中的应用技巧和优化策略。
|
19天前
|
存储 算法 C语言
C语言手撕数据结构代码_顺序表_静态存储_动态存储
本文介绍了基于静态和动态存储的顺序表操作实现,涵盖创建、删除、插入、合并、求交集与差集、逆置及循环移动等常见操作。通过详细的C语言代码示例,展示了如何高效地处理顺序表数据结构的各种问题。
|
11月前
|
存储 缓存 C语言
数据结构——双链表(C语言)
数据结构——双链表(C语言)
|
3月前
|
存储
数据结构——双向链表(C语言版)
数据结构——双向链表(C语言版)
28 2