我知道这个问题已被提出,并且使用最小堆有一个非常好的优雅解决方案。
我的问题是如何使用合并排序的合并功能来做到这一点。
您已经有一组已排序的数组。所以你应该能够在O(nlog K)时间内将它们全部合并到一个数组中,对吗?
我只是想不通怎么做!
说我有
[[5,6],[3,4],[1,2],[0]]
第1步:[[3,4,5,6],[0,1,2]]
第2步:[[0,1,2,3,4,5,6]]
有一种简单的方法吗? O(nlog K)理论上是否可以通过mergesort实现?
答案 0 :(得分:12)
正如其他人所说,使用最小堆来保存下一个项目是最佳方式。它被称为N路合并。它的复杂性是O(n log k)。
你可以使用双向合并算法对k阵列进行排序。也许最简单的方法是修改标准合并排序,以便它使用非常量分区大小。例如,假设您有4个长度为10,8,12和33的数组。每个数组都已排序。如果将数组连接成一个,则会有这些分区(数字是数组的索引,而不是值):
[0-9][10-17][18-29][30-62]
合并排序的第一遍将具有0和10的起始索引。您可以将其合并到新数组中,就像使用标准合并排序一样。下一遍将从第二个阵列中的位置18和30开始。完成第二遍后,输出数组包含:
[0-17][18-62]
现在你的分区从0和18开始。你将这两个合并到一个数组中就完成了。
唯一真正的区别是,不是以分区大小为2并且加倍,而是具有非常量分区大小。在进行每次传递时,新分区大小是您在上一次传递中使用的两个分区的大小之和。这实际上只是对标准合并排序的一点修改。
需要使用log(k)传递来进行排序,并在每次传递时查看所有n个项目。该算法为O(n log k),但具有比N路合并高得多的常数。
实现时,构建一个包含每个子数组的起始索引的整数数组。所以在上面的例子中你会得到:
int[] partitions = [0, 10, 18, 30];
int numPartitions = 4;
现在进行标准合并排序。但是您从partitions
阵列中选择了分区。所以你的合并将从以下开始:
merge (inputArray, outputArray, part1Index, part2Index, outputStart)
{
part1Start = partitions[part1Index];
part2Start = partitions[part2Index];
part1Length = part2Start - part1Start;
part2Length = partitions[part2Index-1] - part2Start;
// now merge part1 and part2 into the output array,
// starting at outputStart
}
你的主循环看起来像是:
while (numPartitions > 1)
{
for (int p = 0; p < numPartitions; p += 2)
{
outputStart = partitions[p];
merge(inputArray, outputArray, p, p+1, outputStart);
// update partitions table
partitions[p/2] = partitions[p] + partitions[p+1];
}
numPartitions /= 2;
}
这是基本的想法。当数字是奇数时,你将不得不做一些工作来处理悬空分区,但总的来说就是它是如何完成的。
您也可以通过维护一个数组数组,并将每两个数组合并为一个新数组,并将其添加到数组的输出数组中来实现。泡沫,冲洗,重复。
答案 1 :(得分:5)
你应该注意到,当我们说复杂度为O(n log k)时,我们假设n表示所有k个数组中的元素总数,即最终合并数组中的元素数。
例如,如果要合并每个包含n个元素的k个数组,则最终数组中的元素总数将为nk。所以复杂性将是O(nk log k)。
答案 2 :(得分:2)
有不同的方法来合并数组。要在N * Log(K)时间内完成该任务,您可以使用名为Heap的结构(实现优先级队列是一种很好的结构)。我想你已经拥有它,如果你没有,那么请选择任何可用的实现:http://en.wikipedia.org/wiki/Heap_(data_structure) 然后你就可以这样做:
1. We have A[1..K] array of arrays to sort, Head[1..K] - current pointer for every array and Count[1..K] - number of items for every array.
2. We have Heap of pairs (Value: int; NumberOfArray: int) - empty at start.
3. We put to the heap first item of every array - initialization phase.
4. Then we organize cycle:
5. Get pair (Value, NumberOfArray) from the heap.
6. Value is next value to output.
7. NumberOfArray – is number of array where we need to take next item (if any) and place to the heap.
8. If heap is not empty, then repeat from step 5
因此,对于每个项目,我们只使用从K项目构建的堆作为最大值。这意味着我们会根据您的要求提供N * Log(K)复杂度。
答案 3 :(得分:1)
我在python中实现了它。主要思想与mergesort类似。 列表中有k个数组。在函数mainMerageK中,只需将列表(k)划分为左(k / 2)和右(k / 2)。因此,分区的总数是log(k)。关于函数合并,很容易知道运行时是O(n)。最后,我们得到O(n log k) 顺便说一下,它也可以在min heap中实现,并且有一个链接:Merging K- Sorted Lists using Priority Queue
def mainMergeK(*lists):
# implemented by k-way partition
k = len(lists)
if k > 1:
mid = int(k / 2)
B = mainMergeK(*lists[0: mid])
C = mainMergeK(*lists[mid:])
A = merge(B, C)
print B, ' + ', C, ' = ', A
return A
return lists[0]
def merge(B, C):
A = []
p = len(B)
q = len(C)
i = 0
j = 0
while i < p and j < q:
if B[i] <= C[j]:
A.append(B[i])
i += 1
else:
A.append(C[j])
j += 1
if i == p:
for c in C[j:]:
A.append(c)
else:
for b in B[i:]:
A.append(b)
return A
if __name__ == '__main__':
x = mainMergeK([1, 3, 5], [2, 4, 6], [7, 8, 10], [9])
print x
输出如下:
[1, 3, 5] + [2, 4, 6] = [1, 2, 3, 4, 5, 6]
[7, 8, 10] + [9] = [7, 8, 9, 10]
[1, 2, 3, 4, 5, 6] + [7, 8, 9, 10] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
答案 4 :(得分:1)
除了K项之外,就像双向合并一样。将导致O(NK)。如果你想要O(N logK),你将需要使用最小堆来跟踪下面算法中的K指针(源数组作为元数据):
保留一个K元素数组 - 即K指针显示每个数组中的位置。 标记所有K个元素都有效。
循环: 比较K指针中有效的值。如果值为最小值,请选择最小索引指针并将其增加到数组中的下一个值。如果递增的值已超过其数组,则将其标记为无效。 将最小值添加到结果中。 重复直到所有K元素都无效。
例如:
Positions Arrays
p1:0 Array 1: 0 5 10
p2:3 Array 2: 3 6 9
p3:2 Array 3: 2 4 6
输出(min为0,3,2)=&gt; 0.因此输出为{0}
Array
p1:5 0 5 10
p2:3 3 6 9
p3:2 2 4 6
输出(最小值为5,3,2)=&gt; 2.所以{0,2}
Array
p1:5 0 5 10
p2:3 3 6 9
p3:4 2 4 6
输出(最小值为5,3,4)=&gt; 3。所以{0,2,3} ..等等..直到你来到一个输出为{0,2,3,4,5,6}
的状态 Array
p1:5 0 5 10
p2:9 3 6 9
p3:6 2 4 6
输出(min为5,9,6)=&gt; 6。所以{0,2,3,4,5,6} + {6}当你将数据标记为“无效”时将p3标记为“无效”。 (或者如果你使用的是min-heap,你只需删除min-item,获取它的源数组元数据:在本例中为数组3,看看它已经完成,所以你不会在min-heap中添加任何新内容)