我是靠谱客的博主 飘逸小白菜,最近开发中收集的这篇文章主要介绍合并K个有序数组,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

目录(?)[-]

  1. 算法原理及实现
  2. 算法时间复杂度理解
    1. 建堆的时间复杂度
    2. 排序的时间复杂度

给定K个有序数组,每个数组有n个元素,想把这些数组合并成一个有序数组。

算法原理及实现

一. 最简单的方法是创建一个n*k大小的数组,然后把所有数字拷贝进去,然后再进行时间复杂度为O(nlogn)排序算法,这样总体时间复杂度为O(nklognk)

二. 可以利用最小堆完成,时间复杂度是O(nklogk),具体过程如下:

  1. 创建一个大小为n*k的数组保存最后的结果
  2. 创建一个大小为k的最小堆,堆中元素为k个数组中的每个数组的第一个元素
  3. 重复下列步骤n*k次:
    1. 每次从堆中取出最小元素(堆顶元素),并将其存入输出数组中
    2. 用堆顶元素所在数组的下一元素将堆顶元素替换掉,
    3. 如果数组中元素被取光了,将堆顶元素替换为无穷大。每次替换堆顶元素后,重新调整堆
下面给出相关算法的C++实现代码

[cpp]  view plain  copy
  1. #include<iostream>  
  2. #include<limits.h>  
  3. using namespace std;  
  4.    
  5. #define n 4  
  6.    
  7. // 定义最小堆节点  
  8. struct MinHeapNode  
  9. {  
  10.     int element; // 带排序元素  
  11.     int i; // 数组索引,element是从哪个数组取得  
  12.     int j; // 元素索引,下一个element的索引  
  13. };  
  14.    
  15. // 定义一个最小节点之间的比较函数  
  16. void swap(MinHeapNode *x, MinHeapNode *y);  
  17.   
  18. class MinHeap  
  19. {  
  20.     MinHeapNode *harr; // 定义一个指针,指向最小堆的堆顶元素  
  21.     int heap_size; // 堆的大小  
  22. public:  
  23.     // 构造函数(建堆):构造一个指定大小的最小堆  
  24.     MinHeap(MinHeapNode a[], int size);  
  25.    
  26.     // 调整堆:调整给定根结点的子树结构  
  27.     void MinHeapify(int );  
  28.    
  29.     // 给定节点的左子节点  
  30.     int left(int i) { return (2*i + 1); }  
  31.    
  32.     // 给定节点的右子节点  
  33.     int right(int i) { return (2*i + 2); }  
  34.    
  35.     // 获取根节点值(堆顶元素)  
  36.     MinHeapNode getMin() { return harr[0]; }  
  37.    
  38.     // 替换根节点(堆顶元素),重新调整堆  
  39.     void replaceMin(MinHeapNode x) { harr[0] = x;  MinHeapify(0); }  
  40. };  
[cpp]  view plain  copy
  1. int *mergeKArrays(int arr[][n], int k)  
  2. {  
  3.     int *output = new int[n*k];  // 最后输出的数组,保存排序结果  
  4.    
  5.     // 创建一个大小为k的最小堆,堆中元素为k个数组中的每个数组的第一个元素  
  6.     MinHeapNode *harr = new MinHeapNode[k];  
  7.     for (int i = 0; i < k; i++)  
  8.     {  
  9.         harr[i].element = arr[i][0]; // 每个数组的第一个元素(每个数组的最小元素)  
  10.         harr[i].i = i;  // 当前数组索引  
  11.         harr[i].j = 1;  // 该数组下一个元素的索引(判断元素是否用完)  
  12.     }  
  13.     MinHeap hp(harr, k); // 对上述大小为k的数组建堆  
  14.    
  15.     // 逐次取出堆顶元素,存入输出数组中,并将其替换为所在数组的下一元素  
  16.     for (int count = 0; count < n*k; count++)  
  17.     {  
  18.         // 取堆顶,存结果  
  19.         MinHeapNode root = hp.getMin();  
  20.         output[count] = root.element;  
  21.    
  22.         // 替换堆顶  
  23.         if (root.j < n)  
  24.         {  
  25.             root.element = arr[root.i][root.j];  
  26.             root.j += 1;  
  27.         }  
  28.         // 当前数组元素用完了,设堆顶为无穷大  
  29.         else root.element =  INT_MAX; //INT_MAX is for infinite  
  30.    
  31.         // 如果还有元素,就替换堆顶元素,调整堆结构  
  32.         hp.replaceMin(root);  
  33.     }  
  34.    
  35.     return output;  
  36. }  
  37. MinHeap::MinHeap(MinHeapNode a[], int size)  
  38. {  
  39.     heap_size = size;  
  40.     harr = a;  // store address of array  
  41.     int i = (heap_size - 1)/2;  
  42.     while (i >= 0)  
  43.     {  
  44.         MinHeapify(i);  
  45.         i--;  
  46.     }  
  47. }  
  48.    
  49. // 递归的调整  
  50. // This method assumes that the subtrees are already heapified  
  51. void MinHeap::MinHeapify(int i)  
  52. {  
  53.     int l = left(i);  
  54.     int r = right(i);  
  55.     int smallest = i;  
  56.     if (l < heap_size && harr[l].element < harr[i].element)  
  57.         smallest = l;  
  58.     if (r < heap_size && harr[r].element < harr[smallest].element)  
  59.         smallest = r;  
  60.     if (smallest != i)  
  61.     {  
  62.         swap(&harr[i], &harr[smallest]);  
  63.         MinHeapify(smallest);  
  64.     }  
  65. }  
  66. void swap(MinHeapNode *x, MinHeapNode *y)  
  67. {  
  68.     MinHeapNode temp = *x;  *x = *y;  *y = temp;  
  69. }  
  70.    
  71. // 输出排序结果  
  72. void printArray(int arr[], int size)  
  73. {  
  74.    for (int i=0; i < size; i++)  
  75.        cout << arr[i] << " ";  
  76. }  
  77.    
  78.   
  79. int main()  
  80. {  
  81.   
  82.     int arr[][n] =  {{2, 6, 12, 34},  
  83.                      {1, 9, 20, 1000},  
  84.                      {23, 34, 90, 2000}};  
  85.     int k = sizeof(arr)/sizeof(arr[0]);  
  86.    
  87.     int *output = mergeKArrays(arr, k);  
  88.    
  89.     cout << "Merged array is " << endl;  
  90.     printArray(output, n*k);  
  91.    
  92.     return 0;  
  93. }  

算法时间复杂度理解

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)。

最后

以上就是飘逸小白菜为你收集整理的合并K个有序数组的全部内容,希望文章能够帮你解决合并K个有序数组所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(44)

评论列表共有 0 条评论

立即
投稿
返回
顶部