【数据结构】——外部排序(K路归并)
问题:给你一个20G大小的数组,但是内存只有4G,如何进行排序。 这个问题在面试中出现的还比较高频。
解决方式就是使用归并排序,分批读取这个数组,比如每次读取3个G左右,对这部分数据排序后写到磁盘,然后读取下一批,这样最后大约会有7个小文件,每个小文件内部都有序。
然后加载这7个小文件,用所有小文件的首元素构建一个初始堆,后续利用堆排序每次取堆顶最小元素加入到最终结果,然后从这个堆顶最小元素所在的文件中继续取下一个元素加到堆顶继续堆调整,一直重复直到所有小文件数据都被读取完成。
所以归并排序主要就是用来解决数据远超内存的排序场景,且空间复杂度取决于归并的路数K,跟数据量大小无关。
下面基于python实现对随机生成的7个数组进行7路归并,链表版本的实现可以参考力扣上的这道题解 23. 合并 K 个升序链表:
from typing import Listdef k_way_merge_sort(arrays: List[List[int]]) -> List[int]:def adjust_heap(nums, p, end_idx):while 2 * p + 1 <= end_idx:c = 2 * p + 1if c + 1 <= end_idx and nums[c + 1][0] < nums[c][0]:c += 1if nums[p][0] <= nums[c][0]:breaknums[p], nums[c] = nums[c], nums[p]p = cdef create_heap(nums):last_not_leaf = len(nums) // 2 - 1while last_not_leaf >= 0:adjust_heap(nums, last_not_leaf, len(nums) - 1)last_not_leaf -= 1k_nums = []for idx, arr in enumerate(arrays):if arr:k_nums.append((arr[0], 0, idx)) # (值, 值索引, 子序列索引)if not k_nums:return []create_heap(k_nums)result = []while k_nums:value, value_idx, arr_idx = k_nums[0]result.append(value)if value_idx < len(arrays[arr_idx]) - 1:k_nums[0] = (arrays[arr_idx][value_idx + 1], value_idx + 1, arr_idx)adjust_heap(k_nums, 0, len(k_nums) - 1)else:k_nums[0], k_nums[len(k_nums) - 1] = k_nums[len(k_nums) - 1], k_nums[0]k_nums.pop()if k_nums:adjust_heap(k_nums, 0, len(k_nums) - 1)return resultif __name__ == '__main__':import randomrandom.seed(42)test_arrays = [sorted([random.randint(1, 100) for _ in range(random.randint(0, 10))])for _ in range(7)] # 随机生成包含7个有序子数组的二维数组for idx, arr in enumerate(test_arrays):print(f'arr{idx}: {arr}')print(f'merge result: {k_way_merge_sort(test_arrays)}')
仅考虑以上单次归并的过程,时间复杂度O(n∗log2k)Ο(n*log_2{k})O(n∗log2k),k是总的归并路数,n是总的元素个数。