Qual é a complexidade de tempo de enfileiramento e desenfileiramento de uma fila implementada com uma lista vinculada individualmente?

7

Estou tentando entender a complexidade de tempo de uma fila implementada com uma estrutura de dados de lista vinculada. Meu livro diz que podemos implementar uma fila no tempo O (1):

  • enfileiramento na parte de trás
  • desenfileiramento na cabeça

e também diz

Observe que, embora adicionar um elemento à cauda seja tempo constante, remover um elemento da cauda é O (n), pois precisamos encontrar a nova cauda

Eu sei que adicionar um novo nó à minha fila na cabeça (enfileirar) leva O (1), pois tenho o ponteiro da cabeça. Da mesma forma, a remoção de um nó na cabeça (desenfileirar) também levará O (1). Mas não entendo por que adicionar um elemento na parte de trás (enfileirar) leva O (1), enquanto removê-lo (desenfileirar) leva O (n). Faria mais sentido para mim se enfileirar na parte de trás pegasse O (n) em vez de O (1), pois em ambos os casos (adicionar / remover na parte de trás) precisaria encontrar o ponteiro da cauda e, portanto, atravessaria a lista inteira.

Appe
fonte
7
Não remova informações significativas da sua pergunta depois de receber uma resposta. Queremos que o par de perguntas e respostas seja útil para outras pessoas no futuro, portanto, tente manter as coisas completas.
Lagarto discreto

Respostas:

14

Enfileiramento

Você não precisa percorrer a lista inteira para encontrar a nova cauda, ​​basta adicionar um novo nó para onde a cauda atual aponta e definir esse nó como a nova cauda.

Pseudocódigo (assumindo que head != nulle tail != null):

function enqueue(value) {
    node = new Node(value)     // O(1)
    tail.next = node           // O(1)
    tail = node                // O(1)
    size++                     // O(1)
}

A partir do qual podemos concluir que a complexidade do tempo é .O(1 1)


Retirar da fila

Para desenfileirar, precisamos apenas definir o próximo nó da cabeça atual como a nova cabeça e retornar o valor da cabeça antiga.

Nota : Não esqueça que se o novo cabeçote estiver definido como null, a cauda também deverá estar definida null:

Pseudocódigo (assumindo que head != nulle tail != null):

function dequeue() {
    value = head.value         // O(1)
    head = head.next           // O(1)
    size--                     // O(1)

    if (head == null) {        // O(1)
        tail = null            // O(1)
    }

    return value               // O(1)
}

Todas essas operações possuem complexidade de tempo , o que torna a complexidade de tempo da função de desenfileiramento também.O(1 1)O(1 1)


Procurando

A busca de um valor é feita percorrendo todos os itens, começando pela cabeça. No pior cenário, você precisaria atravessar a fila inteira, o que torna a complexidade do pior caso .O(n)

Por exemplo, se você deseja remover a cauda, ​​a complexidade do tempo seria . Isso ocorre porque você precisaria encontrar a nova cauda para a fila e, como a cauda não tem acesso ao elemento anterior em uma lista vinculada individualmente, seria necessário procurar a nova cauda na fila inteira.O(n)

Pseudocódigo (assumindo que head != nulle tail != null):

function removeLast() {

    // Edge case when there is only 1 element in the queue.
    if (head == tail) {                   // O(1)
        value = head.value                // O(1)
        head = null                       // O(1)
        tail = null                       // O(1)

        return value                      // O(1)
    }

    // Searching for the new tail.
    newTail = head                        // O(1)
    while (newTail.next != tail) {        // O(n)
        newTail = newTail.next            // O(1)
    }

    value = tail.value                    // O(1)
    newTail.next = null                   // O(1)
    tail = newTail                        // O(1)

    return tail                           // O(1)    
}

A partir do qual se pode observar que a complexidade do tempo é de fato .O(n)

unoriginallycreative
fonte
O enfileiramento não lida com uma lista vazia. Enquanto desenfileirar manipula quando a lista fica vazia.
Ratchet freak
1

O comentário no livro aparentemente pressupõe que a implementação da lista vinculada mantém dois ponteiros, headque apontam para o primeiro nó da lista e lastque apontam para o último nó.

Com esse design, anexar à lista é simplesmente:

list.last.next = newNode;
list.last = newNode;

Mas remover o último nó requer encontrar o segundo ao último nó, para que você possa limpar seu nextlink e alterar o lastponteiro para apontar para ele. Isso requer a varredura da lista para encontrar o nó em que node.next == list.last.

É possível que você também tenha um list.secondToLastponteiro, mas isso não resolve o problema, porque quando você remove o último nó, é necessário alterá-lo para apontar para o terceiro para o último nó anterior, e esse problema se repete na lista inteira.

Barmar
fonte