187 votes

Liste chaînée Python

Quelle est la manière la plus simple d'utiliser une liste chaînée en python ? En schéma, une liste chaînée est définie simplement par '(1 2 3 4 5) . Les listes de Python, [1, 2, 3, 4, 5] et des tuples, (1, 2, 3, 4, 5) Or, les listes liées ont des propriétés intéressantes, comme la concaténation en temps constant et la possibilité de référencer des parties distinctes de ces listes. Rendez-les immuables et il sera très facile de travailler avec elles !

13 votes

Cela pourrait vous aider à le visualiser pythontutor.com/)%0Ab+%3D+a%5B1%5D&mode=display&cumulative=false&py=2&curInstr=2)

0 votes

@user1889082 génial ! cela m'aide vraiment à comprendre certains concepts de python.

162voto

Ber Points 10364

Pour certains besoins, un deque peut également être utile. Vous pouvez ajouter et supprimer des éléments aux deux extrémités d'une deque à un coût de O(1).

from collections import deque
d = deque([1,2,3,4])

print d
for x in d:
    print x
print d.pop(), d

23 votes

Alors que deque est un type de données utile, mais ce n'est pas une liste chaînée (bien qu'il soit implémenté en utilisant une liste doublement chaînée au niveau C). Il répond donc à la question "qu'utiliseriez-vous ? au lieu de linked lists in Python ?" et dans ce cas, la première réponse devrait être (pour certains besoins) une liste Python ordinaire (ce n'est pas non plus une liste chaînée).

4 votes

@J.F.Sebastian : Je suis presque d'accord avec vous :) Je pense que la question à laquelle cela répond est plutôt : "Quelle est la manière pythonique de résoudre un problème qui utilise une liste chaînée dans d'autres langages". Ce n'est pas que les listes liées ne sont pas utiles, c'est juste que les problèmes où un deque ne fonctionne pas sont très rares.

9 votes

Cela n'a rien à voir avec "Python" : une liste chaînée est une structure de données différente d'une deque, et à travers les diverses opérations que les deux supportent, elles ont des temps d'exécution différents.

73voto

Nick Stinemates Points 5642

J'ai écrit ceci l'autre jour

#! /usr/bin/env python

class Node(object):
    def __init__(self):
        self.data = None # contains the data
        self.next = None # contains the reference to the next node

class LinkedList:
    def __init__(self):
        self.cur_node = None

    def add_node(self, data):
        new_node = Node() # create a new node
        new_node.data = data
        new_node.next = self.cur_node # link the new node to the 'previous' node.
        self.cur_node = new_node #  set the current node to the new one.

    def list_print(self):
        node = self.cur_node # cant point to ll!
        while node:
            print node.data
            node = node.next

ll = LinkedList()
ll.add_node(1)
ll.add_node(2)
ll.add_node(3)

ll.list_print()

0 votes

Comment pourriez-vous parcourir la liste et rechercher un nœud spécifique avec des données spécifiques ?

1 votes

@locoboy le code pour faire cela serait similaire dans la logique au code dans list_print() .

1 votes

Affiche la liste en ordre inverse

72voto

J.F. Sebastian Points 102961

Voici quelques fonctions de liste basées sur Représentation de Martin v. Löwis :

cons   = lambda el, lst: (el, lst)
mklist = lambda *args: reduce(lambda lst, el: cons(el, lst), reversed(args), None)
car = lambda lst: lst[0] if lst else lst
cdr = lambda lst: lst[1] if lst else lst
nth = lambda n, lst: nth(n-1, cdr(lst)) if n > 0 else car(lst)
length  = lambda lst, count=0: length(cdr(lst), count+1) if lst else count
begin   = lambda *args: args[-1]
display = lambda lst: begin(w("%s " % car(lst)), display(cdr(lst))) if lst else w("nil\n")

donde w = sys.stdout.write

Bien que les listes doublement chaînées soient utilisées de façon célèbre dans le livre de Raymond Hettinger intitulé recette de l'ensemble commandé les listes singulièrement liées n'ont aucune valeur pratique en Python.

J'ai jamais utilisé une liste singulièrement liée en Python pour tout problème sauf éducatif.

Thomas Watnedal Proposition de une bonne ressource éducative Comment penser comme un informaticien, chapitre 17 : listes chaînées :

Une liste chaînée est soit :

  • la liste vide, représentée par None, ou
  • un nœud qui contient un objet cargo et une référence à une liste chaînée.

    class Node: 
      def __init__(self, cargo=None, next=None): 
        self.car = cargo 
        self.cdr = next    
      def __str__(self): 
        return str(self.car)
    
    def display(lst):
      if lst:
        w("%s " % lst)
        display(lst.cdr)
      else:
        w("nil\n")

39 votes

Vous dites : Vous n'avez jamais utilisé une liste singulièrement liée en Python pour un problème autre qu'éducatif. C'est bien pour vous :-) Mais je peux vous assurer : Il y a des problèmes dans le monde réel où une liste chaînée fournira une solution idéale :-) C'est pourquoi j'ai scanné StackOverflow à la recherche de listes chaînées en premier lieu :-)

1 votes

@RegisMay : il y avait une longue liste de commentaires où votre point et d'autres étaient discutés. Malheureusement, ils ont été supprimés. Je ne veux pas ressasser les mêmes arguments à nouveau. Vous pourriez essayer de demander à un modérateur d'annuler la suppression.

0 votes

Merci de votre réponse, Sébastien. L'annulation de la suppression ne sera pas nécessaire. Il suffit que mon commentaire (ou un commentaire similaire) reste visible. Sinon, certains débutants pourraient penser que les listes liées sont purement académiques. Ce qui n'est pas le cas : Elles résolvent des problèmes que les autres variantes de listes ne peuvent tout simplement pas résoudre. Ces problèmes peuvent être très spécifiques, mais ils existent. Dans le monde réel.

39voto

Chris Redford Points 1417

La réponse acceptée est plutôt compliquée. Voici un modèle plus standard :

L = LinkedList()
L.insert(1)
L.insert(1)
L.insert(2)
L.insert(4)
print L
L.clear()
print L

Il s'agit d'un simple LinkedList basée sur la conception C++ simple et Chapitre 17 : Les listes chaînées comme recommandé par Thomas Watnedal .

class Node:
    def __init__(self, value = None, next = None):
        self.value = value
        self.next = next

    def __str__(self):
        return 'Node ['+str(self.value)+']'

class LinkedList:
    def __init__(self):
        self.first = None
        self.last = None

    def insert(self, x):
        if self.first == None:
            self.first = Node(x, None)
            self.last = self.first
        elif self.last == self.first:
            self.last = Node(x, None)
            self.first.next = self.last
        else:
            current = Node(x, None)
            self.last.next = current
            self.last = current

    def __str__(self):
        if self.first != None:
            current = self.first
            out = 'LinkedList [\n' +str(current.value) +'\n'
            while current.next != None:
                current = current.next
                out += str(current.value) + '\n'
            return out + ']'
        return 'LinkedList []'

    def clear(self):
        self.__init__()

10 votes

J'aime cette réponse. Un bémol, je crois que X is None est préféré à == . stackoverflow.com/a/2988117/1740227

0 votes

C'est la deuxième branche de insert pas un cas particulier du troisième, de sorte que vous pouvez entièrement supprimer le elif clause ?

13voto

Brian Points 48423

Voici une version légèrement plus complexe d'une classe de liste liée, avec une interface similaire aux types de séquences de python (c'est-à-dire qu'elle supporte l'indexation, le découpage, la concaténation avec des séquences arbitraires, etc). Elle devrait avoir un prepend O(1), ne copie pas les données sauf si c'est nécessaire et peut être utilisée de manière interchangeable avec les tuples.

Ce ne sera pas aussi efficace en termes d'espace et de temps que les cellules lisp, car les classes python sont évidemment un peu plus lourdes. (Vous pourriez améliorer légèrement les choses avec " __slots__ = '_head','_tail' "pour réduire l'utilisation de la mémoire). Il aura cependant les caractéristiques de performance big O souhaitées.

Exemple d'utilisation :

>>> l = LinkedList([1,2,3,4])
>>> l
LinkedList([1, 2, 3, 4])
>>> l.head, l.tail
(1, LinkedList([2, 3, 4]))

# Prepending is O(1) and can be done with:
LinkedList.cons(0, l)
LinkedList([0, 1, 2, 3, 4])
# Or prepending arbitrary sequences (Still no copy of l performed):
[-1,0] + l
LinkedList([-1, 0, 1, 2, 3, 4])

# Normal list indexing and slice operations can be performed.
# Again, no copy is made unless needed.
>>> l[1], l[-1], l[2:]
(2, 4, LinkedList([3, 4]))
>>> assert l[2:] is l.next.next

# For cases where the slice stops before the end, or uses a
# non-contiguous range, we do need to create a copy.  However
# this should be transparent to the user.
>>> LinkedList(range(100))[-10::2]
LinkedList([90, 92, 94, 96, 98])

Mise en œuvre :

import itertools

class LinkedList(object):
    """Immutable linked list class."""

    def __new__(cls, l=[]):
        if isinstance(l, LinkedList): return l # Immutable, so no copy needed.
        i = iter(l)
        try:
            head = i.next()
        except StopIteration:
            return cls.EmptyList   # Return empty list singleton.

        tail = LinkedList(i)

        obj = super(LinkedList, cls).__new__(cls)
        obj._head = head
        obj._tail = tail
        return obj

    @classmethod
    def cons(cls, head, tail):
        ll =  cls([head])
        if not isinstance(tail, cls):
            tail = cls(tail)
        ll._tail = tail
        return ll

    # head and tail are not modifiable
    @property  
    def head(self): return self._head

    @property
    def tail(self): return self._tail

    def __nonzero__(self): return True

    def __len__(self):
        return sum(1 for _ in self)

    def __add__(self, other):
        other = LinkedList(other)

        if not self: return other   # () + l = l
        start=l = LinkedList(iter(self))  # Create copy, as we'll mutate

        while l:
            if not l._tail: # Last element?
                l._tail = other
                break
            l = l._tail
        return start

    def __radd__(self, other):
        return LinkedList(other) + self

    def __iter__(self):
        x=self
        while x:
            yield x.head
            x=x.tail

    def __getitem__(self, idx):
        """Get item at specified index"""
        if isinstance(idx, slice):
            # Special case: Avoid constructing a new list, or performing O(n) length 
            # calculation for slices like l[3:].  Since we're immutable, just return
            # the appropriate node. This becomes O(start) rather than O(n).
            # We can't do this for  more complicated slices however (eg [l:4]
            start = idx.start or 0
            if (start >= 0) and (idx.stop is None) and (idx.step is None or idx.step == 1):
                no_copy_needed=True
            else:
                length = len(self)  # Need to calc length.
                start, stop, step = idx.indices(length)
                no_copy_needed = (stop == length) and (step == 1)

            if no_copy_needed:
                l = self
                for i in range(start): 
                    if not l: break # End of list.
                    l=l.tail
                return l
            else:
                # We need to construct a new list.
                if step < 1:  # Need to instantiate list to deal with -ve step
                    return LinkedList(list(self)[start:stop:step])
                else:
                    return LinkedList(itertools.islice(iter(self), start, stop, step))
        else:       
            # Non-slice index.
            if idx < 0: idx = len(self)+idx
            if not self: raise IndexError("list index out of range")
            if idx == 0: return self.head
            return self.tail[idx-1]

    def __mul__(self, n):
        if n <= 0: return Nil
        l=self
        for i in range(n-1): l += self
        return l
    def __rmul__(self, n): return self * n

    # Ideally we should compute the has ourselves rather than construct
    # a temporary tuple as below.  I haven't impemented this here
    def __hash__(self): return hash(tuple(self))

    def __eq__(self, other): return self._cmp(other) == 0
    def __ne__(self, other): return not self == other
    def __lt__(self, other): return self._cmp(other) < 0
    def __gt__(self, other): return self._cmp(other) > 0
    def __le__(self, other): return self._cmp(other) <= 0
    def __ge__(self, other): return self._cmp(other) >= 0

    def _cmp(self, other):
        """Acts as cmp(): -1 for self<other, 0 for equal, 1 for greater"""
        if not isinstance(other, LinkedList):
            return cmp(LinkedList,type(other))  # Arbitrary ordering.

        A, B = iter(self), iter(other)
        for a,b in itertools.izip(A,B):
           if a<b: return -1
           elif a > b: return 1

        try:
            A.next()
            return 1  # a has more items.
        except StopIteration: pass

        try:
            B.next()
            return -1  # b has more items.
        except StopIteration: pass

        return 0  # Lists are equal

    def __repr__(self):
        return "LinkedList([%s])" % ', '.join(map(repr,self))

class EmptyList(LinkedList):
    """A singleton representing an empty list."""
    def __new__(cls):
        return object.__new__(cls)

    def __iter__(self): return iter([])
    def __nonzero__(self): return False

    @property
    def head(self): raise IndexError("End of list")

    @property
    def tail(self): raise IndexError("End of list")

# Create EmptyList singleton
LinkedList.EmptyList = EmptyList()
del EmptyList

0 votes

Je suppose que ce n'est pas si surprenant, mais cet exemple vieux de 8 ans ( !) ne fonctionne pas avec python 3 :)

1 votes

Veuillez fournir une explication pour nouveau et un peu d'explication générale.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X