Mengapa kita tidak menggunakan pengurutan cepat pada daftar tertaut?

16

Algoritma pengurutan cepat dapat dibagi menjadi langkah-langkah berikut

  1. Identifikasi inden.

  2. Partisi daftar tertaut berdasarkan pivot.

  3. Bagi daftar yang ditautkan secara rekursif menjadi 2 bagian.

Sekarang, jika saya selalu memilih elemen terakhir sebagai pivot, maka mengidentifikasi elemen pivot (langkah 1) membutuhkan waktu .HAI(n)

Setelah mengidentifikasi elemen pivot, kita dapat menyimpan datanya dan membandingkannya dengan semua elemen lain untuk mengidentifikasi titik partisi yang benar (langkah ke-2). Setiap perbandingan akan memakan waktu saat kami menyimpan data pivot dan setiap swap membutuhkan waktu . Jadi secara total dibutuhkan waktu untuk elemen.HAI(1)HAI(1)HAI(n)n

Jadi relasi perulangan adalah:

T(n)=2T(n/2)+n yang merupakan yang sama seperti dalam gabungan gabungan dengan daftar yang ditautkan.HAI(ncatatann)

Jadi mengapa penggabungan sortir lebih disukai daripada sortir cepat untuk daftar tertaut?

Angin barat
sumber
Tidak perlu memilih elemen terakhir sebagai poros bukan yang pertama
TheCppZoo

Jawaban:

19

Pola akses memori di Quicksort adalah acak, juga implementasi out-of-the-box di tempat, sehingga menggunakan banyak swap jika sel untuk mencapai hasil yang dipesan.
Pada saat yang sama jenis gabungan adalah eksternal, itu memerlukan array tambahan untuk mengembalikan hasil yang dipesan. Dalam array, ini berarti overhead ruang tambahan, dalam kasus jika daftar tertaut, dimungkinkan untuk menarik nilai dan mulai menggabungkan node. Aksesnya lebih berurutan.

Karena itu, quicksort bukanlah pilihan alami untuk daftar tertaut, sementara penggabungan mengambil keuntungan besar.

Notasi Landau mungkin (kurang lebih, karena Quicksort masih ) setuju, tetapi konstanta jauh lebih tinggi.HAI(n2)

Dalam kasus rata-rata kedua algoritma berada di sehingga kasus asimptotiknya sama, tetapi preferensi secara ketat disebabkan oleh konstanta tersembunyi dan terkadang kestabilannya adalah masalah (quicksort secara inheren tidak stabil, mergsort stabil) .HAI(ncatatann)

Jahat
sumber
Tetapi kompleksitas waktu rata-rata sama, kan? Menggunakan pengurutan cepat dan juga menggabungkan pengurutan untuk daftar tertaut.
Zephyr
10
@ Zephyr, Anda harus ingat bahwa notasi kompleksitas menjatuhkan faktor konstan. Ya, quicksort pada daftar tertaut dan mergesort pada daftar tertaut adalah kelas kompleksitas yang sama, tetapi konstanta yang tidak Anda lihat membuat mergesort lebih cepat secara seragam.
Tandai
@ Zephyr Pada dasarnya ini adalah perbedaan hasil teoritis dan empiris. Secara empiris, quicksort lebih cepat.
ferit
1
Juga, memilih poros yang baik sulit untuk daftar yang ditautkan. Jika Anda mengambil elemen terakhir, seperti yang disarankan OP, itu berarti bahwa kasus terburuk ( ) adalah daftar atau sublist yang sudah diurutkan. Dan kasus terburuk itu sangat mungkin muncul dalam praktik. HAI(n2)
Stig Hemmer
3
Quicksort tidak pernah ada, itu kesalahpahaman umum. Ini membutuhkan ruang tambahan. Selain itu, pola akses memori "acak" juga tidak cukup akurat: sangat tergantung pada pilihan poros, seperti yang dijelaskan dalam jawaban lain. HAI(catatann)
Konrad Rudolph
5

Anda dapat dengan cepat menyortir daftar yang ditautkan namun Anda akan sangat terbatas dalam hal pemilihan pivot, membatasi Anda untuk pivot di dekat bagian depan daftar yang buruk untuk input yang hampir diurutkan, kecuali jika Anda ingin mengulang setiap segmen dua kali (satu kali untuk pivot dan satu kali untuk partisi). Dan Anda harus menyimpan setumpuk batas partisi untuk daftar yang masih harus Anda sortir. Tumpukan itu dapat tumbuh ke ketika pemilihan pivot buruk bersama dengan kompleksitas waktu yang berkembang menjadi O ( n 2 ) .HAI(n)HAI(n2)

Penggabungan sortir pada daftar tertaut dapat dieksekusi hanya menggunakan ruang tambahan jika Anda mengambil pendekatan bottom-up dengan menghitung di mana batas-batas partisi dan penggabungan yang sesuai.HAI(1)

Namun menambahkan 64 elemen array pointer Anda dapat menghindari iterasi ekstra dan mengurutkan daftar hingga elemen di O ( 1 ) ruang tambahan tambahan.264HAI(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;

Ini adalah algoritma yang digunakan kernel linux untuk menyortir daftar tertautnya. Meskipun dengan beberapa optimasi tambahan seperti mengabaikan previouspointer selama semua tapi operasi gabungan terakhir.

ratchet freak
sumber
-2

Anda dapat menulis semacam penggabungan, partisi semacam, semacam pohon dan membandingkan hasil
Hal ini sangat mudah untuk menulis pohon semacam jika Anda mengizinkan beberapa ruang tambahan
Untuk pohon semacam setiap node dari linked list harus memiliki dua pointer bahkan jika kita semacam tunggal linked list
Dalam daftar link saya lebih suka memasukkan dan menghapus daripada menukar
partisi Hoare hanya dapat dilakukan untuk daftar tertaut ganda

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.

Kode ini memerlukan beberapa perbaikan.
Pertama kita harus membatasi penyimpanan ekstra untuk kebutuhan rekursi,
kemudian kita harus mencoba mengganti rekursi dengan iterasi.
Jika kita ingin meningkatkan algoritma lebih lanjut, kita harus menggunakan pohon self balancing.

Mariusz
sumber
Terima kasih atas kontribusi terperinci Anda, tetapi ini bukan situs pengkodean. 200 baris kode tidak melakukan apa pun untuk menjelaskan mengapa pengurutan gabungan lebih disukai daripada pengurutan cepat untuk daftar tertaut.
David Richerby
Dalam partisi Sortir memilih pivot terbatas pada elemen pertama atau terakhir (terakhir jika kita tetap mengarahkan pointer ke node ekor) jika tidak memilih pivot lambat Partisi hoare hanya mungkin untuk daftar yang ditautkan dua kali. Swapping harus diganti dengan menyisipkan dan menghapus Urutan pohon dengan tidak seimbang tree memiliki compexity yang sama dengan quicksort jika kita mengabaikan faktor konstan tetapi lebih mudah untuk menghindari kasus terburuk dalam sortir pohon. Untuk penggabungan sort ada beberapa karakter dalam komentar
Mariusz
-2

Quicksort
Mungkin saya akan menunjukkan langkah-langkah untuk quicksort

Jika daftar berisi lebih dari satu simpul

  1. Pilihan poros
  2. Daftar partisi menjadi tiga
    sublist pertama sublist berisi node dengan kunci kurang dari kunci pivot
    sublist kedua berisi node dengan kunci sama dengan kunci pivot
    sublist ketiga berisi node dengan kunci lebih besar dari kunci pivot
  3. Panggilan rekursif untuk sublists yang berisi node tidak sama dengan pivot node
  4. Gabungan daftar yang diurutkan menjadi satu daftar yang diurutkan

Iklan 1.
Jika kita ingin memilih pivot cepat, pilihannya terbatas.
Kita dapat memilih head node atau tail node
. Daftar kita harus memiliki poiner ke node jika kita ingin pivot kita
dapat diakses dengan cepat jika tidak kita harus mencari node

Iklan 2.
Kita dapat menggunakan operasi antrian untuk langkah ini.
Fist we dequeue node dari daftar tertaut asli
membandingkan kuncinya dengan kunci pivot dan enqueue ke sublist yang benar.
Sublist dibuat dari node yang ada dan tidak perlu
mengalokasikan memori untuk node baru

Pointer ke tail tail akan bermanfaat karena operasi antrian
dan penggabungan berjalan lebih cepat dengan kehadiran pointer ini

Mariusz
sumber
Saya khawatir saya tidak bisa melihat bagaimana ini menjawab pertanyaan.
John L.