Archief - [C++] Eigen linked list

Het archief is een bevroren moment uit een vorige versie van dit forum, met andere regels en andere bazen. Deze posts weerspiegelen op geen enkele manier onze huidige ideeën, waarden of wereldbeelden en zijn op sommige plaatsen gecensureerd wegens ontoelaatbaar. Veel zijn in een andere tijdsgeest gemaakt, al dan niet ironisch - zoals in het ironische subforum Off-Topic - en zouden op dit moment niet meer gepost (mogen) worden. Toch bieden we dit archief nog graag aan als informatiedatabank en naslagwerk. Lees er hier meer over of start een gesprek met anderen.

Curahee Q

Legacy Member
Beste 9Livers

Als laatste labo van het academiejaar (3 weken terug ofzo) hebben we nog een opdracht gekregen om een eigen linked list te maken. Ik was hier al aan begonnen maar heb het vandaag opnieuw gemaakt om te zien of ik alles nu zelf kon schrijven (vooral het oefenen op pointers).

De klasse kregen we en die heb ik dan in lijst.h gestoken als volgt
Code:
#ifndef LIJST_H
#define LIJST_H

#include <string>
using namespace std;

typedef int Sleuteltype;
typedef string Datatype;

class Lijst {
    public:
        Lijst();
        Lijst(const Lijst &l);
        ~Lijst();
        
        bool is_ledig() const;
        int lengte() const;
        void schrijf() const;
        
        Datatype* zoek(const Sleuteltype & s);
        void voeg_toe(const Sleuteltype & s, const Datatype & d);
        void verwijder(const Sleuteltype & s);
        
        void samenvoegen_met(const Lijst & l);
        
    private:  
        struct Knoop {
            Sleuteltype sleutel;
            Datatype data;
            Knoop *vlg;
        };
        Knoop *begin;
};

#endif

Dus aan de klasse moet niets meer veranderd worden want dit was de opgave. Nu heb ik alle lidfuncties verder uitgewerkt en is het uiteindelijk het volgende geworden (lijst.cpp)

Code:
#include <iostream>
#include "lijst.h"
using namespace std;

/*
 * Constructor
 */
Lijst::Lijst() {
    begin = 0;  
}

/*
 * Copy Constructor
 */
Lijst::Lijst(const Lijst & l) {
    Knoop *l_k = l.begin;
    
    if(l_k != 0) {        
        Knoop *knoop = new Knoop;
        knoop->data = l_k->data;
        knoop->sleutel = l_k->sleutel;
            
        begin = knoop;
        l_k = l_k->vlg;
        Knoop *temp = begin;
        
        while(l_k != 0) {
            Knoop *kn = new Knoop;
            kn->data = l_k->data;
            kn->sleutel = l_k->sleutel;
            
            temp->vlg = kn;
            temp = kn;
            l_k = l_k->vlg;       
        }       
        
        temp->vlg = 0;
    }
    else {
        begin = 0;   
    }  
}

/*
 * Destructor, maakt geheugen terug vrij
 */
Lijst::~Lijst() {
    if(begin != 0) {
        Knoop *k = begin;
    
        while(k->vlg != 0) {
            k = k->vlg;
            delete begin;
            begin = k;         
        }
        
        delete begin;
    }    
}

/*
 * Kijkt of de lijst ledig is, of maw als begin naar nullpointer verwijst
 */
bool Lijst::is_ledig() const {
    return begin==0;   
}

/*
 * Geeft het aantal knopen binnen de lijst weer
 */
int Lijst::lengte() const {
    Knoop *k = begin;
    int len=0;
    
    while(k != 0) {
        len++;
        k = k->vlg;   
    }
    
    return len;
}

/*
 * Schrijft de gegevens naar het scherm onder de vorm <sleutel>: <data>
 */
void Lijst::schrijf() const {
    Knoop *k = begin;
    
    while(k != 0) {
        cout << k->sleutel << ": " << k->data << endl;
        
        k = k->vlg;   
    }   
}

/*
 * Zoekt naar een bepaalde sleutel en geeft een pointer naar de data terug
 * Indien desbetreffende knoop niet gevonden is wordt er een nullpointer teruggegeven
 */
Datatype* Lijst::zoek(const Sleuteltype & s) {
    Knoop *k = begin;
    
    while(k != 0 && s != k->sleutel) {
        k = k->vlg;   
    }
    
    return (k==0?0:&(k->data));                         // Als k de nullpointer is, return nullpointer anders adres naar k->data
}

/*
 * Voegt data toe aan de lijst, duplicaten worden weerhouden
 */
void Lijst::voeg_toe(const Sleuteltype & s, const Datatype & d) {    
    if(zoek(s) == 0) {                                  // Vermijd duplicaten
        Knoop *l = begin;
    
        Knoop *k = new Knoop;                           // aanmaken nieuwe knoop
        k->sleutel = s;
        k->data = d;
    
        if(begin == 0 || s < begin->sleutel) {
            begin = k;
            k->vlg = l;           
        }        
        else {
            while(l->vlg != 0 && s > l->vlg->sleutel) {
                l = l->vlg;   
            }
        
            k->vlg = l->vlg;
            l->vlg = k;
        }
    }
}

/*
 * Verwijdert een knoop aan de hand van de sleutel
 */
void Lijst::verwijder(const Sleuteltype & s) {
    Knoop *k = begin;
    
    if(begin != 0) {
        if(s == k->sleutel) {
            begin = k->vlg;
            delete k;   
        }
        else {
            while(k->vlg != 0 && s > k->vlg->sleutel) {
                k = k->vlg;   
            }   
        
            if(k->vlg != 0 && s == k->vlg->sleutel) {
                Knoop *l = k->vlg;
                k->vlg = k->vlg->vlg;
                delete l;
            }
        }
    }     
}

/*
 * Voegt de data van l toe aan deze lijst, duplicaten worden weerhouden
 */
void Lijst::samenvoegen_met(const Lijst & l) {
    Knoop *k = l.begin;
    
    while(k != 0) {
        voeg_toe(k->sleutel, k->data);
        
        k = k->vlg;   
    }    
}

Het werkt deftig. Maar toch zou ik jullie willen vragen om te kijken of er nog verbeteringen aangebracht kunnen worden.

De main ziet er dan zo uit

Code:
#include <string>
#include "lijst.cpp"
using namespace std;

int main() {
    Lijst l;
    
    l.voeg_toe(1, "een");
    l.voeg_toe(5, "vijf");
    l.voeg_toe(3, "drie");
    l.voeg_toe(8, "acht");
    l.voeg_toe(2, "twee");
    
    l.schrijf();
    
    return 0;   
}


Alvast bedankt

metalleke

Legacy Member
Is het niet de bedoeling van die oefening om in je verwijder functie, die zoek te gebruiken? Heb die oefening ook gekregen :)

Curahee Q

Legacy Member
Bij zoek krijg je geen pointer naar de knoop maar naar de data dus lijkt het me niet dat je dat kan gebruiken.

Cycloon

Legacy Member
Normaal schrijf je ook een private zoekfunctie die de knoop geeft, de zoekfunctie de data geeft maakt dan gebruik van die zoekfunctie (alsook je verwijder functie dan).

Curahee Q

Legacy Member
Ja inderdaad, maar die stond niet in de gegeven classe dusja. Maar inderdaad, het zou beter zijn zoals Cycloon het zegt.

Cycloon

Legacy Member
Curahee Q zei:
Ja inderdaad, maar die stond niet in de gegeven classe dusja. Maar inderdaad, het zou beter zijn zoals Cycloon het zegt.

Private methoden mag je altijd toevoegen ;)

Cycloon

Legacy Member
Het ligt er nochtans vingerdik op dat ze willen dat je zo'n gemeenschappelijke functie introduceert. Maar goed, als jij denkt dat zoiets niet mag, wie ben ik dan :)

Ik merk trouwens dat je er van uit gaat dat je lijst gesorteerd is. Een gewone gelinkte lijst is standaard niet gesorteerd (maakt toevoegen een stuk sneller).

theforce

Legacy Member
De code ziet er juist uit, maar misschien kan je nog wat optimaliseren qua complexiteit ofzo (als ge da al gezien hebt)?

Zo kan je bv. een membervariabele voor de lengte van de lijst bijhouden en telkens aanpassen als je iets verwijdert of toevoegt. Bij de functie lengte() ga je zo van lineaire naar constante tijd.

Ook kan je als membervariabele een pointer naar het laatste element in de lijst bijhouden. Bij het achteraan toevoegen of samenvoegen van 2 lijsten, ga je ook weer van lineaire naar constante tijd.

Dit zijn zowa een paar dingskes die ik zelf zou aanpassen, maar natuurlijk doe je wat je zelf wilt he

Curahee Q

Legacy Member
Dat hebben we nog niet gezien maar ik snap wel wat je wilt zeggen. Een teller verhogen of verlagen is natuurlijk altijd veel sneller dan heel de lijst afgaan naar de nullpointer.

Dat van die pointer naar het laatste element snap ik niet zo goed. Het is geen dubbel gelinkte lijst dus ik kan niet van achter naar voor gaan. Dus wat kan daar dan het voordeel mee zijn?

Is de destructor eigenlijk goed? Want daar had ik mijn twijfels over omdat ik die ook niet kan testen...

Cycloon

Legacy Member
Achteraan toevoegen is dan sneller (normaal voeg je vooraan toe en dan heb je daar geen last van). Maar vermits jij gaat sorteren heeft dat totaal geen voordeel. Bij het samenvoegen van lijsten gaat dit wel een voordeel opleveren omdat je het laatste element niet moet gaan zoeken. Je moet wel opletten dat als je het laatste element verwijderd je het "nieuwe laatste element" gaat moeten zoeken. Als je vaak achteraan moet verwijderen is het voordeel al snel terug weg natuurlijk.

theforce

Legacy Member
Ja, ik zie het nu pas, dat van dat laatste element samen met toevoegen is niet nodig in jouw geval, omdat jij gesorteerd toevoegt.

Maar als je nu altijd achteraan zou toevoegen, zou je misschien telkens met een while lus naar het laatste element navigeren (bv. while (knoop->next != null)) om dan hierachter een element toe te voegen.

Als je het laatste element dan ergens zou opslaan (en telkens update bij het toevoegen/verwijderen) zou die while-lus niet nodig zijn en gebeurt toch exact hetzelfde.

Bij de methode voor het samenvoegen (concateneren) geeft dit wel voordeel omdat je dan maar een paar pointervariabelen moet aanpassen.



De destructor is juist denk ik. Maar je kan het bv. eens op een blad papier tekenen om te zien wat er telkens gebeurt, met een paar gevallen (bv een lege lijst, lijst met 1 element, etc.)



Nog iets kleins: In je copy-constructor staat dit:

Lijst::Lijst(const Lijst & l) {
Knoop *l_k = l.begin;
...

Hier, en overal waar iets soortgelijks gebeurt, zou ik nog even een testje zetten of uw parameter niet gelijk is aan NULL. Dit kan namelijk vervelende crashes voorkomen.

Curahee Q

Legacy Member
Ja ik teken bij pointers alles op papier terwijl ik programmeer, anders vindk da ni te doen. Denk dat iedereen dat wel doet. Het lijk me in ieder geval juist.

Bij de copy constructor, ik test direct daarna toch of l_k != 0?

theforce

Legacy Member
ja sorry, dat was ff verkeerd van mij in dat geval. Uw parameter l kan hier nooit NULL zijn, omdat het een referentie is.

Maar wat ik eigenlijk bedoelde in het algemeen: neem nu dat je ergens een pointer doorgeeft als parameter en iets met die pointer wilt gaan doen. Dan zou ik wel altijd ergens testen of de pointer niet gelijk is aan NULL.

Curahee Q

Legacy Member
Ik probeer nu dus een private lidfunctie te maken die een pointer naar de knoop teruggeeft. Echter krijg ik errors. Mijn classe ziet er dan als volgt uit

Code:
#ifndef LIJST_H
#define LIJST_H

#include <string>
using namespace std;

typedef int Sleuteltype;
typedef string Datatype;

class Lijst {
    public:
        Lijst();
        Lijst(const Lijst &l);
        ~Lijst();
        
        bool is_ledig() const;
        int lengte() const;
        void schrijf() const;
        
        Datatype* zoek(const Sleuteltype & s);
        void voeg_toe(const Sleuteltype & s, const Datatype & d);
        void verwijder(const Sleuteltype & s);
        
        void samenvoegen_met(const Lijst & l);
        
    private:
        struct Knoop {
            Sleuteltype sleutel;
            Datatype data;
            Knoop *vlg;
        };
        Knoop *begin;
        int size;

        Knoop* zoekknoop(const Sleuteltype & s);
};

#endif

En in mijn lijst.cpp staat dan

Code:
Knoop* Lijst::zoekknoop(const Sleuteltype & s) {
    Knoop *k = begin;
    
    while(k != 0 && s != k->sleutel) {
        k = k->vlg;   
    }
    
    return k;  
}

De error messages
Code:
expected constructor, destructor, or type conversion before '*' token 
expected `,' or `;' before '*' token

forloRn_

Legacy Member
Je return type is niet helemaal juist.

Code:
Lijst::Knoop* Lijst::zoekknoop(const Sleuteltype & s) {
    // ...
}

nguaroth

Legacy Member
Waarom werk je hier eigenlijk niet met templates?
Is het ook de bedoeling om met een soort eigen iterator te werken?

theforce

Legacy Member
Volgens mij komt dat omdat de struct "Knoop" niet herkend wordt als returntype. Probeer eens de struct "Knoop" buiten de klasse te definiëren (bij de 2 typedefs). Dan moet het normaal wel lukken.

Houd er ook rekening mee, waar je de functie "zoekknoop" oproept, dat deze de waarde NULL kan teruggeven.

En ik wou nog ff opmerken dat je "lijst.CPP" include bij je main. Je moet hier echter de headerfile includen, anders krijg je linking-errors.

Curahee Q

Legacy Member
Het werkt met
Code:
Lijst::Knoop* Lijst::zoekknoop(const Sleuteltype & s) {
    // ...
}

Als ik lijst.h include in mijn main programma weet hij toch niet waar de lidfuncties staan? Heb het getest en krijg linking errors als ik lijst.h include ipv lijst.cpp.

lijst.h -> lijst.cpp -> oef.cpp

Jij zegt

lijst.h -> lijst.cpp
lijst.h -> oef.cpp
Het archief is een bevroren moment uit een vorige versie van dit forum, met andere regels en andere bazen. Deze posts weerspiegelen op geen enkele manier onze huidige ideeën, waarden of wereldbeelden en zijn op sommige plaatsen gecensureerd wegens ontoelaatbaar. Veel zijn in een andere tijdsgeest gemaakt, al dan niet ironisch - zoals in het ironische subforum Off-Topic - en zouden op dit moment niet meer gepost (mogen) worden. Toch bieden we dit archief nog graag aan als informatiedatabank en naslagwerk. Lees er hier meer over of start een gesprek met anderen.
Terug
Bovenaan