Perché non utilizziamo l'ordinamento rapido in un elenco collegato?


16

L'algoritmo di ordinamento rapido può essere suddiviso nei seguenti passaggi

  1. Identificare pivot.

  2. Partiziona l'elenco collegato in base al pivot.

  3. Dividi ricorsivamente l'elenco collegato in 2 parti.

Ora, se scelgo sempre l'ultimo elemento come pivot, l'identificazione dell'elemento pivot (primo passaggio) richiede tempo.O(n)

Dopo aver identificato l'elemento pivot, possiamo memorizzare i suoi dati e confrontarli con tutti gli altri elementi per identificare il punto di partizione corretto (2 ° passaggio). Ogni confronto impiegherà il tempo mentre memorizziamo i dati pivot e ogni scambio impiega tempo. Quindi in totale ci vuole tempo per elementi.O(1)O(1)O(n)n

Quindi la relazione di ricorrenza è:

T(n)=2T(n/2)+n che è che è lo stesso dell'ordinamento unito con un elenco collegato.O(nlogn)

Quindi perché è preferibile unire l'ordinamento rispetto all'ordinamento rapido per gli elenchi collegati?


Non è necessario selezionare l'ultimo elemento come perno anziché il primo
TheCppZoo

Risposte:


19

Il modello di accesso alla memoria in Quicksort è casuale, anche l'implementazione immediata è a posto, quindi utilizza molti swap se le celle per ottenere il risultato ordinato.
Allo stesso tempo, l'ordinamento di tipo merge è esterno, richiede un array aggiuntivo per restituire il risultato ordinato. In array significa sovraccarico di spazio aggiuntivo, nel caso in cui l'elenco collegato, è possibile estrarre valore e iniziare a unire i nodi. L'accesso è di natura più sequenziale.

Per questo motivo, quicksort non è una scelta naturale per l'elenco collegato, mentre l'ordinamento di tipo merita un grande vantaggio.

La notazione di Landau potrebbe (più o meno, perché Quicksort è ancora ) concordare, ma la costante è molto più alta.O(n2)

Nel caso medio entrambi gli algoritmi sono in quindi il caso asintotico è lo stesso, ma la preferenza è strettamente dovuta alla costante nascosta e talvolta la stabilità è il problema (quicksort è intrinsecamente instabile, mergsort è stabile).O(nlogn)


Ma la complessità del tempo medio è la stessa, vero? Utilizzando l'ordinamento rapido e unisci ordinamento per l'elenco collegato.
Zefiro,

10
@Zephyr, è necessario ricordare che la notazione di complessità lascia cadere fattori costanti. Sì, quicksort in un elenco collegato e mergesort in un elenco collegato sono la stessa classe di complessità, ma quelle costanti che non vedi rendono l'unione più rapida uniformemente.
Segna il

@Zephyr Fondamentalmente è la differenza di risultati teorici ed empirici. Empiricamente, quicksort è più veloce.
ferit

1
O(n2)

3
O(logn)

5

O(n)O(n2)

O(1)

264O(1)

head = list.head;
head_array = array of 64 nulls

while head is not null
    current = head;
    head = head.next;
    current.next = null;
    for(i from 0 to 64)
        if head_array[i] is null
            head_array[i] = current;
            break from for loop;
        end if
        current = merge_lists(current, array[i]);
        head_array[i] = null;
     end for
end while

current = null;
for(i from 0 to 64)
    if head_array[i] is not null
        if current is not null
            current = merge_lists(current, head_array[i]);
        else
            current = head_array[i];
        end if
     end if
 end for

 list.head = current;

Questo è l'algoritmo che il kernel Linux usa per ordinare i suoi elenchi collegati. Sebbene con alcune ottimizzazioni extra come ignorare il previouspuntatore durante tutte le operazioni tranne l'ultima.


-2

È possibile scrivere unisci ordinamento, ordinamento partizione, ordinamento albero e confrontare i risultati
È abbastanza semplice scrivere ordinamento albero se si concede spazio aggiuntivo
Per l'ordinamento albero ogni nodo dell'elenco collegato deve avere due puntatori anche se ordiniamo l'elenco collegato singolarmente Nell'elenco
collegato preferisco inserire ed eliminare invece di scambiare la
partizione Hoare può essere fatta solo per l'elenco doppiamente collegato

program untitled;


type TData = longint;
     PNode = ^TNode;
     TNode = record
                data:TData;
                prev:PNode;
                next:PNode;
             end;

procedure ListInit(var head:PNode);
begin
  head := NIL;
end;

function ListIsEmpty(head:PNode):boolean;
begin
  ListIsEmpty := head = NIL;
end;

function ListSearch(var head:PNode;k:TData):PNode;
var x:PNode;
begin
  x := head;
  while (x <> NIL)and(x^.data <> k)do
     x := x^.next;
  ListSearch := x;
end;

procedure ListInsert(var head:PNode;k:TData);
var x:PNode;
begin
  new(x);
  x^.data := k;
  x^.next := head;
  if head <> NIL then
     head^.prev := x;
   head := x;
   x^.prev := NIL;
end;

procedure ListDelete(var head:PNode;k:TData);
var x:PNode;
begin
   x := ListSearch(head,k);
   if x <> NIL then
   begin
     if x^.prev <> NIL then
        x^.prev^.next := x^.next
      else 
        head := x^.next;
     if x^.next <> NIL then
        x^.next^.prev := x^.prev;
     dispose(x);
   end;
end;

procedure ListPrint(head:PNode);
var x:PNode;
    counter:longint;
begin
  x := head;
  counter := 0;
  while x <> NIL do
  begin
    write(x^.data,' -> ');
    x := x^.next;
    counter := counter + 1;
  end;
  writeln('NIL');
  writeln('Liczba elementow listy : ',counter);
end;

procedure BSTinsert(x:PNode;var t:PNode);
begin
  if t = NIL then
    t := x
  else
    if t^.data = x^.data then
            BSTinsert(x,t^.prev)
        else if t^.data < x^.data then
            BSTinsert(x,t^.next)
        else
            BSTinsert(x,t^.prev);
end;

procedure BSTtoDLL(t:PNode;var L:PNode);
begin
   if t <> NIL then
   begin
     BSTtoDLL(t^.next,L);
     ListInsert(L,t^.data);
     BSTtoDLL(t^.prev,L);
   end;
end;

procedure BSTdispose(t:PNode);
begin
   if t <> NIL then
   begin
    BSTdispose(t^.prev);
    BSTdispose(t^.next);
    dispose(t);
   end; 
end;

procedure BSTsort(var L:PNode);
var T,S:PNode;
    x,xs:PNode;
begin
  T := NIL;
  S := NIL;
  x := L;
  while x <> NIL do
  begin
    xs := x^.next;
    x^.prev := NIL;
    x^.next := NIL;
    BSTinsert(x,t);
    x := xs;
  end;
  BSTtoDLL(T,S);
  BSTdispose(T);
  L := S;
end;

var i : byte;
    head:PNode;
    k:TData;
BEGIN
  ListInit(head);
  repeat
     writeln('0. Wyjscie');
     writeln('1. Wstaw element na poczatek listy');
     writeln('2. Usun element listy');
     writeln('3. Posortuj elementy drzewem binarnym');
     writeln('4. Wypisz elementy  listy');
     readln(i);
     case i of
     0:
     begin
       while not ListIsEmpty(head) do
            ListDelete(head,head^.data);
     end;
     1:
     begin
       writeln('Podaj element jaki chcesz wstawic');
       readln(k);
       ListInsert(head,k);
     end;
     2:
     begin
       writeln('Podaj element jaki chcesz usunac');
       readln(k);
       ListDelete(head,k);
     end;
     3:
     begin
       BSTsort(head);
     end;
     4:
     begin
        ListPrint(head);    
     end
     else
        writeln('Brak operacji podaj inny numer');
     end;
  until i = 0;  
END.

Questo codice necessita di alcuni miglioramenti
In primo luogo dovremmo limitare l'archiviazione aggiuntiva alle esigenze di ricorsione
quindi dovremmo provare a sostituire la ricorsione con iterazione
Se vogliamo migliorare ulteriormente l'algoritmo dovremmo usare l'albero di auto-bilanciamento


Grazie per il tuo contributo dettagliato ma questo non è un sito di codifica. 200 righe di codice non fanno nulla per spiegare perché si preferisce unire l'ordinamento rispetto all'ordinamento rapido per gli elenchi collegati.
David Richerby,

Nell'ordinamento delle partizioni la scelta del pivot è limitata al primo o all'ultimo elemento (ultimo se manteniamo il puntatore al nodo di coda) altrimenti la scelta del pivot è lenta La partizione Hoare è possibile solo per elenchi doppiamente collegati Lo scambio deve essere sostituito con l'inserimento e l'eliminazione dell'ordinamento degli alberi con squilibrato l'albero ha la stessa complicità di quicksort se ignoriamo il fattore costante ma è più facile evitare il caso peggiore nell'ordinamento degli alberi Per unire l'ordinamento ci sono pochi caratteri nel commento
Mariusz,

-2

Quicksort
Forse mostrerò i passaggi per quicksort

Se l'elenco contiene più di un nodo

  1. Selezione pivot
  2. Elenco di partizioni in tre elenchi La
    prima lista secondaria contiene nodi con chiavi inferiori alla chiave pivot La
    seconda lista secondaria contiene nodi con chiavi uguali alla chiave pivot La
    terza lista secondaria contiene nodi con chiavi maggiori della chiave pivot
  3. Chiamate ricorsive per elenchi secondari che contengono nodi non uguali al nodo pivot
  4. Concatena gli elenchi ordinati in un elenco ordinato

Annuncio 1.
Se vogliamo scegliere il pivot velocemente la scelta è limitata
Possiamo scegliere il nodo head o il nodo tail Il
nostro elenco deve avere poiner al nodo se vogliamo che il nostro pivot
sia accessibile velocemente altrimenti dobbiamo cercare il nodo

Annuncio 2.
Possiamo usare le operazioni in coda per questo passaggio.
Pugno, estromettiamo il nodo dall'elenco collegato originale
confrontando la sua chiave con la chiave pivot e accodando il nodo con l'elenco
secondario corretto. Le liste secondarie vengono create da nodi esistenti e non è necessario
allocare memoria per nuovi nodi

Il puntatore al nodo di coda sarà utile perché le operazioni in coda
e la concatenazione vengono eseguite più rapidamente con la presenza di questo puntatore


Temo di non poter vedere come questo risponda alla domanda.
John L.,
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.