排序单元前面四道题都是堆,暗示还不够明显吗👀
代码前面除了 heap_insert_value(堆元素插入函数)都是把前面四道题所编写的函数复制过来直接用的
对于 heap_insert_value 函数,需要改造一下,使其也能插入的信息完整多了otherInfo(怪不得 MinHeapNode 里会多了 otherinfo 原来都是有用的太贴心了😭),用 otherinfo 里的 i 和 j 来分别表示【元素所在数组的索引】和【元素在数组中的索引】
思路:
先将每个数组的第一个入堆;输出最小元素的后,将其对应的数组中的下一 个元素入堆,重复即可。
题目:
假设有 n 个长度为 k 的已排好序(升序)的数组,请设计数据结构和算法,将这 n 个数组合并到一个数组,且各元素按升序排列。即实现函数:
void merge_arrays(const int* arr, int n, int k, int* output);
其中 arr 为按行优先保存的 n 个长度都为 k 的数组,output 为合并后的按升序排列的数组,大小为 n×k。
时间要求(评分规则),当 n > k 时:
- 满分:时间复杂度不超过 O(n×k×log(n))
- 75分:时间复杂度不超过 O(n×k×log(n)×k)
- 59分:其它,如:时间复杂度为 O(n2×k2) 时。
代码:
#include<stdio.h>
#include<stdlib.h>
typedef struct _otherInfo
{
int i;
int j;
}OtherInfo;
typedef struct _minHeapNode
{
int value;
OtherInfo otherInfo;
}MinHeapNode, * PMinHeapNode;
typedef struct _minPQ {
PMinHeapNode heap_array; // 指向堆元素数组
int heap_size; // 当前堆中的元素个数
int capacity; //堆数组的大小
}MinHeap, * PMinHeap;
// 返回堆元素数组下标为 i 的结点的父结点下标
int parent(int i) {
i++;// 注意是数组下标 +1才是完全二叉树的顺序编号
return i / 2 - 1;// 注意返回也是数组下标 所以要-1
}
// 返回堆元素数组下标为 i 的结点的左子结点下标
int left(int i) {
i++;
return i * 2 - 1;
}
// 返回堆元素数组下标为 i 的结点的右子结点下标
int right(int i) {
i++;
return i * 2 + 1 - 1;
}
// 交换两个堆元素的值
void swap_node(MinHeapNode* x, MinHeapNode* y) {
MinHeapNode temp = *x; // 通过解引用获取指针指向的实际数据
*x = *y; // 将指针y指向的数据赋值给指针x指向的位置
*y = temp; // 将保存在temp中的数据赋值给指针y指向的位置
}
// 堆初始化
void init_min_heap(PMinHeap pq, int capacity) {
pq->capacity = capacity;
pq->heap_size = 0;
pq->heap_array = (PMinHeapNode)malloc(capacity * sizeof(MinHeapNode));
}
// 堆元素插入
bool heap_insert_value(PMinHeap pq, int value, int k, int j) {
if (pq->heap_size == pq->capacity) {
// 堆已满,无法插入元素
return false;
}
// 创建新的堆节点
PMinHeapNode newNode = (PMinHeapNode)malloc(sizeof(MinHeapNode));
if (newNode == NULL) {
// 内存分配失败
return false;
}
// 将元素插入到堆的最后一个位置
int i = pq->heap_size;
pq->heap_array[i].value = value;
pq->heap_array[i].otherInfo.i = k;
pq->heap_array[i].otherInfo.j = j;
// 更新堆大小
pq->heap_size++;
// 从插入位置开始向上调整堆,以维持最小堆的性质
while (i != 0 && pq->heap_array[parent(i)].value > pq->heap_array[i].value) {
swap_node(&pq->heap_array[i], &pq->heap_array[parent(i)]);
i = parent(i);
}
return true;
}
// 堆化
void min_heapify(PMinHeap pq, int k) {
int smallest = k; // 当前节点标记为最小值
while (true) {
int l = left(k); // 当前节点的左子节点索引
int r = right(k); // 当前节点的右子节点索引
// 比较左子节点与当前节点的值,更新最小值的索引
if (l < pq->heap_size && pq->heap_array[l].value < pq->heap_array[smallest].value) {
smallest = l;
}
// 比较右子节点与当前节点的值,更新最小值的索引
if (r < pq->heap_size && pq->heap_array[r].value < pq->heap_array[smallest].value) {
smallest = r;
}
// 如果最小值不是当前节点,则进行交换,并更新当前节点索引
if (smallest != k) {
swap_node(&pq->heap_array[k], &pq->heap_array[smallest]);
k = smallest;
}
else {
break; // 当前节点已经是最小堆,退出循环
}
}
}
// 提取堆中的最小元素
int extractMin(MinHeap* heap) {
int minElement = heap->heap_array[0].value;
heap->heap_array[0] = heap->heap_array[heap->heap_size - 1];
heap->heap_size--;
min_heapify(heap, 0);
return minElement;
}
void merge_arrays(const int* arr, int n, int k, int* output) {
// 创建最小堆
MinHeap* Heap = (MinHeap*)malloc(sizeof(MinHeap));
init_min_heap(Heap, n);
// 将每个数组的第一个元素插入堆中
for (int i = 0; i < n; i++) {
// 改造了一下insert函数,使其也能插入的信息完整(多了otherInfo)
heap_insert_value(Heap, arr[i * k], i, 0);
// Heap->heap_array[i].otherInfo.i ————所在数组的索引
// Heap->heap_array[i].otherInfo.j ————元素在数组中的索引
}
// 依次从堆中取出最小元素,并将下一个元素插入堆中
for (int i = 0; i < n * k; i++) {
// 获取最小元素所在数组的索引和元素在数组中的索引
int arrIndex = Heap->heap_array[0].otherInfo.i;
int elemIndex = Heap->heap_array[0].otherInfo.j;
// 取出堆中的最小元素
output[i] = extractMin(Heap);
// 如果当前数组还有元素,将下一个元素插入堆中
if (elemIndex < k - 1) {
int value = arr[arrIndex * k + elemIndex + 1];
heap_insert_value(Heap, value, arrIndex, elemIndex + 1);
}
}
// 记得用完堆需要free
free(Heap->heap_array);
free(Heap);
}