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


15

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

Дайте алгоритм часу для об'єднання k відсортованих списків у один відсортований список, де n - загальна кількість елементів у всіх вхідних списках. (Підказка: Використовуйте міні-купу для об'єднання k -way.)O(nlgk)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+2lgk))O(nk+nlgk)O(nk)O(k)O(n) щоб знайти наступний мінімальний елемент із списків k. Чи є інший спосіб?алгоритм O ( n lg k ) ?O(nlgk)

Відповіді:


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(klogk+n2logk)=O(nlogk)

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

@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(klgk)
  2. Для від 1 до n робити: i1n
    • Витягніть мінімум з Н і збережіть його в r e s u l t [ i ] ( O ( lg k )mHresult[i]O(lgk) )
    • Вставте прямий наступник в l m (якщо такий є) в H ( O ( lg k ) )mlmHO(lgk)

Час виконання обов язково в і алгоритм правильно сортує r e s u l t .O(klgk+nlgk)=O(nlgk)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(klogk) 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.