堆-给出


15

最有可能在问过这个问题。来自CLRS(第二版)问题6.5-8-

给出一个时间算法,将k个排序列表合并为一个排序列表,其中n是所有输入列表中元素的总数。(提示:请使用最小堆进行k向合并。)O(nlgk)knk

由于有排序的列表且总共有n个值,所以我们假设每个列表包含n个kn数字,而且每个列表都严格按升序排序,结果也将按升序存储。nk

我的伪代码看起来像这样-

    list[k]   ; k sorted lists
    heap[k]   ; an auxiliary array to hold the min-heap
    result[n] ; array to store the sorted list
    for i := 1 to k                 ; O(k)
    do
        heap[i] := GET-MIN(list[i]) ; pick the first element 
                                    ; and keeps track of the current index - O(1)
    done
    BUILD-MIN-HEAP(heap) ; build the min-heap - O(k)
    for i := 1 to n
    do
        array[i] := EXTRACT-MIN(heap)   ; store the min - O(logk)
        nextMin := GET-MIN(list[1])     ; get the next element from the list 1 - O(1)
        ; find the minimum value from the top of k lists - O(k)
        for j := 2 to k                 
        do
            if GET-MIN(list[j]) < nextMin
                nextMin := GET-MIN(list[j]) 
        done
        ; insert the next minimum into the heap - O(logk)
        MIN-HEAP-INSERT(heap, nextMin)
    done

我的整体复杂度变。我找不到任何办法避免Ô ķ 内部循环Ø ñ O(k)+O(k)+O(n(k+2lgk))O(nk+nlgk)O(nk)O(k)O(n)循环查找k个列表中的下一个最小元素。还有其他办法吗?如何获得算法?O(nlgk)

Answers:


13

堆的目的是给您最少的内存,所以我不确定此for循环的目的是- for j := 2 to k

我对伪代码的看法:

lists[k][?]      // input lists
c = 0            // index in result
result[n]        // output
heap[k]          // stores index and applicable list and uses list value for comparison
                 // if i is the index and k is the list
                 //   it has functions - insert(i, k) and deleteMin() which returns i,k
                 // the reason we use the index and the list, rather than just the value
                 //   is so that we can get the successor of any value

// populate the initial heap
for i = 1:k                   // runs O(k) times
  heap.insert(0, k)           // O(log k)

// keep doing this - delete the minimum, insert the next value from that list into the heap
while !heap.empty()           // runs O(n) times
  i,k = heap.deleteMin();     // O(log k)
  result[c++] = lists[k][i]
  i++
  if (i < lists[k].length)    // insert only if not end-of-list
    heap.insert(i, k)         // O(log k)

因此,总时间复杂度为O(klogk+n2logk)=O(nlogk)

您也可以代替(deleteMininsert)使用getMin)和(O(1)incrementIndex),这将减少常数因子,但不会降低复杂度。O(logk)

示例:(
为清楚起见,使用值而不是索引和列表索引以及堆表示为排序数组)

Input: [1, 10, 15], [4, 5, 6], [7, 8, 9]

Initial heap: [1, 4, 7]

Delete 1, insert 10
Result: [1]
Heap: [4, 7, 10]

Delete 4, insert 5
Result: [1, 4]
Heap: [5, 7, 10]

Delete 5, insert 6
Result: [1, 4, 5]
Heap: [6, 7, 10]

Delete 6, insert nothing
Result: [1, 4, 5, 6]
Heap: [7, 10]

Delete 7, insert 8
Result: [1, 4, 5, 6, 7]
Heap: [8, 10]

Delete 8, insert 9
Result: [1, 4, 5, 6, 7, 8]
Heap: [9, 10]

Delete 9, insert nothing
Result: [1, 4, 5, 6, 7, 8, 9]
Heap: [10]

Delete 10, insert 15
Result: [1, 4, 5, 6, 7, 8, 9, 10]
Heap: [15]

Delete 15, insert nothing
Result: [1, 4, 5, 6, 7, 8, 9, 10, 15]
Heap: []

Done

假设您要合并这些列表,列表[1] = [1,10,15],列表[2] = [4,5,6]和列表[3] = [7,8,9]。在第一次迭代中,堆中的值将为1,然后您的算法会将10插入到堆中,但是10是所有列表中的最大值-您将如何避免这种情况?
ramgorur 2013年

@ramgorur堆中没有10没关系。4,5,6,7,8和9都将在此之前得到处理,因为我们总是从堆中获取最小的值,并继续用同一列表中的下一项替换删除的值。编辑示例答案。
Dukeling

好吧,如果是这样,我们不必为下一次元素推送记住相同的列表。我们每次都可以选择一个随机列表,然后将下一个元素推入堆中-据说这也会产生相同的结果,对吗?还是有其他特殊原因要遵循相同的列表参数?
ramgorur

删除时4,如果选择一个随机列表,则可能最终插入8,因此堆将是[7, 8, 10],将从中插入7而不是5插入结果集中,这是错误的。
2013年

O(k)

13

n/k

对于您的问题,以下算法可以解决问题:

  1. HklmO(klgk)
  2. i1n
    • mHresult[i]O(lgk)
    • mlmHO(lgk)

O(klgk+nlgk)=O(nlgk)result

iresultHiresult[1..i]i

在第一次迭代之前是这样:首先,我们表明要插入到中的第一个元素resultHresultr1lr1l[1]r1r1l[1]<r1r1result

iriHHmllHresultrimll

[RËsüŤ[1 ..ñ]


实际上,更严格的时间复杂度将是O(K + 2 * NlogK)= O(NlogK)。制作堆时,O(K)的约束比O(KlogK)的约束更紧密。请参阅为进一步澄清。
Ashwani Gautam

O(k)O(klogk)k
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.