Contents  1-5  6-11  12-16  17-21  22-27   28-33  34 - 38  39-46  Projects   MFC

   Contact
   C
   C++
   Visual Basic
   Java
   JavaScript
   DHTML
   Style Sheets
   About
   Normalization
   Active X
   TDC Binding
   PHP
   Perl and CGI
   Flash
   XML
   SQL
   Chat
   MCSE
   Linux
   Cabling   
 

   
 
    
    

Linked Lists - Linked Lists are data structures that consist of small links that snap together - LEGOs, once again!. 
They can be compared to arrays in the way that they can store and sort elements.  However, arrays have several key
disadvantages:

"I do not feel obliged to believe that the same God who has endowed us with sense, reason, and intellect has intended us to forgo their use." - Galileo

"
Man is still the most extraordinary computer of all." - John F Kennedy
  • The size of an array is fixed.  If less elements are used than memory is allocated, memory is wasted.  If more elements are required than has been allocated for the array while the program is running, it will crash.  Without linked lists, the workaround for this is cumbersome: create an array dynamically on the heap and use realloc() to dynamically resize it at run-time.
  • Inserting new elements into an array can be expensive performance-wise, since the old elements have to be shifted over in memory.
  • Arrays allocate memory for all their elements in one block of memory.

By contrast, linked lists allocate memory for each object they store in its own separate block.  These separate segments of memory are often called nodes or segments, and are chained together with pointers.   Each node usually consists of two fields:

1) "data" field - used to store the data type/object.
2) "next" field - a pointer that links one node to the next.

The basic idea behind creating a linked list is to create a class to hold an object of data that points at the next container in the list.  You then create a container for each object you need to store, and then you chain them together as needed.  Linked lists consist of "nodes", modules of the list that hold the objects the list stores.  The linked list does little work other than delegating to the nodes.  It is a dynamically sized collection.  Linked lists have an advantage over arrays in that they can be sized dynamically at run time, whereas arrays are static and can not.  Once operating properly, you can reuse code for a number of objects in the list without having to rewrite anything.  Linked lists operate under this fundamental premise of encapsulation: Each object does ONE thing very well, then delegates to other objects anything that is not its core mission.  Let's review some basics:

nodes - the containers of a linked list are called nodes. The 1st is called the HEAD, the middle is called INTERNAL, and the last is called the TAIL node. 

Lists come in 3 fundamental forms:

  • Singly linked - each node points to next one but not backward
  • Doubly linked - moves backward AND forward in the chain
  • Trees - complex structure of nodes where each can point in 2 or 3 directions (binary trees).

Let's create a simple node to hold three Monster objects. 

//In the train example
#include <iostream.h>

class node
{    public:
     int x;
     node *next;   
//Points to node object itself
};


void LinkedList() {

     node *root;        
//This won't change, or we would lose the list in memory
     node *conductor;
    //This will point to each node as it traverses the list
     root = new node;   
//root creates a new node object on the heap and points to it

       //root is a pointer to a node object accessing that object's next data member.
       //This data member is itself a pointer to another node object.
       //It is initialized to NULL, otherwise it would not function correctly.

     root->next = NULL;

    
//pointer stores a value in the "int x" data member of the node object it points to.
     root->x=12;

     conductor = root;    
//The conductor now points to the first node.

     if(conductor != NULL)
     {
          while(conductor->next != NULL)
          {
             conductor = conductor->next;
          }
     }

     conductor->next = new node;    
//Creates a node at the end of the list
     conductor = conductor->next;   
//Points to that node
     conductor->next = NULL;        
//Prevents it from going any further
     conductor->x = 42;
     conductor = root;

     if(conductor != NULL)         
//Makes sure there is a place to start
     {
        while(conductor->next != NULL)
        {
           cout << conductor->x << "\n";
           conductor = conductor->next;
        }
        cout << conductor->x << "\n";
     }
}
//close function


void main() {

     LinkedList();
}

Example of a more developed linked list:

// Demonstrates an object-oriented approach to linked lists. The list delegates to the node.  The node is an
// abstract data type. Three types of nodes are used: head nodes, tail nodes and internal nodes. Only the internal
// nodes hold data.  The Data class is created to serve as an object to hold in the linked list.


#include <iostream.h>
//          0          1         2
enum { kIsSmaller, kIsLarger, kIsSame};

// forward class declarations/prototypes
class Node;
class HeadNode;
class TailNode;
class InternalNode;

//-----------------------------------------------------------------------------------------

//The Data class is the object to be to put into the linked list.  It holds in int data member.  Any class
//in this linked list must support two methods: Show (displays the value) and Compare (returns relative position).


class Data
{
      public:
      Data(int val):myValue(val){}
      ~Data(){}

      int Compare(const Data &);
      void Show() { cout << myValue << endl; }

      private:
      int myValue;
};

// Compare defined outside Data class, used to decide where in the list a particular object belongs.
int Data::Compare(const Data & theOtherData)
{
    if (myValue < theOtherData.myValue)
        return kIsSmaller;
    if (myValue > theOtherData.myValue)
        return kIsLarger;
    else
        return kIsSame;
}

//-----------------------------------------------------------------------------------------

//ADT representing the node object in the list. Every derived class must override Insert and Show.
class Node
{
      public:
      Node(){}
      virtual ~Node(){}

     
//Pure virtual functions
      virtual Node *Insert(Data *theData)= 0;
      virtual void Show() = 0;

      private:
};

//-----------------------------------------------------------------------------------------

// This is the node which holds the actual object. In this case the object is of type Data We'll see how to
// make this more general when we cover templates. Derives from ADT "node".


class InternalNode: public Node
{
      public:
      InternalNode(Data *theData, Node *next);
      ~InternalNode(){ delete myNext; delete myData; }

      virtual Node *Insert(Data *theData);
      virtual void Show() { myData->Show(); myNext->Show(); } 
//delegate!

      private:
      Data *myData;       
// the data itself
      Node *myNext;       
// points to next node in the linked list
};


// All the constructor does is initialize
InternalNode::InternalNode(Data *theData, Node *next):
myData(theData),myNext(next) { }

// The meat of the list. When you put a new object into the list, it is passed to
// the node which figures out where it goes and inserts it into the list.

Node *InternalNode::Insert(Data *theData)
{
   
   // Is the new Data object bigger or smaller than me?
      int result = myData->Compare(*theData);

      switch(result)
      {
      
// If it is the same as me, it comes first
        case kIsSame:     
// fall through
        case kIsLarger:   
// new data comes before me
             {
               InternalNode *dataNode = new InternalNode(theData, this);
               return dataNode;
             }

           // it is bigger than I am so pass it on to the next node and let HIM handle it.
        case kIsSmaller:
               myNext = myNext->Insert(theData);
               return this;
      }
//close switch

      return this;      
// appease MSC
}

//-----------------------------------------------------------------------------------------

// Tail node is just a sentinel

class TailNode : public Node
{
      public:
      TailNode(){}
      ~TailNode(){}

      virtual Node *Insert(Data *theData);
      virtual void Show() { }

      private:
};

// If data comes to me, it must be inserted before me. I am the tail and NOTHING comes after me.
Node *TailNode::Insert(Data *theData)
{
     InternalNode *dataNode = new InternalNode(theData, this);
     return dataNode;
}

//-----------------------------------------------------------------------------------------

// The Head node has no data, it just points to the very beginning of the list.

class HeadNode : public Node
{
      public:
      HeadNode();
      ~HeadNode() { delete myNext; }

      virtual Node *Insert(Data *theData);
      virtual void Show() { myNext->Show(); }

      private:
      Node *myNext;
};

// Head node constructor defined outside class. As soon as the head is created, it creates the tail.
HeadNode::HeadNode()
{ myNext = new TailNode; }

// Nothing comes before the head so just, pass the data on to the next node.
Node *HeadNode::Insert(Data *theData)
{
       myNext = myNext->Insert(theData);
       return this;
}

//-----------------------------------------------------------------------------------------

// Linked list.  The work of adding and sorting objects is delegated outside this class.

class LinkedList
{
      public:
      LinkedList();
      ~LinkedList() { delete myHead; }

      void Insert(Data *theData);
      void ShowAll() { myHead->Show(); }

      private:
      HeadNode *myHead;
};

// At birth, I create the head node. It creates the tail node. So an empty list points to the head
// which points to the tail and has nothing between.  LinkedList constructor defined outside.

LinkedList::LinkedList()
{ myHead = new HeadNode; }


// Delegate, delegate, delegate

void LinkedList::Insert(Data *pData)
{ myHead->Insert(pData); }

//-----------------------------------------------------------------------------------------

int main()
{
    Data *pData;
    int val;
    LinkedList ll;

// Ask the user to produce some values put them in the list.
    for (;;)
    {
        cout << "What value? (0 to stop): ";
        cin >> val;

        if (!val)
            break;

        pData = new Data(val);
        ll.Insert(pData);
    }

     // Now walk the list and show the data.
   ll.ShowAll();

   return 0;        
// ll falls out of scope and is destroyed!
}
 

In this example, enumerated constants are returned by the Compare() method which must be supported by every object held in the linked list.  Inside the main() function, a local linked list is defined.  Then an infinite loop begins.  The linked list immediately delegates responsibility for inserting new objects to its HEAD node, invoking the Insert() method.  The HEAD node passes this responsibility to whatever node its pointer "myNext" is pointing to.  In the first case, before there are any objects stored, it points to the TAIL node.  The method TailNode::Insert inserts each object it is handed immediately before itself.  The TAIL node passes in its own "this" pointer (passes in itself).  Once the InternalNode is created, its address is assigned to the pointer dataNode, and the address is returned from the TailNode::Insert method.  Then it goes to HeadNode::Insert(), where the address of InternalNode is assigned to HeadNode's "myNext" pointer.  Finally the HeadNode's address is returned to the Linked List where it is thrown away (nothing is done with it because the linked list already knows the address of the HeadNode.  Insert() is declared in the base class Node.  If you change the value of HeadNode::Insert() you'll get a compile time error.  It is simpler to return the HeadNode and let the linked list throw it away.  Here's what happens:

1 - Data is inserted into the list.
2 - The linked list passes the data to the HEAD.
3 - The HEAD blindly passes the data to whatever node it is pointing to.
4 - In the first loop through the list, the HEAD is pointing to the TAIL.
5 - The TAIL then immediately creates a new InternalNode.
6 - The TAIL initializes the new InternalNode to point to the TAIL.
7 - The TAIL returns the address of the new node to the HEAD node.
8 - The HEAD reassigns its "myNext" pointer to point to the new node.

Let's review this process:

  • The Linked list maintains the HEAD node.
  • The HEAD node passes new data. 
  • The TAIL node creates new nodes and inserts them whenever handed data. 
  • The Internal nodes ask existing objects to compare themselves with new objects. 
  • Depending on results, they insert new objects or instead, pass them along. 
  • The Internal node has no idea how to do comparison, that is left to Data object itself. 
  • In this way each individual object is given a narrow, well-defined set of responsibilities - hence encapsulation. 

A Linked List for Monster Objects:

// Demonstrates an object-oriented approach to linked lists. The list delegates to the node.  The node is an
// abstract data type. Three types of nodes are used: head nodes, tail nodes and internal nodes. Only the internal
// nodes hold data.  The Data class is created to serve as an object to hold in the linked list.
#include <iostream.h>
//          0          1         2
enum { kIsSmaller, kIsLarger, kIsSame};
// forward class declarations/prototypes
class Node;
class HeadNode;
class TailNode;
class InternalNode;
//-----------------------------------------------------------------------------------------
//The Monster class is the object to be to put into the linked list.  It holds in int Monster member.  Any class
//in this linked list must support two methods: Show (displays the value) and Compare (returns relative position).
class Monster {
public:
	static int x;
    Monster(char * nam, int str) 
	{ name = nam;
	  strength = str; }
	~Monster() { cout << "Monster detroyed.\n"; }
	char * GetName() { return name;}
    int GetStrength() { return strength; }
    int Compare(const Monster &);
	void Show() { cout << name << x << ":" << strength << endl;
	             x++;}
private:
	char * name;
	int strength;
};
//Static member data is shared by all instances of a class.  static data members must be initialized OUTSIDE the class.
int Monster::x = 0;
// Compare defined outside Monster class, used to decide where in the list a particular object belongs.
int Monster::Compare(const Monster & theOtherMonster)
{
    if (strength < theOtherMonster.strength)
        return kIsSmaller;
    if (strength > theOtherMonster.strength)
        return kIsLarger;
else
return kIsSame;
}
//-----------------------------------------------------------------------------------------
//ADT representing the node object in the list. Every derived class must override Insert and Show.
class Node
{
      public:
      Node(){}
      virtual ~Node(){}
      //Pure virtual functions
      virtual Node *Insert(Monster *theMonster)= 0;
      virtual void Show() = 0;
      private:
};
//-----------------------------------------------------------------------------------------
// This is the node which holds the actual object. In this case the object is of type Monster We'll see how to
// make this more general when we cover templates. Derives from ADT "node".
class InternalNode: public Node
{
      public:
      InternalNode(Monster *theMonster, Node *next);
      ~InternalNode(){ delete myNext; delete myMonster; }
      virtual Node *Insert(Monster *theMonster);
      virtual void Show() { myMonster->Show(); myNext->Show(); }  //delegate!
      private:
      Monster *myMonster;        // the Monster itself
      Node *myNext;        // points to next node in the linked list
};
// All the constructor does is initialize
InternalNode::InternalNode(Monster *theMonster, Node *next):
myMonster(theMonster),myNext(next) { }
// The meat of the list. When you put a new object into the list, it is passed to 
// the node which figures out where it goes and inserts it into the list.
Node *InternalNode::Insert(Monster *theMonster)
{
       // Is the new Monster object bigger or smaller than me?
      int result = myMonster->Compare(*theMonster);
      switch(result)
      {
       // If it is the same as me, it comes first
        case kIsSame:      // fall through
        case kIsLarger:    // new Monster comes before me
             {
               InternalNode *MonsterNode = new InternalNode(theMonster, this);
               return MonsterNode;
             }
           // it is bigger than I am so pass it on to the next node and let HIM handle it.
        case kIsSmaller:
               myNext = myNext->Insert(theMonster);
               return this;
      } //close switch
      return this;       // appease MSC
}
//-----------------------------------------------------------------------------------------
// Tail node is just a sentinel
class TailNode : public Node
{
      public:
      TailNode(){}
      ~TailNode(){}
      virtual Node *Insert(Monster *theMonster);
      virtual void Show() { }
      private:
};
// If Monster comes to me, it must be inserted before me. I am the tail and NOTHING comes after me.
Node *TailNode::Insert(Monster *theMonster)
{
     InternalNode *MonsterNode = new InternalNode(theMonster, this);
     return MonsterNode;
}
//-----------------------------------------------------------------------------------------
// The Head node has no Monster, it just points to the very beginning of the list.
class HeadNode : public Node
{
      public:
      HeadNode();
      ~HeadNode() { delete myNext; }
      virtual Node *Insert(Monster *theMonster);
      virtual void Show() { myNext->Show(); }
      private:
      Node *myNext;
};

// Head node constructor defined outside class. As soon as the head is created, it creates the tail.
HeadNode::HeadNode()
{ myNext = new TailNode; }

// Nothing comes before the head so just, pass the Monster on to the next node.
Node *HeadNode::Insert(Monster *theMonster)
{
       myNext = myNext->Insert(theMonster);
       return this;
}
//-----------------------------------------------------------------------------------------
// Linked list.  The work of adding and sorting objects is delegated outside this class.
class LinkedList
{
      public:
      LinkedList();
      ~LinkedList() { delete myHead; }
      void Insert(Monster *theMonster);
      void ShowAll() { myHead->Show(); }
      private:
      HeadNode *myHead;
};
// At birth, I create the head node. It creates the tail node. So an empty list points to the head
// which points to the tail and has nothing between.  LinkedList constructor defined outside.
LinkedList::LinkedList()
{ myHead = new HeadNode; }
// Delegate, delegate, delegate
void LinkedList::Insert(Monster *pMonster)
{ myHead->Insert(pMonster); }
//-----------------------------------------------------------------------------------------
int main()
{
Monster Giant("Frank", 250);
    Monster *pMonster;
    int val;
    LinkedList MonsterList;
    //Ask the user to name some Monsters and put them in the list.
    for (;;)
    {
        cout << "What value? (0 to stop): ";
        cin >> val;
        if (!val)
            break;
        
        pMonster = new Monster("Monster",val);
        MonsterList.Insert(pMonster);
    }
     // Now walk the list and show the Monster.
   MonsterList.ShowAll();
   return 0;         // MonsterList falls out of scope and is destroyed!  A lot of cleaning up goes on!
}

 


©2004 C. Germany