[A]Algorithmen/Datenstrukturen: Einfach-/Doppeltverkettete Listen



  • Ok,

    bin auch für den Vorschlag von nep. Und darum mach ich das jetzt er[edit]s[/edit]t einmal.



  • Tobias Gerg schrieb:

    Hi GPC,

    haben wir den schon einen Autor für Queue usw.? Wenn der Artikel vorher erscheint, ist das meiner Ansicht nach OK. Wenn nicht, dann würde ich es verwirrend finden, wenn Teil 2 vor Teil 1 kommt.

    Grüsse
    Tobi

    Du glaubst doch wohl selbst net das Teil2 vor Teil1 kommt 😃

    BR
    Vinzenz



  • Tobias Gerg schrieb:

    haben wir den schon einen Autor für Queue usw.?

    ja, CStoll hat Interesse bekundet.

    Tobias Gerg schrieb:

    Ok,

    bin auch für den Vorschlag von nep. Und darum mach ich das jetzt er[edit]s[/edit]t einmal.

    ACK.



  • Inhaltsverzeichnis

    • Einleitung
    • Struktur von Listen
    • Einfachverkette Listen
    • Doppeltverkette Listen
    • Fazit

    1 Einleitung
    Um effiziente Programme erstellen zu können, braucht man immer wieder verschiedene Datenstrukturen auf denen dann verschiedene Algorithmen laufen müssen. In dieser Artikelreihe werden wir uns mit diversen Datenstrukturen und auch einigen Algorithmen befassen.

    Speziell in diesem Artikel werde ich einen kleinen Einblick in einfach- bzw. doppeltverkettete Listen geben. In den einzelnen Kapiteln wird durch Beispiele gezeigt, wo und wie Listen verwendet werden können.

    2 Struktur von Listen.
    Um Listen darstellen zu können benötigt man zu Beginn erst einmal das Aussehen eines Listenelementes. Das könnte beispielsweise so aussehen.

    /* C Variante */
    struct Listenelement
    {
      /* Zu verwaltende Daten im Listenelement */
      /* ... */
    
      /* Zeiger auf das nächste Element */
      Listenelement* next;
    
      /* Bei einer Doppeltverkettung benötigen wir auch noch */
      /* einen Zeiger auf das vorherige Element */
      Listenelement* prev;
    
    };
    
    /* cpp Variante */
    class Listenelement
    {
    public:
      Listenelement()
      {
    
      }
      Listenelement(/*übergabe von zu speichernden Daten*/)
      {
        /*Daten initialisieren*/
      } 
    
    private:
      // Die Listenzeiger
      Listenelement* next;
      Listenelement* prev;
    };
    

    Neben der Struktur für das Listenelement brauchen wir noch das Aussehen für die Liste selbst. Normalerweise würde uns ein Zeiger auf ein Listenelement genügen, allerdings ist es geschickter sich eine eigene Struktur bzw. Klasse für die Liste zu definieren, da listenspezifische Dinge wie etwa die Anzahl der Listenelemente in dieser Struktur/Klasse gespeichert werden können.

    Die Liste selbst könnte dann wie folgt aussehen.

    /* C Variante */
    struct Liste
    {
      /* Listenbeginn */
      Listenelement* start;
      /* Anzahl der Elemente */
      int Elementanzahl;
    };
    
    /* cpp Variante */
    class Liste
    {
    public:
      Liste
      {
    
      }
      Liste(/*übergabe von zu speichernden Daten*/)
      {
        /*Daten initialisieren*/
      } 
    
    private:
      /* Listenbeginn */
      Listenelement* start;
      /* Anzahl der Elemente  */
      int Elementanzahl;
    };
    


  • Sollte es in C nicht so aussehen:

    /* C Variante */
    struct Listenelement
    {
      /* Zu verwaltende Daten im Listenelement */
      /* ... */
    
      /* Zeiger auf das nächste Element */
      struct Listenelement* next;
    
      /* Bei einer Doppeltverkettung benötigen wir auch noch */
      /* einen Zeiger auf das vorherige Element */
      struct Listenelement* prev;
    
    };
    

    Das struct vor "Listenelement" ist afaik in C essentiell wenn kein typedef dafür da ist.

    BR
    Vinzenz



  • Upps,

    danke hast natürlich recht. S****ß Leichtsinnsfehler.



  • Inhaltsverzeichnis

    • Einleitung
    • Struktur von Listen
    • Einfachverkette Listen
    • Doppeltverkette Listen
    • Fazit

    1 Einleitung
    Um effiziente Programme erstellen zu können, braucht man immer wieder verschiedene Datenstrukturen auf denen dann verschiedene Algorithmen laufen müssen. In dieser Artikelreihe werden wir uns mit diversen Datenstrukturen und auch einigen Algorithmen befassen.

    Speziell in diesem Artikel werde ich einen kleinen Einblick in einfach- bzw. doppeltverkettete Listen geben. In den einzelnen Kapiteln wird durch Beispiele gezeigt, wo und wie Listen verwendet werden können.

    2 Struktur von Listen.
    Um Listen darstellen zu können benötigt man zu Beginn erst einmal das Aussehen eines Listenelementes. Das könnte beispielsweise so aussehen.

    /* C Variante */
    struct Listenelement
    {
      /* Zu verwaltende Daten im Listenelement */
      /* ... */
    
      /* Zeiger auf das nächste Element */
      struct Listenelement* next;
    
      /* Bei einer Doppeltverkettung benötigen wir auch noch */
      /* einen Zeiger auf das vorherige Element */
      struct Listenelement* prev;
    
    };
    
    /* cpp Variante */
    class Listenelement
    {
    public:
      Listenelement()
      {
    
      }
      Listenelement(/*übergabe von zu speichernden Daten*/)
      {
        /*Daten initialisieren*/
      } 
    
    private:
      // Die Listenzeiger
      Listenelement* next;
      Listenelement* prev;
    };
    

    Neben der Struktur für das Listenelement brauchen wir noch das Aussehen für die Liste selbst. Normalerweise würde uns ein Zeiger auf ein Listenelement genügen, allerdings ist es geschickter sich eine eigene Struktur bzw. Klasse für die Liste zu definieren, da listenspezifische Dinge wie etwa die Anzahl der Listenelemente in dieser Struktur/Klasse gespeichert werden können.

    Die Liste selbst könnte dann wie folgt aussehen.

    /* C Variante */
    struct Liste
    {
      /* Listenbeginn */
      struct Listenelement* start;
      /* Anzahl der Elemente */
      int Elementanzahl;
    };
    
    /* cpp Variante */
    class Liste
    {
    public:
      Liste
      {
    
      }
      Liste(/*übergabe von zu speichernden Daten*/)
      {
        /*Daten initialisieren*/
      } 
    
    private:
      /* Listenbeginn */
      Listenelement* start;
      /* Anzahl der Elemente  */
      int Elementanzahl;
    };
    


  • Salute,

    ich muss noch mal stören. Ich sehe gerade, dass du sowohl die C als auch die C++ Variante zeigst... hat das nen bestimmten Grund? Mir wäre es, wenn du nur eine zeigst. Entweder C oder C++. Ich würde C vorschlagen, da es u.a. einfacher is, den C Code auf C++ umzusatteln, als von C++ auf C runter zu müssen. Du kannst aber auch gerne C++ nehmen. Oder anders gesagt: Nimm die Sprache, in der du fitter bist 🙂

    Wir könnten ja die Parallelimplementierung als Download zur Verfügung stellen, das wäre imo ein guter Mittelweg. Oder?

    Grüße

    GPC



  • OK,

    ich wollt es halt für beide machen. Aber dann mach ich es in C und die C++ Variante kommt dann als Download mit rein.

    Grüsse
    Tobi



  • Gut, aber wie gesagt, wenn du lieber die C++ Variante zeigen und die C als Download anbieten willst, kein Problem.



  • Inhaltsverzeichnis

    • Einleitung
    • Struktur von Listen
    • Einfachverkette Listen
    • Doppeltverkette Listen
    • Fazit

    1 Einleitung
    Um effiziente Programme erstellen zu können, braucht man immer wieder verschiedene Datenstrukturen auf denen dann verschiedene Algorithmen laufen müssen. In dieser Artikelreihe werden wir uns mit diversen Datenstrukturen und auch einigen Algorithmen befassen.

    Speziell in diesem Artikel werde ich einen kleinen Einblick in einfach- bzw. doppeltverkettete Listen geben. In den einzelnen Kapiteln wird durch Beispiele gezeigt, wo und wie Listen verwendet werden können.

    2 Struktur von Listen.
    Um Listen darstellen zu können benötigt man zu Beginn erst einmal das Aussehen eines Listenelementes. Das könnte beispielsweise so aussehen.

    /* C Variante */
    struct Listenelement
    {
      /* Zu verwaltende Daten im Listenelement */
      /* ... */
    
      /* Zeiger auf das nächste Element */
      struct Listenelement* next;
    
      /* Bei einer Doppeltverkettung benötigen wir auch noch */
      /* einen Zeiger auf das vorherige Element */
      struct Listenelement* prev;
    
    };
    

    Neben der Struktur für das Listenelement brauchen wir noch das Aussehen für die Liste selbst. Normalerweise würde uns ein Zeiger auf ein Listenelement genügen, allerdings ist es geschickter sich eine eigene Struktur bzw. Klasse für die Liste zu definieren, da listenspezifische Dinge wie etwa die Anzahl der Listenelemente in dieser Struktur/Klasse gespeichert werden können.

    Die Liste selbst könnte dann wie folgt aussehen.

    /* C Variante */
    struct Liste
    {
      /* Listenbeginn */
      struct Listenelement* start;
      /* Anzahl der Elemente */
      int Elementanzahl;
      //...
    };
    

    Die Basisoperationen bei verketteten Listen sind "Element einfügen" und "Element löschen". Implementierungen dieser und weiterer Operaptionen werden in den nächsten beiden Kapiteln gezeigt.

    3 Einfachverkettete Listen



  • Hallo!

    Ich habe das hier mal kurz überflogen und habe ein paar Empfehlungen dazu. Das Netz ist voll von Tutorials mit doppelt verketteten Listen.
    Leider wird bei 99% dieser Listen eine etwas ungeschickte Implementierung verwendet, indem das Ende der Liste durch einen Null-Pointer markiert wird.

    Ich kann hier noch nicht erkennen, was es mal werden soll, würde aber, auch um den Artikel von anderen ein bißchen abzuheben auf jeden Fall eine andere Variante ohne Null als Ende vorschlagen. Das ist afaik auch der Weg, den die meisten STL-Implementierungen gehen. Die Einführung eines Dummy-Knotens macht die Suche performanter, erleichtert die Implementierung von Iteratoren und macht den Code einfacher. Man bezahlt dafür nur die Kosten für den Speicher eines einzelnen ListNodes.

    MfG Jester

    edit: da ich auf dem Forumtreffen sowas in der Art eh in nem Vortrag erzählen werde, kann ich Dir gerne ein paar Folien dazu schicken (auch wenn die alles andere als ausführlich sind).



  • Hallo Jester,

    erstmal danke für die Tipps.

    Ich hatte nicht vor, das Ende der Liste mit einem Nullpointer zu makieren. Auch die Idee mit dem Dummy Knoten ist mir schon gekommen. Mal sehen wie ich das realisiere. Muss wohl meine alten Vorlesungsunterlagen nochmal durchkauen ;).

    Für die Folien wäre ich dir dankbar.

    Grüsse
    Tobi



  • Inhaltsverzeichnis

    • Einleitung
    • Struktur von Listen
    • Einfachverkette Listen
    • Doppeltverkette Listen
    • Fazit

    1 Einleitung
    Um effiziente Programme erstellen zu können, braucht man immer wieder verschiedene Datenstrukturen auf denen dann verschiedene Algorithmen laufen müssen. In dieser Artikelreihe werden wir uns mit diversen Datenstrukturen und auch einigen Algorithmen befassen.

    Speziell in diesem Artikel werde ich einen kleinen Einblick in einfach- bzw. doppeltverkettete Listen geben. In den einzelnen Kapiteln wird durch Beispiele gezeigt, wo und wie Listen verwendet werden können.

    2 Struktur von Listen.
    Um Listen darstellen zu können benötigt man zu Beginn erst einmal das Aussehen eines Listenelementes. Das könnte beispielsweise so aussehen.

    /* C Variante */
    struct Listenelement
    {
      /* Zu verwaltende Daten im Listenelement */
      /* ... */
    
      /* Zeiger auf das nächste Element */
      struct Listenelement* next;
    
      /* Bei einer Doppeltverkettung benötigen wir auch noch */
      /* einen Zeiger auf das vorherige Element */
      struct Listenelement* prev;
    
    };
    

    Neben der Struktur für das Listenelement brauchen wir noch das Aussehen für die Liste selbst. Normalerweise würde uns ein Zeiger auf ein Listenelement genügen, allerdings ist es geschickter sich eine eigene Struktur bzw. Klasse für die Liste zu definieren, da listenspezifische Dinge wie etwa die Anzahl der Listenelemente in dieser Struktur/Klasse gespeichert werden können.

    Die Liste selbst könnte dann wie folgt aussehen.

    /* C Variante */
    struct Liste
    {
      /* Listenbeginn */
      struct Listenelement* start;
      /* Anzahl der Elemente */
      int Elementanzahl;
      //...
    };
    

    Die Basisoperationen bei verketteten Listen sind "Element einfügen" und "Element löschen". Implementierungen dieser und weiterer Operaptionen werden in den nächsten beiden Kapiteln gezeigt.

    3 Einfachverkettete Listen
    Bei einfachverketteten Listen gibt es "nur" eine Vorwärtsverkettung. Das bedeutet, dass jeder Knoten nur seinen Nachfolger, nicht aber seinen Vorgänger kennt.

    [bild]
    ...
    [/bild]

    Im folgenden bauen wir uns eine Liste mit Beispieldaten auf. Die aneinanderzuhängenden Informationen sind hierbei der Einfachheit halber Integerwerte.

    3.1 Die klassische Implementierung
    Zunächst das Listenelement (die Node)

    typedef struct ListNode
    {
        int data; /* hier steht ein beliebiger Datentyp (auch Stukturen) */
        ListNode* next;
    
    };
    

    Dann benötigen wir unsere Listenstruktur

    typedef struct List
    {
        ListNode* begin;
        int ElementCounter;
        List()
        {
          begin = NULL;
          ElementCounter = 0;
        }
    
    };
    

    Als nächster Schritt ist eine Ein- bzw Anfügen Operation zu realisieren.

    // unsortiertes Einfügen in die Liste
    int InsertIntoList(List* list, int data)
    {
        //keine Liste? Schlecht! return
        if(!list)
          return 1;
    
        //Liste leer?
        if(list->begin == NULL)
        {
           // platz reservieren
           list->begin = (ListNode*)malloc(sizeof(ListNode));
    
           // Malloc schlägt fehl
           if(!(list->begin))
             return 1;
    
           // Daten schreiben
           list->begin->data = data;
           list->begin->next = NULL;
        }
        //ansonsten
        else
        {
          // Laufknoten
          ListNode* node;
          node = list->begin;
    
          // ans Ende hangeln
          while(node->next != NULL)
            node = node->next;
    
          // Speicher reservieren
          node->next = (ListNode*)malloc(sizeof(ListNode));
          if(!node)
            return 1;
    
          // Daten schreiben
          node->next->data = data;
          node->next->next = NULL;      
        }
        ++(list->ElementCounter);
        return 0;
    }
    

    [Anmerkung: Den Code verbessere ich noch war jetzt nur mal aus dem Kopf hingefrickelt]

    Als nächstes braucht es natürlich noch eine Löschoperation
    ...



  • Tobias Gerg schrieb:

    int InsertIntoList(List* list, int data)
    {
        //keine Liste? Schlecht! return
        if(!list)
          return 1;
        
        //Liste leer?
        if(list->begin == NULL)
        {
           // platz reservieren
           list->begin = (ListNode*)malloc(sizeof(ListNode));
           
           // Malloc schlägt fehl
           if(!(list->begin))
             return 1;
           
           // Daten schreiben
           list->begin->data = data;
           list->begin->next = NULL;
        }
        //ansonsten
        else
        {
          // Laufknoten
          ListNode* node;
          node = list->begin;
          
          // ans Ende hangeln
          while(node->next != NULL)
            node = node->next;
        
          // Speicher reservieren
          node->next = (ListNode*)malloc(sizeof(ListNode));
          if(!node)
            return 1;
    
          // Daten schreiben
          node->next->data = data;
          node->next->next = NULL;      
        }
        ++(list->ElementCounter);
        return 0;
    }
    

    naja, man versteht irgendwie, wie du drauf kommst.
    den ElementCounter würde ich wegwerfen und auch void returnen.

    das ans-ende-hangeln verstehe ich nicht. füge doch am anfang ein.

    und dann hilft von der naiven implemetierung zum ferrari der listen volkards bewähtes 5-punkte-tuning für verhettete listen.

    schritt1:
    oder du willst unbedingt ne lifo und keine fifo, dann merk die anfangs- und endezeiger. problem: zu viel if unf alles schwer zuz lesen und zu schreiben.

    schritt2:
    mach nen ring draus. ein bisschen if geht schon weg.

    schriit3:
    und merk die den letzten knoten statt beiden. member gespart und updatearbeit.

    schritt4:
    leg am anfang nen dummyknoten an, und merk dir den. sorge damit dafür, daß die liste nie leer ist. kein if mehr! 😮 👍

    schritt5:
    mach noch die indirektion weg, indem der dummyknoten selbst dein member ist und nicht ein zeiger drauf.

    die ganzen schritte würde ich am insert vormachen. jede zwischenstufe ausprogrammiert. die anderen methoden kann man dann direkt an den ferrari löten.
    außerdem ist die verwandlung der nativen implementierung in den ferrari bei der doppelt verketteten liste besser und am ende steht auch eine vollständige klasse da, an der nichts fehlt oder ungeschickt ist.
    bei der einfach verketteten liste haste eine remove-methode der zeitkomplexität o(n). die wirste fein los mit nem iterator, der aufs vorgängerelement zeigt (nebst wunderbarkeit, daß remove(it);++it; geht und der iterator nicht zerstört wurde). da wäre es vielleicht nett, die dopplet verkettete vor der einfach verketteten zu machen.



  • volkard schrieb:

    (nebst wunderbarkeit, daß remove(it);++it; geht und der iterator nicht zerstört wurde). da wäre es vielleicht nett, die dopplet verkettete vor der einfach verketteten zu machen.

    Aber das löschen könnte andere Iteratoren invalidieren, obwohl ihre Knoten nicht gelöscht werden. Klingt unschön.

    Vielleicht kann man in singly linked-lists einfach kein schönes delete anbieten.



  • @Volkard

    Jupp, ich weiß schon was du meinst, der Code war auch nur ein gefrickel von mir... Ich bieg dass noch gerade...

    Trotzdem danke für die Tipps. Werd mich dran halten...

    Grüsse
    Tobi



  • Inhaltsverzeichnis

    • Einleitung
    • Struktur von Listen
    • Einfachverkette Listen
    • Doppeltverkette Listen
    • Fazit

    1 Einleitung
    Um effiziente Programme erstellen zu können, braucht man immer wieder verschiedene Datenstrukturen auf denen dann verschiedene Algorithmen laufen müssen. In dieser Artikelreihe werden wir uns mit diversen Datenstrukturen und auch einigen Algorithmen befassen.

    Speziell in diesem Artikel werde ich einen kleinen Einblick in einfach- bzw. doppeltverkettete Listen geben. In den einzelnen Kapiteln wird durch Beispiele gezeigt, wo und wie Listen verwendet werden können.

    2 Struktur von Listen.
    Um Listen darstellen zu können benötigt man zu Beginn erst einmal das Aussehen eines Listenelementes. Das könnte beispielsweise so aussehen.

    /* C Variante */
    struct Listenelement
    {
      /* Zu verwaltende Daten im Listenelement */
      /* ... */
    
      /* Zeiger auf das nächste Element */
      struct Listenelement* next;
    
      /* Bei einer Doppeltverkettung benötigen wir auch noch */
      /* einen Zeiger auf das vorherige Element */
      struct Listenelement* prev;
    
    };
    

    Neben der Struktur für das Listenelement brauchen wir noch das Aussehen für die Liste selbst. Normalerweise würde uns ein Zeiger auf ein Listenelement genügen, allerdings ist es geschickter sich eine eigene Struktur bzw. Klasse für die Liste zu definieren, da listenspezifische Dinge wie etwa die Anzahl der Listenelemente in dieser Struktur/Klasse gespeichert werden können.

    Die Liste selbst könnte dann wie folgt aussehen.

    /* C Variante */
    struct Liste
    {
      /* Listenbeginn */
      struct Listenelement* start;
      /* Anzahl der Elemente */
      int Elementanzahl;
      //...
    };
    

    Die Basisoperationen bei verketteten Listen sind "Element einfügen" und "Element löschen". Implementierungen dieser und weiterer Operaptionen werden in den nächsten beiden Kapiteln gezeigt.

    3 Einfachverkettete Listen
    Bei einfachverketteten Listen gibt es "nur" eine Vorwärtsverkettung. Das bedeutet, dass jeder Knoten nur seinen Nachfolger, nicht aber seinen Vorgänger kennt.

    [bild]
    ...
    [/bild]

    Im folgenden bauen wir uns eine Liste mit Beispieldaten auf. Die aneinanderzuhängenden Informationen sind hierbei der Einfachheit halber Integerwerte.

    3.1 Die klassische Implementierung
    Zunächst das Listenelement (die Node)

    typedef struct ListNode
    {
        int data; /* hier steht ein beliebiger Datentyp (auch Stukturen) */
        ListNode* next;
    
    };
    

    Dann benötigen wir unsere Listenstruktur

    typedef struct List
    {
        ListNode* begin;
        int ElementCounter;
        List()
        {
          begin = NULL;
          ElementCounter = 0;
        }
    
    };
    

    Als nächster Schritt ist eine Ein- bzw Anfügen Operation zu realisieren.

    // unsortiertes Einfügen in die Liste
    int InsertIntoList(List* list, int data)
    {
        //keine Liste? Schlecht! return
        if(!list)
          return 1;
    
        //Liste leer?
        if(list->begin == NULL)
        {
           // platz reservieren
           list->begin = (ListNode*)malloc(sizeof(ListNode));
    
           // Malloc schlägt fehl
           if(!(list->begin))
             return 1;
    
           // Daten schreiben
           list->begin->data = data;
           list->begin->next = NULL;
        }
        //ansonsten
        else
        {
          // Laufknoten
          ListNode* node;
          node = list->begin;
    
          // ans Ende hangeln
          while(node->next != NULL)
            node = node->next;
    
          // Speicher reservieren
          node->next = (ListNode*)malloc(sizeof(ListNode));
          if(!node)
            return 1;
    
          // Daten schreiben
          node->next->data = data;
          node->next->next = NULL;      
        }
        ++(list->ElementCounter);
        return 0;
    }
    

    Nun ja, wie man sieht ist das ziemlich umständlich. Darum führen wir ein Dummyelement ein, an das wir die Elemente anhängen.

    [bild]
    ...
    [/bild]

    Der Code dazu sieht wie folgt aus.

    typedef struct List
    {
        ListNode* end;
        int ElementCounter;
        List()
        {
          end = (ListNode*)malloc(sizeof(ListNode));
          if(!end)
            return;
    
          end->next = end;
          end->data = 0xFFFF;  // ein Wert der in der normalen Liste
                               // NICHT angenommen wird.
          ElementCounter = 0;
        }
    
    };
    
    int insert(List* l, int data)
    {
        if(!l)
          return 1;
    
        //Speicher für Element reservieren
        ListNode* inserter = (node*)malloc(sizeof(node));
        if(!inserter)
          return 1;
    
        //Element einfügen (vor Enddummy)
        inserter->next = l->end->next;
        inserter->data = data;
        l->end->next = inserter;
        ++(l->ElementCounter);     
        return 0;
    }
    

    Der Dummy end, der für den begin Knoten ins Spiel gekommen ist, erleichert uns die Arbeit ungemein. Die Überprüfung ob die Liste leer ist entfällt komplett, da wir zu Beginn ein Dummyelement besitzen.

    Und das Einfügen geht somit auch leicht von der Hand, da wir immer am Beginn der Liste das neue Element einfügen. Damit haben wir eine LIFO Liste realisiert.

    Es gibt unzählige weitere Methoden Einfügeoperationen zu realisieren. Man könnte beispielsweise einen FIFO realisieren, indem man den Next Zeiger des Dummy immer auf das Element vor dem Dummy zeigen lässt.

    Als nächstes braucht es natürlich noch eine Löschoperation. Diese ist nicht so einfach zu realisieren wie es vielleicht zu begin aussehen mag, da die Verkettungsinformation nicht verloren gehen darf.

    //liste löschen
    int deleteList(List* l)
    {
        if(!l)
          return 1;
        //zwei laufzeiger
        ListNode* run1, *run2;
        run1 = run2 = l->end->next;	
        //solange elemente (einschließlich enddummy)
        //vorhanden. löschen
        while(run2)
        {
          //run2 aufs nächste Element setzen
          run2 = run1->next;
          //vorheriges Element löschen
          free(run1);
          //run1 nachrücken
          run1 = run2;
          //vom Ende entkoppeln
          l->end->next = NULL;
        }
        l->end = NULL;
    
        return 0;
    }
    

    Bei diesem Code läuft man immer um ein Element voraus, und löscht dann den vorhergehenden. Mit l->end->next = NULL entkoppelt man den Endzeiger vom ersten Element und bricht so den Ring auf, damit man nicht in eine Endlosschleife mit Speicherverletzungen kommt.

    Als nächstes betrachen wir noch zwei andere Operationen auf die Liste. Das Finden und Löschen von einzelnen Elementen.

    Das Finden:

    int findElement(List* l, int data)
    {
        if(!l)
          return 1;
    
        int count = 0;
        node* run = l->end->next;
    
        //durch die Liste laufen	
        while(run != l->end)
        {
          //gefunden?
          if(run->data == data)
            return count;
    
          //weitersuchen
          run = run->next;
          count++;
        }
        return -1;
    }
    

    Man läuft einfach durch die Liste und vergleicht die Elemente. Wird das Element gefunden liefern wir seine Position zurück ansonsten -1.

    Das Löschen eines einzelnen Elements:

    //ein Element löschen
    bool deleteElement(List* l, int data)
    {
        node* run, *pdelete;
        run = l->end;
        do
        {
          //gefunden
          if(run->next->data == data)
          {
            //Zeiger umhängen und Element merken
            pdelete = run->next;
    
            run->next = run->next->next;
            //Element löschen
            free(pdelete);
            --(l->ElementCounter);
            return true;
          }
    
          //weitersuchen
          run = run->next;
    
        }
        while(run != l->end);
    
        return false;
    }
    

    Auch beim Löschen laufen wir durch die Elemente der Liste bis wir das zu Löschende gefunden haben. Wir merken uns das Element und hängen die Zeiger entsprechend um. Danach wird das Element gelöscht.

    Auch hier gibt es wieder unzählige Strategien um Zeit und Kosten zu sparen. Zum Beispiel wäre es möglich, die Elemente die zu Löschen sind in einen Pool zu hängen und sich beim Einfügen aus diesem Pool zu bedienen. Das spart etwa die Speicherallokation beim Einfügen neuer Elemente.

    Mehr zu diesem Thema gibt es in den Literaturhinweisen. 😉

    4 Doppeltverkettete Listen
    ...



  • Morgen,

    auch hier die Frage, wie's zum nächsten Termin aussieht? Bitte um realistische Werte 😉

    Grüße

    GPC



  • 0 Inhaltsverzeichnis

    • Die Liste als Datenstruktur
    • Einfach verkettete Liste
    • Doppelt verkettete Liste
    • Typlisten (Listen zur Compilezeit)
    • Verwendundsbeispiele

    ...


Anmelden zum Antworten