web-dev-qa-db-fra.com

Pourquoi les opérations de suppression et d'insertion de liste liée ont-elles une complexité de O(1) ? ça ne devrait pas être de O(n)

On dit que la complexité de la suppression de LinkedList et que l'opération add est de O(1). et dans le cas de ArrayList il est de O(n)

Calcul pour ArrayList de taille "M": si je veux enlever l'élément en Nième position, alors je peux aller directement à la Nième position en utilisant l'index en une fois (je n'ai pas à traverser jusqu'au Nième index), puis je peux l'enlever L'élément, jusqu'à ce point la complexité est O(1) alors je devrai déplacer le reste des éléments (MN se déplace) ainsi ma complexité sera linéaire ie O (M-N + 1). et par conséquent la suppression ou l’insertion à la dernière position me donnera la meilleure performance (en tant que N ~ M) et la suppression ou l’insertion au début sera pire (en tant que N ~ 1).

Maintenant, LisnkedList de taille "M": comme nous ne pouvons pas atteindre directement le Nième élément de la LinkedList, pour accéder au Nième élément, nous devons traverser N éléments. La recherche dans la LinkedList coûte donc plus cher que la ArrayList ... mais Supprimer et les opérations add sont dites de O(1) dans le cas de LinkedList car, dans LinkedList, le décalage n'est pas impliqué, mais il y a une opération de traverse impliquée? la complexité doit donc être de l'ordre O(n), où la pire performance se situe au nœud de queue et la meilleure des performances au nœud de tête. 

Quelqu'un pourrait-il m'expliquer s'il vous plaît, pourquoi ne prenons-nous pas en compte le coût de déplacement lors du calcul de la complexité de l'opération de suppression de LinkedList?

Je veux donc comprendre comment cela fonctionne dans le package Java.util. et si je veux implémenter la même chose en C ou C++, comment pourrais-je obtenir le O(1) pour la suppression aléatoire et l'insertion dans LinkedList?

6
Aditya Agarwal

Remove et les opérations add sont dites de O(1) dans le cas de LinkedList car, dans LinkedList, le décalage n'est pas impliqué, mais il y a une opération de translation impliquée non?

L'ajout à l'une des extrémités d'une liste chaînée ne nécessite pas de traversée, tant que vous conservez une référence aux deux extrémités de la liste. C’est ce que fait Java pour ses méthodes add et addFirst / addLast .

Il en va de même pour les paramètres sans paramètre remove et removeFirst / removeLast methods - ils fonctionnent aux extrémités de la liste.

remove(int) et remove(Object) les opérations, d'autre part, ne sont pas O (1). Ils nécessitent une traversée, vous avez donc correctement identifié leurs coûts comme étant O (n).

16
dasblinkenlight

La complexité de la suppression est considérée comme si vous avez déjà le pointeur sur la bonne position de l'élément que vous souhaitez supprimer ...

N'est pas considéré le coût que vous avez pris pour le trouver

Information on this topic is now available on Wikipedia at: Search data structure

    +----------------------+----------+------------+----------+--------------+
    |                      |  Insert  |   Delete   |  Search  | Space Usage  |
    +----------------------+----------+------------+----------+--------------+
    | Unsorted array       | O(1)     | O(1)       | O(n)     | O(n)         |
    | Value-indexed array  | O(1)     | O(1)       | O(1)     | O(n)         |
    | Sorted array         | O(n)     | O(n)       | O(log n) | O(n)         |
    | Unsorted linked list | O(1)*    | O(1)*      | O(n)     | O(n)         |
    | Sorted linked list   | O(n)*    | O(1)*      | O(n)     | O(n)         |
    | Balanced binary tree | O(log n) | O(log n)   | O(log n) | O(n)         |
    | Heap                 | O(log n) | O(log n)** | O(n)     | O(n)         |
    | Hash table           | O(1)     | O(1)       | O(1)     | O(n)         |
    +----------------------+----------+------------+----------+--------------+

 * The cost to add or delete an element into a known location in the list (i.e. if you have an iterator to the location) is O(1). If you don't know the location, then you need to traverse the list to the location of deletion/insertion, which takes O(n) time. 
** The deletion cost is O(log n) for the minimum or maximum, O(n) for an arbitrary element.
6
Rafael Lima

Oui, vous êtes correct si vous considérez deux opérations (indexation et insertion) en une fois. Ce n'est pas vrai dans ce cas car, lorsque vous insérez un nœud au milieu d'une liste chaînée, vous supposez que vous êtes déjà à l'adresse où vous devez insérer le nœud.

La complexité temporelle de l'accès au nœud est O(n) alors que seule l'insertion d'un nœud est O (1).

L'insertion à la tête nécessite d'ajouter l'élément et de mettre à jour le pointeur de la tête.

newnode->next = head;
head = newnode;

L'insertion à la queue nécessite de garder un pointeur sur l'élément tail, d'ajouter l'élément à la queue et de mettre à jour le pointeur.

tail->next = newnode;
tail = newnode;

La suppression de l’élément head nécessite la mise à jour de la tête et la suppression de l’élément précédent.

temp = head;
head = head->next;
delete temp; /* or free(temp); */

Toutes les opérations ci-dessus sont triviales et ne dépendent pas du nombre d’éléments de la liste chaînée. Par conséquent, ils sont O(1)

La suppression de l’élément tail serait cependant une opération O(n), car même si vous aviez un pointeur tail, vous auriez toujours besoin de l’avant-dernier nœud qui serait configuré comme nouveau nœud tail (en mettant à jour le pointeur de queue et en définissant le membre suivant du nœud sur NULL). Pour cela, vous devez parcourir toute la liste liée.

penultimate_el = find_penultimate_el(head); /* this is O(n) operation */
delete tail; /* or free(tail) */
tail = penultimate_el;
tail->next = NULL;
1
Ankit Joshi

ArrayList fournit resizable-array et stocke des "références" ou des "pointeurs" sur le stockage réel . Ce tableau de références doit être recréé s'il est développé au-delà de sa taille allouée. En d'autres termes, l'insertion d'un nœud au début nécessiterait soit de remonter tous les éléments existants, soit de réallouer la liste entière si elle dépasse la taille allouée. C'est pourquoi l'insertion est O(n).

Une LinkedList consiste en une chaîne de nœuds; chaque nœud est séparé alloué. Ainsi, lors de l'insertion, il n'est pas nécessaire de parcourir tous les nœuds. Et c'est pourquoi il a la complexité O(1). Cependant, si vous insérez à la fin et que vous n’avez que la référence du premier nœud, vous devrez peut-être parcourir la liste complète. La complexité dans ce cas sera donc O (n). .

EDIT

Si vous regardez le code source de Java.util.LinkedList, vous constaterez que LinkedList garde toujours la trace du dernier élément

Vous trouverez ci-dessous des extraits de code de la classe Java.util.LinkedList actuelle.

package Java.util;

...

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, Java.io.Serializable
{
    transient int size = 0;

    /**
     * Pointer to first node.
     */
    transient Node<E> first;

    /**
     * Pointer to last node.
     */
    transient Node<E> last;


    ...
    ...


    /**
     * Appends the specified element to the end of this list.
     */
    public boolean add(E e) {
        linkLast(e);
        return true;
    }



    ...
    ...


    /**
     * Links e as last element.
     */
    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }


    ...
    ...

}

Voir en particulier la méthode linkLast(). Il ne traverse pas toute la liste. Il insère simplement l'élément à la fin de la liste et c'est pourquoi la complexité temporelle est O(1).

1
Raman Sahasi