Чому ми не використовуємо швидке сортування у зв’язаному списку?


16

Алгоритм швидкого сортування можна розділити на наступні етапи

  1. Визначте стрижень.

  2. Розділіть зв'язаний список на основі стрижня.

  3. Рекурсивно розділіть пов'язаний список на 2 частини.

Тепер, якщо я завжди вибираю останній елемент як стрижневий, то для виявлення зведеного елемента (1-й крок) потрібно час.O(n)

Після ідентифікації зведеного елемента ми можемо зберігати його дані та порівнювати їх з усіма іншими елементами для виявлення правильної точки розділу (2-й крок). Кожне порівняння займе час коли ми зберігаємо зведені дані, і кожен своп займає час O ( 1 ) . Таким чином , в загальній складності це займає O ( п ) час для п елементів.O(1)O(1)O(n)n

Отже, відношення рецидиву таке:

що є O ( n log n ) , такий самий, як у сортуванні з об'єднаним списком.T(n)=2T(n/2)+нО(нжурналн)

Так чому сортування об’єднання віддається перевазі швидкому сортуванню для пов'язаних списків?


Не потрібно вибирати останній елемент як опорний замість першого
TheCppZoo

Відповіді:


19

Шаблон доступу до пам'яті в Quicksort є випадковим, також нестандартна реалізація не існує, тому він використовує безліч свопів, якщо клітини для досягнення впорядкованого результату.
У той же час сортування злиття є зовнішнім, для повернення впорядкованого результату потрібен додатковий масив. У масиві це означає додатковий простір накладних витрат, у випадку, якщо зв'язаний список, можна витягнути значення і почати об'єднання вузлів. Доступ має більш послідовний характер.

З-за цього, квакісорт не є природним вибором для зв'язаного списку, тоді як сортування злиття має велику перевагу.

Нотація Ландау може (більше чи менше), оскільки Квіксорт все ще є ), але константа набагато вища.О(н2)

У середньому випадку обидва алгоритми знаходяться в тому асимптотичний випадок однаковий, але перевагу суворо обумовлено прихованою постійною, а інколи стабільність є проблемою (quicksort за своєю суттю нестабільний, mergsort стабільний).О(нжурналн)


Але середня складність у часі однакова? Використання швидкого сортування, а також об'єднання сортування для пов'язаного списку.
Зефір

10
@ Zephyr, потрібно пам’ятати, що позначення складності падає постійних факторів. Так, квакісорт у зв’язаному списку та злиття у зв'язаному списку - це той самий клас складності, але ті константи, яких ви не бачите, роблять злиття рівномірно швидшими.
Марк

@Zephyr В основному це різниця теоретичних та емпіричних результатів. Емпірично швидкий швидкий вибір.
ferit

1
О(н2)

3
О(журналн)

5

О(н)О(н2)

О(1)

264О(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;

Це алгоритм, який ядро ​​Linux використовує для сортування пов'язаних списків. Хоча з деякими додатковими оптимізаціями, такими як ігнорування previousвказівника протягом усіх, крім останньої операції злиття.


-2

Ви можете записати сортування об'єднати, сортувати розділ, сортувати дерево та порівняти результати.
Досить легко написати сортування дерева, якщо ви маєте додатковий простір.
Для сортування дерев кожен вузол пов'язаного списку повинен мати два покажчики, навіть якщо ми сортуємо окремо
пов'язаний список. я вважаю за краще вставляти та видаляти замість заміни
розділу Hoare можна лише для подвійно пов'язаного списку

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.

Цей код потребує певних удосконалень.
По-перше, ми повинні обмежити додаткове зберігання на потреби рекурсії,
тоді ми повинні спробувати замінити рекурсію ітерацією.
Якщо ми хочемо додатково вдосконалити алгоритм, ми повинні використовувати дерево самоврівноваження


Дякуємо за ваш детальний внесок, але це не сайт кодування. 200 рядків коду нічого не пояснюють, чому сортування злиття є кращим за швидке сортування для пов'язаних списків.
Девід Річербі

У розділі Сортування вибору півота обмежується першим чи останнім елементом (останній, якщо ми будемо тримати вказівник на хвостовий вузол), інакше вибір півота повільний дерево має таку ж складність, як і кваксорт, якщо ми ігноруємо постійний фактор, але легше уникнути гіршого випадку у сортуванні дерев. Для сортування злиття в коментарі є декілька символів
Маріуш

-2

Quicksort
Можливо, я покажу кроки для quicksort

Якщо список містить більше одного вузла

  1. Вибір стрижня
  2. Список розділів на три підлісти
    Перший підпис складається з вузлів з ключами, меншими, ніж клавіша,
    другий підпис має вузли з ключами, що дорівнюють
    клавіші повороту.
  3. Рекурсивні виклики для списків, які містять вузли, не рівні поворотному вузлу
  4. Об'єднайте відсортовані списки в один відсортований список

Оголошення 1.
Якщо ми хочемо вибрати шарнір швидко, вибір обмежений.
Ми можемо вибрати головний вузол або хвостовий вузол. У
нашому списку повинно бути покажчик на вузол, якщо ми хочемо, щоб наш півот
був доступний швидко, інакше ми повинні шукати вузол

Оголошення 2.
Ми можемо використовувати операції черги для цього кроку
Кулаком ми видаляємо вузол з оригінального пов'язаного списку,
порівнюємо його ключ із клавішею повороту та вузлом
enqueue з правильним підсписком. Підлісти створюються з існуючих вузлів, і не потрібно
виділяти пам'ять для нових вузлів.

Вказівник на хвостовий вузол буде корисний, оскільки операції з черги
та конкатенація працюють швидше за наявності цього вказівника


Боюся, я не міг побачити, як це відповідає на питання.
Джон Л.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.