我是
靠谱客的博主
飘逸小白菜,最近开发中收集的这篇文章主要介绍
合并K个有序数组,觉得挺不错的,现在分享给大家,希望可以做个参考。
给定K个有序数组,每个数组有n个元素,想把这些数组合并成一个有序数组。
算法原理及实现
一. 最简单的方法是创建一个n*k大小的数组,然后把所有数字拷贝进去,然后再进行时间复杂度为O(nlogn)排序算法,这样总体时间复杂度为O(nklognk)
二. 可以利用最小堆完成,时间复杂度是O(nklogk),具体过程如下:
- 创建一个大小为n*k的数组保存最后的结果
- 创建一个大小为k的最小堆,堆中元素为k个数组中的每个数组的第一个元素
- 重复下列步骤n*k次:
- 每次从堆中取出最小元素(堆顶元素),并将其存入输出数组中
- 用堆顶元素所在数组的下一元素将堆顶元素替换掉,
- 如果数组中元素被取光了,将堆顶元素替换为无穷大。每次替换堆顶元素后,重新调整堆
下面给出相关算法的C++实现代码
- #include<iostream>
- #include<limits.h>
- using namespace std;
-
- #define n 4
-
-
- struct MinHeapNode
- {
- int element;
- int i;
- int j;
- };
-
-
- void swap(MinHeapNode *x, MinHeapNode *y);
-
- class MinHeap
- {
- MinHeapNode *harr;
- int heap_size;
- public:
-
- MinHeap(MinHeapNode a[], int size);
-
-
- void MinHeapify(int );
-
-
- int left(int i) { return (2*i + 1); }
-
-
- int right(int i) { return (2*i + 2); }
-
-
- MinHeapNode getMin() { return harr[0]; }
-
-
- void replaceMin(MinHeapNode x) { harr[0] = x; MinHeapify(0); }
- };
- int *mergeKArrays(int arr[][n], int k)
- {
- int *output = new int[n*k];
-
-
- MinHeapNode *harr = new MinHeapNode[k];
- for (int i = 0; i < k; i++)
- {
- harr[i].element = arr[i][0];
- harr[i].i = i;
- harr[i].j = 1;
- }
- MinHeap hp(harr, k);
-
-
- for (int count = 0; count < n*k; count++)
- {
-
- MinHeapNode root = hp.getMin();
- output[count] = root.element;
-
-
- if (root.j < n)
- {
- root.element = arr[root.i][root.j];
- root.j += 1;
- }
-
- else root.element = INT_MAX;
-
-
- hp.replaceMin(root);
- }
-
- return output;
- }
- MinHeap::MinHeap(MinHeapNode a[], int size)
- {
- heap_size = size;
- harr = a;
- int i = (heap_size - 1)/2;
- while (i >= 0)
- {
- MinHeapify(i);
- i--;
- }
- }
-
-
-
- void MinHeap::MinHeapify(int i)
- {
- int l = left(i);
- int r = right(i);
- int smallest = i;
- if (l < heap_size && harr[l].element < harr[i].element)
- smallest = l;
- if (r < heap_size && harr[r].element < harr[smallest].element)
- smallest = r;
- if (smallest != i)
- {
- swap(&harr[i], &harr[smallest]);
- MinHeapify(smallest);
- }
- }
- void swap(MinHeapNode *x, MinHeapNode *y)
- {
- MinHeapNode temp = *x; *x = *y; *y = temp;
- }
-
-
- void printArray(int arr[], int size)
- {
- for (int i=0; i < size; i++)
- cout << arr[i] << " ";
- }
-
-
- int main()
- {
-
- int arr[][n] = {{2, 6, 12, 34},
- {1, 9, 20, 1000},
- {23, 34, 90, 2000}};
- int k = sizeof(arr)/sizeof(arr[0]);
-
- int *output = mergeKArrays(arr, k);
-
- cout << "Merged array is " << endl;
- printArray(output, n*k);
-
- return 0;
- }
算法时间复杂度理解
1. 建堆的时间复杂度
堆排序有两种建堆方式,一种是知道了n个元素,进行建堆排序,另一种是逐次添加一个元素,一个n个,进行建堆排序
(1)第一种就是普通的堆排序:建堆+调整。
建堆是从最后一个非叶子节点开始,总共要遍历n/2个元素,每次调整需要的时间为O(logn),所以大致整体的运行时间复杂度为O(nlogn).但是如果更进一步分析,堆排序的实际情况会更理想一些,因为是从最后一个非叶子节点开始排序的,就算要调整,最多需要一次,不需要logn这么多次。
建堆过程实际是从完全二叉树最后一个非叶子节点开始,将其与子节点进行比较和交换。对于每个非叶子节点来说,最多进行2次比较和交换操作,因此整个建堆的时间复杂度为
O(n)
(2)第二种是插入建堆:它的大致步骤如下:
a. 首先增加堆的长度,在最末尾的地方加入最新插入的元素
b. 比较当前元素和它的父结点值,如果比父结点值大,则交换两个元素,否则返回
c. 重复步骤b
最优情况就是n。
最坏的情况下,每次新增加一个元素都需要调整到它的根结点。而这个长度为logn。那么它的时间复杂度为O(nlogn):
2. 排序的时间复杂度
完全二叉树的第i个节点到根节点的距离为 floor(logn) + 1,所以第i次取堆顶记录重建堆需要用O(logi)时间,且需要取n-1次堆顶元素,所以调整堆的时间复杂度为O(nlogn)
所以总体而言,堆排序的时间复杂度为O(nlogn),因为堆排序对原始记录的排序状态不敏感,因此它的最好最坏和平均时间复杂度都是O(nlogn)。
发表评论 取消回复