Heap - Assegna un algoritmo temporale


15

Molto probabilmente, questa domanda viene posta prima. Viene dal problema CLRS (2a edizione) 6.5-8 -

Dare un algoritmo per unire k elenchi ordinati in un elenco ordinato, dove n è il numero totale di elementi in tutti gli elenchi di input. (Suggerimento: usa un heap min per kO(nlgk)knk unione di -way.)

Poiché ci sono elenchi ordinati e un totale di n valori, supponiamo che ogni elenco contenga nknnk numeri, inoltre ciascuna delle liste è ordinata in ordine strettamente crescente, e anche i risultati saranno memorizzati in ordine crescente.

Il mio pseudo-codice è simile al seguente:

    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

La mia complessità complessiva diventa . Non sono riuscito a trovare alcun modo per evitare ilciclo O ( k ) all'interno di O ( n )O(k)+O(k)+O(n(k+2lgk))O(nk+nlgk)O(nk)O(k)O(n)ciclo per trovare l'elemento minimo successivo dalle liste k. C'è un altro modo per aggirare? Come ottenere un algoritmo ?O(nlgk)

Risposte:


13

Lo scopo dell'heap è di darti il ​​minimo, quindi non sono sicuro di quale sia lo scopo di questo for-loop: for j := 2 to k .

La mia opinione sullo pseudo-codice:

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)

La complessità temporale totale è quindi O(klogk+n2logk)=O(nlogk)

Puoi anche, invece di deleteMine insert, avere un getMin( ) e un ( O ( log k ) ), che ridurrà il fattore costante, ma non la complessità.O(1)incrementIndexO(logk)

Esempio:
(utilizzando il valore anziché l'indice e l'elenco indice e heap rappresentati come una matrice ordinata per chiarezza)

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

supponiamo che tu abbia questi elenchi da unire, list [1] = [1, 10, 15], list [2] = [4, 5, 6] e list [3] = [7, 8, 9]. Alla prima iterazione, il valore dall'heap sarà 1 e successivamente l'algoritmo inserirà 10 nell'heap, ma 10 è il valore più grande di tutti gli elenchi: come lo eviterai?
Ramgorur,

@ramgorur Non importa che 10 sia nell'heap. 4,5,6,7,8 e 9 verranno tutti elaborati prima di esso poiché otteniamo sempre il valore più piccolo dall'heap e continuiamo a sostituire i valori eliminati con l'elemento successivo dallo stesso elenco. Risposta modificata con esempio.
Dukeling,

bene, se questo è il caso, non dobbiamo effettivamente ricordare lo stesso elenco per il prossimo push dell'elemento. Possiamo scegliere ogni volta un elenco casuale e inserire l'elemento successivo nell'heap - che presumibilmente darà lo stesso risultato, vero? O c'è qualche altro motivo speciale per seguire lo stesso argomento dell'elenco ?
Ramgorur,

Quando si elimina 4, se si sceglie un elenco casuale, si potrebbe finire per inserire 8, quindi sarà l'heap [7, 8, 10], da cui si inserirà 7anziché 5nel set di risultati, che sarà errato.
Dukeling,

O(k)

13

n/k

Per quanto riguarda il tuo problema, il seguente algoritmo dovrebbe fare il trucco:

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

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

iresultHiresult[1..i]i

resultHresultr1lr1l[1]r1r1 è nonl[1]<r1r1result

iriHHmllHresultrimll

result[1..n]


In realtà la complessità temporale più stretta sarebbe O (K + 2 * NlogK) = O (NlogK) . O (K) è più stretto di O (KlogK), quando si fa un Heap. Fare riferimento a questo per ulteriori chiarimenti.
Ashwani Gautam,

O(k)O(klogk)k
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.