Por que não usamos a classificação rápida em uma lista vinculada?

16

O algoritmo de classificação rápida pode ser dividido nas seguintes etapas

  1. Identifique o pivô.

  2. Particione a lista vinculada com base no pivô.

  3. Divida a lista vinculada recursivamente em 2 partes.

Agora, se eu sempre escolher o último elemento como dinâmico, a identificação do elemento dinâmico (1º passo) leva tempo .O(n)

Após identificar o elemento pivô, podemos armazenar seus dados e compará-los com todos os outros elementos para identificar o ponto de partição correto (2ª etapa). Cada comparação levará tempo medida que armazenamos os dados do pivô e cada troca leva tempo . Portanto, no total, leva tempo para elementos.O(1)O ( n ) nO(1)O(n)n

Portanto, a relação de recorrência é:

que é O ( n log n ), que é o mesmo que na classificação de mesclagem com uma lista vinculada.T(n)=2T(n/2)+nO(nregistron)

Então, por que a classificação de mesclagem é preferida à classificação rápida para listas vinculadas?

Zephyr
fonte
Não há necessidade de pegar o último elemento como pivô ao invés do primeiro
TheCppZoo

Respostas:

19

O padrão de acesso à memória no Quicksort é aleatório, e também a implementação pronta para uso, por isso usa muitos swaps se as células atingirem o resultado ordenado.
Ao mesmo tempo, a classificação de mesclagem é externa, requer uma matriz adicional para retornar o resultado ordenado. Na matriz, isso significa sobrecarga de espaço adicional; no caso, se estiver vinculada à lista, é possível extrair valor e começar a mesclar nós. O acesso é mais seqüencial por natureza.

Por esse motivo, o quicksort não é a opção natural para lista vinculada, enquanto a classificação por mesclagem tira grande vantagem.

A notação Landau pode (mais ou menos, porque o Quicksort ainda é ) concordar, mas a constante é muito maior.O(n2)

No caso médio, ambos os algoritmos estão em portanto o caso assintótico é o mesmo, mas a preferência é estritamente devido à constante oculta e, às vezes, a estabilidade é o problema (quicksort é inerentemente instável, mergsort é estável).O(nregistron)

Mal
fonte
Mas a complexidade média do tempo é a mesma, certo? Usando a classificação rápida e a mesclagem para lista vinculada.
Zephyr.
10
@ Zephyr, é preciso lembrar que a notação de complexidade diminui fatores constantes. Sim, o quicksort em uma lista vinculada e o mergesort em uma lista vinculada são da mesma classe de complexidade, mas essas constantes que você não está vendo tornam o mergesort uniformemente mais rápido.
Mark
@ Zephyr Basicamente, é a diferença de resultados teóricos e empíricos. Empiricamente, o quicksort é mais rápido.
ferit 01/12/19
1
O(n2)
3
Quicksort é O(registron)
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;

Este é o algoritmo que o kernel do linux usa para classificar suas listas vinculadas. Embora com algumas otimizações extras, como ignorar o previousponteiro durante toda, exceto a última operação de mesclagem.

catraca arrepiante
fonte
-2

Você pode escrever classificação de mesclagem, classificação de partição, classificação em árvore e comparar resultados
É muito fácil escrever classificação em árvore, se você permitir algum espaço extra.
Para classificação em árvore, cada nó da lista vinculada deve ter dois ponteiros, mesmo se classificarmos lista vinculada individualmente
Na lista vinculada eu prefiro inserir e excluir em vez de trocar a
partição Hoare pode ser feita apenas para lista duplamente vinculada

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.

Esse código precisa de algumas melhorias.
Primeiramente, devemos limitar o armazenamento extra às necessidades de recursão,
e tentar substituir a recursão pela iteração.
Se quisermos melhorar ainda mais o algoritmo, devemos usar a árvore de auto balanceamento.

Mariusz
fonte
Obrigado pela sua contribuição detalhada, mas este não é um site de codificação. 200 linhas de código não fazem nada para explicar por que a classificação por mesclagem é preferível à classificação rápida para listas vinculadas.
David Richerby
Na partição, a escolha do pivô da classificação dinâmica é limitada ao primeiro ou ao último elemento (por último, se mantivermos o ponteiro para o nó da cauda). Caso contrário, a escolha do pivô é lenta. A partição Hoare é possível apenas para listas duplamente vinculadas. árvore tem a mesma compexity como quicksort se ignorarmos fator constante, mas é mais fácil evitar o pior caso na árvore de classificação para merge sort há para alguns caracteres no comentário
Mariusz
-2

Quicksort
Talvez eu mostre as etapas para o quicksort

Se a lista contiver mais de um nó

  1. Seleção de pivô
  2. Lista de partições em três sublistas A
    primeira sub- lista contém nós com chaves menores que a chave dinâmica A
    segunda sub-lista contém nós com chaves iguais à chave dinâmica A
    terceira sub-lista contém nós com chaves maiores que a chave dinâmica
  3. Chamadas recursivas para sublistas que contêm nós diferentes de nó dinâmico
  4. Concatenar sublistas classificadas em uma lista classificada

Anúncio 1.
Se quisermos escolher o pivô rapidamente, a escolha é limitada.
Podemos escolher o nó principal ou o nó final
final. Nossa lista precisa ter um direcionador para o nó, se quisermos que nosso pivô
seja acessível rapidamente, caso contrário, precisamos procurar o nó.

Anúncio 2.
Podemos usar operações de fila para esta etapa.
Primeiro, desenfileiramos o nó da lista vinculada original,
comparamos sua chave com a chave dinâmica e o enfileiramos com o sublist correto. Os sublistas
são criados a partir de nós existentes e não há necessidade de
alocar memória para novos nós

O ponteiro para o nó da cauda será útil porque as operações da fila
e a concatenação são executadas mais rapidamente com a presença desse ponteiro

Mariusz
fonte
Receio não poder ver como isso responde à pergunta.
John L.