Heap - Дайте алгоритм часу


15

Швидше за все, це питання задають і раніше. Це з проблеми 6.5-8 CLRS (2nd Ed) -

Дайте алгоритм часу для об'єднання k відсортованих списків у один відсортований список, де n - загальна кількість елементів у всіх вхідних списках. (Підказка: Використовуйте міні-купу для об'єднання k -way.)O(nlg⁡k)knk

Оскільки існує відсортованих списків та загальна кількість n значень, припустимо, що кожен список містить nkn числа, причому кожен із списків сортується у строго висхідному порядку, а результати також зберігатимуться у порядку зростання.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 ( n )O(k)+O(k)+O(n(k+2lg⁡k))≈O(nk+nlg⁡k)≈O(nk)O(k)O(n) щоб знайти наступний мінімальний елемент із списків k. Чи є інший спосіб?алгоритм O ( n lg k ) ?O(nlg⁡k)

Відповіді:


13

Мета купи - мінімум, тому я не впевнений, яка мета цього циклу - 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(k∗log⁡k+n∗2log⁡k)=O(nlog⁡k)

Ви також можете замість deleteMinі insertмати getMin( ) та an ( O ( log k ) ), що зменшить постійний коефіцієнт, але не складність.O(1)incrementIndexO(log⁡k)

Приклад:
(використовуючи значення, а не індекс та список індексу та купу, представлену як відсортований масив для ясності)

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

@ramgorur Не має значення, що 10 знаходиться в купі. 4,5,6,7,8 і 9 буде оброблено до цього, оскільки ми завжди отримуємо найменше значення з купи та продовжуємо замінювати видалені значення наступним пунктом із того самого списку. Відредаговано відповідь на прикладі.
— Герцогство

добре, якщо це так, нам не потрібно насправді запам'ятовувати той самий список для наступного натискання елемента. Ми можемо обирати випадковий список кожного разу і штовхати наступний елемент у купу - що також нібито дасть той самий результат, я прав? Або є якісь інші особливі причини слідувати тому ж аргументу списку ?
— ramgorur

При видаленні 4, якщо ви вибираєте випадковий список, ви можете закінчити вставляти 8, таким чином, буде купка [7, 8, 10], з якої ви будете вставляти 7замість 5у набір результатів, що буде неправильним.
— Герцогство

@ Коментар AshwaniGautam щодо іншої відповіді влучний: створення купі спочатку можна зробити вчасно . O(k)
— Рафаель

13

Перш за все, я думаю, що ваше припущення щодо всіх списків, що мають записів, не є дійсним, якщо час виконання алгоритму залежить від довжини найдовшого списку.n/k

Що стосується вашої проблеми, наступний алгоритм повинен зробити фокус:

  1. Покладіть перші елементи списків у хвіст розміром k . Пам'ятайте , для кожного елемента списку л м він належить. ( O ( k lg k ) )HklmO(klg⁡k)
  2. Для від 1 до n робити: i1n
    • Витягніть мінімум з Н і збережіть його в r e s u l t [ i ] ( O ( lg k )mHresult[i]O(lg⁡k) )
    • Вставте прямий наступник в l m (якщо такий є) в H ( O ( lg k ) )mlmHO(lg⁡k)

Час виконання обов язково в і алгоритм правильно сортує r e s u l t .O(klg⁡k+nlg⁡k)=O(nlg⁡k)result

Доказ (або, принаймні, ідея для доказу). Розглянемо наступний цикл інваріант: -й елемент, який потрібно вставити в r e s u l t , завжди є мінімумом min-купи H на кроці i, отже, r e s u l t [ 1 .. i ] правильно відсортовано після i- ї ітерації.iresultHiresult[1..i]i

Це справедливо до першої ітерації: По-перше, ми покажемо, що перший елемент, який потрібно вставити в є в H : Припустимо, що суперечить, що перший елемент, який слід вставити в r e s u l t (тобто загальний найменший елемент, назвемо його r 1 ), не був першим елементом. Тоді у списку l, який містить r 1 , перший елемент l [ 1 ] повинен відрізнятися від r 1 (як припускають, r 1 не єresultHresultr1lr1l[1]r1r1перший елемент). Оскільки наші списки сортовані, у нас навіть , але це суперечність, оскільки ми вибрали r 1 як загальний найменший елемент. Очевидно, що мінімум усіх перших елементів - це той, який потрібно вставити в r e s u l tl[1]<r1r1result .

iriHHmllHresultrimll сортується, а отже, інваріант дотримується.

result[1..n] correctly sorted.


Actually the tighter time complexity would be O(K+2*NlogK)=O(NlogK). O(K) is tighter bound than O(KlogK), when making a Heap. Refer this for further clarifications.
— Ashwani Gautam

@AshwaniGautam That's not tighter at all, since both O(k) and O(klog⁡k) are dominated. That said, your point is correct; the first initialization of the heap can indeed be done in linear time (in k). I guess (hope!) both answerers know that, but it's not crucial here.
— Raphael
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.