Pourquoi la suppression est-elle généralement beaucoup plus difficile à mettre en œuvre que l’insertion dans de nombreuses structures de données?

33

Pouvez-vous penser à une raison spécifique pour laquelle la suppression est généralement beaucoup plus difficile à implémenter que l'insertion de nombreuses structures de données (la plupart?)?

Exemple rapide: listes chaînées. L'insertion est triviale, mais la suppression comporte quelques cas particuliers qui la rendent beaucoup plus difficile. Les arbres de recherche binaires à auto-équilibrage tels que AVL et Red-black sont des exemples classiques d'implémentation de suppression douloureuse.

Je voudrais dire que cela a à voir avec la façon de penser de la plupart des gens: il est plus facile pour nous de définir les choses de manière constructive, ce qui conduit facilement à des insertions faciles.

Leo Brito
la source
4
Qu'en est- il pop, extract-min?
Coredump
5
"Plus difficile à mettre en œuvre" relève plus de la psychologie (cognition et des forces et des faiblesses de l'esprit humain) que de la programmation (propriétés des structures de données et des algorithmes).
sortie
1
Comme je pense que coredump a fait allusion à cela, les piles devraient être au moins aussi faciles à supprimer qu’ajouter tableau). Il existe également des cas d'utilisation supposant que les insertions seront fréquentes et les suppressions moins, mais il s'agirait d'une structure de données très magique où le nombre de suppressions est supérieur à celui des insertions. [1] Vous devriez probablement aussi annuler la référence désormais invisible à l'objet sauté pour éviter les fuites de mémoire, ce dont je me souviens bien parce que le manuel de Liskov ne le savait pas
Foon du
43
"Serveur, pourriez-vous s'il vous plaît ajouter plus de mayo à ce sandwich?" "Bien sûr, pas de problème, monsieur." "Pourriez-vous aussi enlever toute la moutarde?" "Euh ......"
cobaltduck
3
Pourquoi la soustraction est-elle plus compliquée que l'addition? Division (ou factorisation principale) plus compliquée que la multiplication? Des racines plus compliquées qu'une exponentiation?
mu est trop court

Réponses:

69

C'est plus qu'un simple état d'esprit; Il existe des raisons physiques (numériques) pour lesquelles la suppression est plus difficile.

Lorsque vous supprimez, vous laissez un trou où quelque chose se trouvait. Le terme technique pour l'entropie qui en résulte est "fragmentation". Dans une liste chaînée, cela nécessite que vous «corrigiez» le nœud supprimé et libérez la mémoire qu'il utilise. Dans les arbres binaires, cela provoque un déséquilibre de l'arbre. Dans les systèmes de mémoire, la mémoire devient inutilisée pendant un certain temps si les blocs nouvellement alloués sont plus grands que les blocs laissés par suppression.

En bref, l'insertion est plus facile car vous devez choisir l'endroit où vous allez insérer. La suppression est plus difficile car vous ne pouvez pas prédire à l'avance quel élément sera supprimé.

Robert Harvey
la source
3
La fragmentation n'est pas un problème où pointeurs et indirection entrent en jeu, que ce soit pour la structure en mémoire ou dans les diagrammes. En mémoire, peu importe l'emplacement des nœuds individuels en raison de l'indirection. Pour les listes, la suppression d'un nœud interne (à l'endroit où vous auriez un trou dans le diagramme) implique un peu moins d'opérations que d'insertion (1 affectation de pointeur et 1 affectation libre contre 1 et 2 affectations de pointeur). Pour les arbres, l'insertion d'un nœud peut déséquilibrer un arbre autant que la suppression. Ce sont les cas extrêmes qui sont à l'origine des difficultés évoquées par Brito, là où la fragmentation n'a pas d'importance.
sortie
12
Je ne suis pas d'accord pour dire que les insertions et les suppressions diffèrent par leur prévisibilité. "Corriger autour" d'un nœud de liste correspond exactement à ce qui se passe inversement si le même nœud doit être inséré à la place. Il n'y a pas d'incertitude dans aucun sens, et dans aucun conteneur sans structure intrinsèque à ses éléments (par exemple un arbre binaire équilibré, un tableau avec une relation stricte entre les décalages d'élément), il n'y a pas de "trou". Par conséquent, j'ai bien peur de ne pas savoir de quoi vous parlez ici.
sqykly
2
Très intéressant, mais je dirais que les arguments sont manquants. Vous pouvez organiser les structures de données autour de la suppression simple / rapide sans problème. C'est juste moins commun, probablement aussi moins utile.
luk32
@sqykly Je pense que la liste était un mauvais choix, car l'insertion moyenne et la relation moyenne sont également difficiles. Un cas alloue de la mémoire où l'autre réalloué. L'un ouvre un trou où l'autre scelle un trou. Tous les cas ne sont donc pas plus complexes à supprimer qu'à ajouter.
Ydobonebi
36

Pourquoi a-t-il tendance à être plus difficile à supprimer qu'à insérer? Les structures de données sont conçues plus avec l'insertion dans l'esprit que la suppression, et à juste titre.

Considérez ceci - pour supprimer quelque chose d'une structure de données, il faut qu'elle soit là en premier lieu. Vous devez donc d'abord l'ajouter, ce qui signifie qu'au plus vous avez autant de suppressions que d'insertions. Si vous optimisez une structure de données pour l'insertion, vous aurez au moins autant d'avantages que si elle avait été optimisée pour la suppression.

De plus, à quoi sert-il de supprimer séquentiellement chaque élément? Pourquoi ne pas simplement appeler une fonction qui le nettoie en une seule fois (éventuellement en créant simplement une nouvelle)? De plus, les structures de données sont particulièrement utiles lorsqu'elles contiennent quelque chose. Donc, le cas d’avoir autant de suppressions que d’insertions ne sera pas, dans la pratique, très courant.

Lorsque vous optimisez quelque chose, vous voulez optimiser ce que vous faites le plus et qui prend le plus de temps. En utilisation normale, la suppression d'éléments d'une structure de données est moins fréquente que l'insertion.

Rob Watts
la source
4
Il y a un cas d'utilisation que je peux imaginer. Une structure de données préparée pour l'insertion initiale, puis la consommation individuelle. Bien sûr, il s'agit d'un cas rare et d'un algorithme peu intéressant, car, comme vous l'avez dit, une telle opération ne peut pas dominer l'insertion de manière asymptotique. Peut-être qu’il ya un espoir en fait que l’insertion de lots puisse avoir un coût amorti assez bon et qu’elle soit rapide et simple à supprimer, de sorte qu’elle aurait des insertions de lot compliquées mais pratiques, ainsi que des suppressions individuelles simples et rapides. Certainement un besoin pratique très rare.
luk32
1
Ummm, je pense qu'un exemple pourrait être un vecteur ordonné inverse. Vous pouvez ajouter kassez rapidement un lot d'éléments: inverser le tri des entrées et fusionner avec le vecteur existant - O(k log k + n). Ensuite, vous avez une structure avec une insertion assez compliquée mais la consommation des uéléments supérieurs est triviale et rapide. Il suffit de prendre dernier uet de déplacer la fin du vecteur. Cependant, si quelqu'un a besoin d'une telle chose, je serai damné. J'espère que cela renforce au moins votre argument.
luk32
Ne devriez-vous pas vouloir optimiser le profil d'utilisation moyen plutôt que ce que vous faites le mieux?
Shiv
Une simple file d'attente de travail FIFO essaiera généralement d'être vide la plupart du temps. Une file d'attente bien conçue sera bien optimisée (c.-à-d. O (1)) pour les insertions et les suppressions (et une très bonne file supportera également les opérations simultanées rapides, mais c'est un problème différent).
Kevin
6

Ce n'est pas plus dur.

Avec les listes doublement liées, lors de l'insertion, vous allouez de la mémoire, puis vous liez avec le nœud principal ou précédent, et avec le nœud suivant ou le nœud suivant. Lorsque vous supprimez, vous supprimez exactement le même lien, puis vous libérez de la mémoire. Toutes ces opérations sont symétriques.

Cela suppose que dans les deux cas, vous avez le noeud à insérer / supprimer. (Et dans le cas de l'insertion, vous devez également insérer le nœud avant, donc, d'une certaine manière, l'insertion peut être considérée comme légèrement plus compliquée.) Si vous essayez de supprimer sans avoir le nœud à supprimer, mais la charge utile du nœud, vous devrez bien sûr commencer par rechercher la charge utile dans la liste, mais ce n’est pas un défaut de suppression, n’est-ce pas?

Il en va de même pour les arbres équilibrés: un arbre doit généralement être équilibré immédiatement après une insertion et également immédiatement après une suppression. C'est une bonne idée d'essayer de ne créer qu'un seul programme d'équilibrage et de l'appliquer après chaque opération, qu'il s'agisse d'une insertion ou d'une suppression. Si vous essayez d'implémenter une insertion qui laisse toujours l'arbre équilibré et une suppression qui laisse toujours l'arbre équilibré, sans que les deux partagent la même routine d'équilibrage, vous compliquez inutilement votre vie.

En bref, il n’ya aucune raison pour que l’un soit plus dur que l’autre, et si vous constatez que c’est le cas, il est en fait possible que vous soyez victime de la tendance (très humaine) de trouver plus naturel de penser de manière constructive plutôt que soustractive, ce qui signifie que vous pouvez implémenter la suppression d'une manière plus compliquée que nécessaire. Mais c'est un problème humain. D'un point de vue mathématique, il n'y a pas de problème.

Mike Nakis
la source
1
Je ne suis pas d'accord. L'algorithme de suppression AVL est plus complexe que l'insertion. Pour certains suppressions de nœuds, vous devrez peut-être rééquilibrer l'arborescence complète, ce qui est généralement effectué de manière récursive, mais peut également être effectué de manière non récursive. Vous n'êtes pas obligé de faire cela pour l'insertion. Je ne suis pas au courant des progrès de l'algorithme où un tel rééquilibrage d'arbre complet peut être évité dans tous les cas.
Dennis
@Dennis: il est possible que les arbres AVL suivent l'exception plutôt que la règle.
outis
@outis IIRC, tous les arbres de recherche équilibrés ont des routines de suppression plus compliquées (que l'insertion).
Raphaël
Qu'en est-il des tables de hachage fermées ? L'insertion est (relativement) simple, la suppression est au moins plus difficile à conceptualiser, car il faut corriger tout ce qui est "ce qui était censé être à l'indice X est actuellement à l'indice Y et nous devons aller le trouver et le remettre" problèmes.
Kevin
3

En termes d'exécution, en regardant la comparaison de complexité temporelle des opérations de structure de données sur Wikipedia, notez que les opérations d'insertion et de suppression ont la même complexité. L'opération de suppression décrite ici est une suppression par index, dans laquelle vous avez une référence à l'élément de structure à supprimer. l'insertion est par article. En pratique, la durée d'exécution la plus longue pour la suppression est due au fait que vous avez généralement un élément à supprimer et non son index. Vous avez donc également besoin d'une opération de recherche. La plupart des structures de données de la table ne nécessitent pas de recherche supplémentaire pour une insertion car la position de placement ne dépend pas de l'élément ou la position est déterminée implicitement lors de l'insertion.

En ce qui concerne la complexité cognitive, la réponse à la question est la suivante: les cas extrêmes. La suppression peut en contenir plus que l’insertion (ceci n’a pas encore été établi dans le cas général). Cependant, au moins certains de ces cas extrêmes peuvent être évités dans certaines conceptions (par exemple, un nœud sentinelle dans une liste chaînée).

outis
la source
2
"La plupart des structures de données ne nécessitent pas de recherche pour une insertion." -- tel que? J'affirmerais le contraire, en fait. (Vous "trouvez" la position d'insertion, ce qui est aussi coûteux que de retrouver le même élément plus tard.)
Raphael
@Raphael: cette réponse doit être lue dans le contexte de la table liée des complexités des opérations, qui n'inclut pas l'opération de recherche dans le cadre de la suppression. En réponse à votre question, j'ai classé la structure par nom commun. Des tableaux, des listes, des arbres, des tables de hachage, des piles, des files d'attente, des tas et des ensembles, des arbres et des ensembles nécessitent une recherche d'insertion; les autres utilisent un index non connecté à l'élément (pour les piles de base, les files d'attente et les tas, un seul index est exposé et la recherche n'est pas prise en charge) ou le calcule à partir de l'élément. Les graphiques peuvent aller dans les deux sens, selon leur utilisation.
outis
... Les essais pourraient être considérés comme des arbres; Cependant, si elles sont classées dans leur propre structure, le fait de savoir s'il existe une "découverte" lors de l'insertion est davantage un sujet de débat, je ne l'inclus donc pas. Notez que la liste de structure de données ne prend pas en compte l'interface vs l'implémentation. En outre, la façon dont vous comptez dépend en grande partie de votre classement. Je verrai si je peux penser à une déclaration plus objective.
outis
Je dois admettre que j'avais à l'esprit l'interface dictionnaire / jeu (comme dans CS). Quoi qu'il en soit, cette table est trompeuse et (iirc) même erronée à plusieurs endroits - Wikipedia, le gouffre de la désinformation de CS. : /
Raphael
0

En plus de tous les problèmes mentionnés, l’intégrité référentielle des données est impliquée. Pour que la structure de données, comme les bases de données SQL, soit correctement construite, l’intégrité référentielle Oracle est très importante.
Pour vous assurer que vous ne détruisez pas accidentellement de nombreuses choses inventées.
Par exemple, la suppression en cascade supprime non seulement ce que vous tentez de supprimer, mais déclenche également le nettoyage des données associées.
Cette base de données nettoie les données indésirables et préserve l’intégrité des données.
Par exemple, vous avez des tables avec des parents et des types en tant qu’enregistrements liés dans la seconde table.
Où parent est la table principale. Si vous ne disposez pas d'une intégrité référentielle renforcée, vous pouvez supprimer tous les enregistrements d'une table. Par la suite, vous ne saurez plus comment obtenir des informations complètes sur la famille, car vous avez des données dans la table enfant et rien dans la table parent.
C'est pourquoi le contrôle d'intégrité référentielle ne vous permettra pas de supprimer l'enregistrement de la table parent tant que les enregistrements de la table enfant n'auront pas été nettoyés.
Et c’est pourquoi, dans la plupart des sources de données, il est plus difficile de supprimer des données.

Alex
la source
Je pense que la question portait sur les structures en mémoire, telles que les listes chaînées, les tables de hachage, etc. plutôt que sur les bases de données, mais l'intégrité référentielle est un problème majeur, même avec les structures en mémoire.
Supercat