C语言刷 堆(优先队列)
堆(优先队列):
定义
堆必须是一个完全二叉树,为啥呢?因为:
堆分为大顶堆(每个父节点都大于子节点)和小顶堆
堆的存储:
联想层序遍历给堆中的节点进行编号:可以用一维数组存储堆的节点
节点下标规律:
父节点下标i 左子节点i
左子节点下标2i + 1 右子节点i + 1
右子节点下标2i + 2 父节点 (i - 1) / 2
基本操作
下沉 和 上浮:
建堆
堆排序
待做的两道题:
295. 数据流的中位数
480. 滑动窗口中位数
703. 数据流中的第 K 大元素
/* 小根堆 */ typedef struct { int heapCapacity; int heapSize; int *heap; } KthLargest; /* 堆顶下标: 0; parent: (k-1)/2; leftChild: 2*k + 1; rightChild: 2*k + 2 */ int ParentIndex(int i) { return (i - 1) / 2; } int LeftChildIndex(int i) { return 2 * i + 1; } int RightChildIndex(int i) { return 2 * i + 2; } void Swap(int *a, int *b) { int tmp = *a; *a = *b; *b = tmp; } /************************************ 堆的有序性的维护 ************************************/ /* * 上浮: * 不符合规则的点(这里是小根堆,规则即父节点最小),与父节点交换(直至符合为止) */ void Swim(int *heap, int i) { while (i > 0 && heap[ParentIndex(i)] > heap[i]) { Swap(&heap[ParentIndex(i)], &heap[i]); i = ParentIndex(i); // 已经上浮一次,更新下次可能上浮的索引 } } /* * 下沉: * 不符合规则的点(这里是小根堆,规则即父节点最小),与子节点中较小的(因为是小根堆)交换(直至符合为止) */ void Sink(int *heap, int heapSize, int i) { while (LeftChildIndex(i) < heapSize) { int smallOneIndex = LeftChildIndex(i); int leftVal = heap[LeftChildIndex(i)]; if (RightChildIndex(i) < heapSize) { int rightVal = heap[RightChildIndex(i)]; // 比较子节点中哪个更小 smallOneIndex = leftVal < rightVal ? smallOneIndex : RightChildIndex(i); } if (heap[i] < heap[smallOneIndex]) { break; } Swap(&heap[i], &heap[smallOneIndex]); i = smallOneIndex; // 已经下沉一次,更新下次可能上浮的索引 } } /* * 出队: * 1.最后一个点换到根(根即待出队的点) * 2.下沉 */ void Pop(int *heap, int *heapSize) { Swap(&heap[0], &heap[*heapSize - 1]); // heap[*heapSize - 1] = 0; (*heapSize)--; Sink(heap, *heapSize, 0); } /* * 入队: * 1.待插入的点放在最后 * 2.上浮 */ void Push(int *heap, int *heapSize, int val) { heap[(*heapSize)++] = val; Swim(heap, *heapSize - 1); } /************************************ 答题 ************************************/ int kthLargestAdd(KthLargest* obj, int val) { if (obj->heapCapacity > obj->heapSize) { Push(obj->heap, &obj->heapSize, val); } else if (val > obj->heap[0]) { // 队列已经满了,并且头节点小于待插入的值 Pop(obj->heap, &obj->heapSize); Push(obj->heap, &obj->heapSize, val); } // 小根堆,每次返回头节点 return obj->heap[0]; } KthLargest* kthLargestCreate(int k, int* nums, int numsSize) { if (k < 1) { return NULL; } KthLargest *obj = (KthLargest *)malloc(sizeof(KthLargest)); obj->heapCapacity = k; obj->heapSize = 0; obj->heap = (int *)malloc(sizeof(int) * k); memset(obj->heap, 0, sizeof(int) * k); for (int i = 0; i < numsSize; i++) { if (obj->heapCapacity > obj->heapSize) { Push(obj->heap, &obj->heapSize, nums[i]); } else { // 堆已经满了,调用add接口 int ret = kthLargestAdd(obj, nums[i]); } } return obj; } void kthLargestFree(KthLargest* obj) { if (obj != NULL) { free(obj->heap); free(obj); } } /** * Your KthLargest struct will be instantiated and called as such: * KthLargest* obj = kthLargestCreate(k, nums, numsSize); * int param_1 = kthLargestAdd(obj, val); * kthLargestFree(obj); */
347. 前 K 个高频元素
解法1:直接用UT_HASH中的排序,返回前k个元素
struct hashTable { int key; // 数组元素 int value; // 数组元素出现的频率 UT_hash_handle hh; }; struct hashTable *g_hash; void AddNode(int num) { struct hashTable *tmp = NULL; HASH_FIND_INT(g_hash, &num, tmp); if (tmp == NULL) { tmp = (struct hashTable *)malloc(sizeof(struct hashTable)); tmp->key = num; tmp->value = 1; HASH_ADD_INT(g_hash, key, tmp); } else { (tmp->value)++; } } /* 排序:逆序 */ int HashCmp(struct hashTable *a, struct hashTable *b) { return b->value - a->value; } /** * Note: The returned array must be malloced, assume caller calls free(). */ int* topKFrequent(int* nums, int numsSize, int k, int* returnSize) { g_hash = NULL; for (int i = 0; i < numsSize; i++) { // 插入到hash表中 AddNode(nums[i]); } // 根据数组元素出现的频次,对hash表进行降序 HASH_SORT(g_hash, HashCmp); int *res = (int *)malloc(sizeof(int) * k); *returnSize = k; int cnt = 0; // 对hash表进行遍历 struct hashTable *cur, *tmp; HASH_ITER(hh, g_hash, cur, tmp) { if (cnt == k) { break; } res[cnt++] = cur->key; } return res; }
解法2:利用最大堆(并不适合做这题)
/** * Note: The returned array must be malloced, assume caller calls free(). */ typedef struct { int num; int count; UT_hash_handle hh; } HashTable; HashTable *g_hash; HashTable* FindNode(int key) { HashTable *tmp = NULL; HASH_FIND_INT(g_hash, &key, tmp); return tmp; } void AddNode(int key) { HashTable *tmp = FindNode(key); if (tmp == NULL) { tmp = malloc(sizeof(HashTable)); tmp->num = key; tmp->count = 1; HASH_ADD_INT(g_hash, num, tmp); } else { tmp->count++; } } typedef struct { int num; int count; } Pair; int g_cur = 0; Pair *g_heap; int ParentId(int i) { return (i - 1) / 2; } int LeftChildId(int i) { return 2 * i + 1; } int RightChildId(int i) { return 2 * i + 2; } void Swap(Pair *a, Pair *b) { Pair tmp = *a; *a = *b, *b = tmp; } /*大顶堆*/ void Push(int num, int count) { // push到队尾 g_heap[g_cur].num = num; g_heap[g_cur].count = count; int i = g_cur; g_cur++; // 判断是否上浮 while (ParentId(i) >= 0 && g_heap[ParentId(i)].count < g_heap[i].count) { Swap(&g_heap[ParentId(i)], &g_heap[i]); i = ParentId(i); } } /*大顶堆*/ void Pop(int cap) { // 交换队头和队尾 Swap(&g_heap[0], &g_heap[g_cur - 1]); // 弹出队尾 g_cur--; // 判断队头是否下沉 int i = 0; int minId = LeftChildId(i); while (minId < cap && g_heap[minId].count < g_heap[i].count) { if (RightChildId(i) < cap) { minId = g_heap[LeftChildId(i)].count < g_heap[RightChildId(i)].count ? LeftChildId(i) : RightChildId(i); } Swap(&g_heap[i], &g_heap[minId]); i = minId; } } int* topKFrequent(int* nums, int numsSize, int k, int* returnSize) { g_hash = NULL; for (int i = 0; i < numsSize; i++) { AddNode(nums[i]); // num -> 出现频次,存在hash中 } g_heap = malloc(sizeof(Pair) * (k + 1)); HashTable *cur, *next; HASH_ITER(hh, g_hash, cur, next) { if (g_cur < k) { // 堆没满 Push(cur->num, cur->count); // g_cur++ } else { // 堆满了 if (cur->count > g_heap[g_cur - 1].count) { g_cur--; Push(cur->num, cur->count); } else { continue; } } HASH_DEL(g_hash, cur); free(cur); } *returnSize = 0; int *res = malloc(sizeof(int) * k); for (int i = 0; i < k; i++) { res[(*returnSize)++] = g_heap[0].num; Pop(k); } free(g_heap); g_cur = 0; return res; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
2020-01-11 分类结果的评价指标