Click here to Skip to main content
Email Password   helpLost your password?

What is a linked list?

A linked list can be thought of like an array, for it does basically the same thing. An array is a convenient way to store and sort many instances of similar data, or in other words, many variables of the same type. A basic linked list is simply a different approach to this idea.

What's the difference?

Arrays work in a way that each individual element is stored right after the previous, in succession, and the only thing needed to access them is a pointer to the first. In this way, you can access the first element by simply dereferencing that pointer.

char array[5];

*array; //Same as array[0]

Then, since each element is stored in succession, if you want to find the second element, simply look in the next piece of memory.

*(array + 1); //Same as array[1]

And so on. This approach uses very minimal memory, a must for things such as game programming and robotics programming, who often times do not have the luxury of a vast amount of memory, that we computer programmers have.

So, as you might have guessed now, linked lists use up much more, the most complex form of a linked list, a tree, uses about four times as much memory as an array, depending on the type of data that is being stored. If each variable is one byte, it would actually be thirteen times as much, but if you're storing strings, or something similar that can take up very large amounts of memory, the increase in memory usage would not be that significant.

So far, I've only talked about space issues, and if that's all you're looking at, there's no question that arrays are the way to go. But, space is not the only issue. There is also speed. One can think of space and speed as darkness and light. The more light there is, the less darkness there is, and vice versa. Space and speed often relate in the same way.

So, thus, in terms of speed, arrays are the lacking and linked lists win out. Or at least, that may be what you're thinking. But, that statement is not entirely true. There are some instances where it is, but there are also some where it is not. You must understand exactly where the two methods differ in order to know which will be faster in a given situation.. I've been going on for a while now, and you still probably have no clue how to make a linked list or use one, so I'll come back to this subject later.

The basics - a singly linked list

A friend once described a basic linked list as a train and each element being stored as its cars. I thought it was quite a good representation, so I shall do the same.

In a passenger train, each car holds data, the people riding in it, and a link to the next car, a door. An item, or node as it is called, of a singly linked list is the same. It holds data and a link to the next car, which in programming would be a pointer.

Now, let's look at the ticket taker. He starts at the front of the train and moves, one car at a time, to the back, checking each occupant for his or her ticket. In a linked list, the equivalent of the ticket taker would be a pointer. The pointer would start out pointing to the first node of a linked list. You, as a programmer, would dereference that pointer and look at the data in that node, perhaps change it or do something with it. Then, you would access the pointer in that node which points to the next node, and set that value into your ticket taker pointer. Now, your ticket taker pointer points to the second node, and you can again look at the data, and when you're done, you can set the ticket taker to point to the next node. The last item in the list would point to NULL as it's the next node, so you'd know you're done.

Now, this seems a bit complex, I know, but it's all just talking. So, let's put it into some actual code. I'll be using a C++ class for both the nodes and the linked list as a whole. For now, the data in each node will just be a simple int.

//Basic node class. 

class LLNode
 {
  public:
    LLNode() //Default Constructor

     {
      p_data = 0;    //No data.

      p_next = NULL; //New node always points to NULL by default.

     }
    LLNode(unsigned int data) //Constructor

     {
      p_data = data; //Set the data

      p_next = NULL; //New node always points to NULL by default.

     }
    //Retreive the data of the node. Constant function.

    unsigned int data(void) const 
     {
      return p_data;
     }
    //Retreive the next point of the node. Constant function.

    LLNode* next(void) const 
     {
      return p_next;
     }
    //Set the data of the node.

    void setdata(unsigned int data) 
     {
      p_data = data;
     }
    //Set the next pointer of the node.

    void setnext(LLNode* next) 
     {
      p_next = next;
     }
  private:
    //Data stored by the node. protected.

    unsigned int p_data; 
    //Pointer to the next node. protected.

    LLNode* p_next;      
 };
//Linked list class. Handles allocation, 

//deallocation, and sorting of nodes.

class LList
 {
  public:
    LList() //Constructor

     {
      p_first = NULL; //List contains no nodes by default.

     }
    ~LList() //Destructor

     {
      //Current node being pointed to.

      LLNode* current = p_first; 
      //Variable for storing the node to be deleted.

      LLNode* deleting;          

      //While current is not NULL, there are 

      //still more items in the list.

      while(current != NULL) 
       {
        //Store the current node to be deleted.

        deleting = current;        
        //Move to the next node.

        current = current->next(); 
        //Delete the stored node.

        delete deleting;           
       }
     }
    //Retreives the first node in the list. 

    //Constant function.

    LLNode* first(void) const 
     {
      return p_first;
     }
    //Add an item to the end of the list.

    void additem(unsigned int data) 
     {
      //Make sure there are nodes in the list.

      if(!p_first)                  
        //If there aren't make the first node.

        p_first = new LLNode(data); 
      else             //If there are items...

       {
        LLNode* current = p_first;     //Stores the current node.

        while(current->next() != NULL)//While there is a next node.

          current = current->next();   //Move to the next node.

        LLNode* prev = current;        //Store the current node.

        current = new LLNode(data);    //Make a new node.

        prev->setnext(current);  //Set the stored node to 

                                    //point to the new one.

       }
     }
    //Delete an item from the list, based on its data.

    bool deleteitem(unsigned int data) 
     {
      if(p_first == NULL) //Make sure there are items in the list.

        return 0;         //If there aren't end the function now.


      LLNode* current = p_first; //Start with the first node.


      //If the first node is the one we want...

      if(current->data() == data) 
       {
        p_first = current->next(); //Set p_first to NULL.

        delete current;            //Delete the node.

        return 1;                  //Success.

       }
      
      //While there is a next node and it is not the one we want.

      while(current->next() != NULL && 
            current->next()->data() != data)
        current = current->next();   //Move to the next node.


      if(current->next() != NULL) //If the node was found.

       {
        //Store the current node.

        LLNode* prev = current;         
        //Move to the node to be deleted.

        current = current->next();      
        //Remove links to the node.

        prev->setnext(current->next()); 
        //Delete the node.

        delete current;                 
        //Success.

        return 1;                       
       }
      return 0; //Failure.

     }
    //Search the list for a node with specified data.

    LLNode* search(unsigned int data) 
     {
      //Set the current node to the first.

      LLNode* current = p_first; 
      //While current is not NULL, there are still more nodes.

      while(current != NULL)     
       {
        //If we have found the node, with the correct data...

        if(current->data() == data) 
          return current;     //return it.

        //Otherwise, move to the next node and continue.

        current = current->next();  
       }
      return NULL; //If we never found the node, return NULL.

     }
    //Insert a new node after a node with specified data.

    bool insertitem(unsigned int prevdata, unsigned int data) 
     {
      //Find the node with the specified data.

      LLNode* prev = search(prevdata); 
      //If the node was not found...

      if(prev == NULL)                 
        return 0;    //We did not succeed.


      //Store the next node.

      LLNode* next = prev->next();        
      //Make the new node.

      LLNode* current = new LLNode(data); 
      //Set the previous node to point to the new one.

      prev->setnext(current);             
      //Set the new node to point to the next one.

      current->setnext(next);             

      return 1; //We did succeed.

     }
    //Insert a new node as the first node.

    void insertfirst(unsigned int data) 
     {
      //Create the new node.

      LLNode* current = new LLNode(data); 
      //Make the current node point to the old first node.

      current->setnext(p_first);          
      //Set p_first to the current node.

      p_first = current;                  
     }
  private:
    LLNode* p_first; //First node in the list. 

 };

There is a lot to take in, yes, but, with the comments, you should be able to understand it, pretty well. In your own linked list implementations, you can add in other functions you find necessary, or remove those that you feel are not. It's up to you.

Doubly Linked lists

Now, one thing you might have found about the previous, a singly linked list, is that when you are traversing it, you may only move in one direction. If your purposes serve, you may take this list and upgrade it to a doubly linked list. There are just a few things you must add to the node class to do this. Mostly, this is just a pointer to the previous node, in addition to the pointer to the next node, but you must make sure that you give this new pointer the correct address value, therefore you must edit both the node's class and the list's class. I've laid out the only node's class below, simply for space issues. The full code for both classes can be found in the source, provided with this article.

//Basic Doubly Linked List node class. 

class DLLNode
 {
  public:
    DLLNode() //Default Constructor

     {
      p_data = 0;    //No data.

      //New node always has a NULL next node.

      p_next = NULL; 
      //New node always has a NULL previous node.

      p_prev = NULL; 
     }
    DLLNode(unsigned int data) //Constructor

     {
      p_data = data; //No data.

      //New node always has a NULL next node.

      p_next = NULL; 
      //New node always has a NULL previous node.

      p_prev = NULL; 
     }
    //Retreive the data of the node. Constant function.

    unsigned int data(void) const 
     {
      return p_data;
     }
    //Retreive the preivous pointer of the node. 

    //Constant function.

    DLLNode* prev(void) const 
     {
      return p_prev;
     }
    //Retreive the previous pointer of the node. 

    //Cosntant function.

    DLLNode* next(void) const 
     {
      return p_next;
     }
    //Set the data of the node. 

    void setdata(unsigned int data) 
     {
      p_data = data;
     }
    //Set the previous pointer of the node.

    void setprev(DLLNode* prev) 
     {
      p_prev = prev;
     }
    //Set the next pointer of the node.

    void setnext(DLLNode* next) 
     {
      p_next = next;
     }
  private:
    //Data stored by the node. protected.

    unsigned int p_data; 
    //Pointer to the next node. protected.

    DLLNode* p_prev;     
    //Pointer to the previous node. protected.

    DLLNode* p_next;     
 };

Arrays and linked lists

So, back to the topic of the differences between the two. I've already discussed the space issues, in depth, so now I will go into speed.

Arrays are actually very fast, both from a programming aspect and from an executing aspect, but this is only in certain areas. Accessing and changing the values of array elements is extremely faster than with linked lists, since all you need to do is dereference a memory address. And finding a specific item in the list is easy as well. You can go directly to it through simple pointer arithmetic, unlike linked lists which must cycle through each node until they get to the desired one. Arrays fall very short, though, when it comes to sorting, re-sorting, inserting, and deleting elements. To sort or re-sort, you must move each individual element in memory, and there is always the aspect that they must be consecutively stored, that you must keep in mind. This applies to inserting and deleting, for in either one, you must move each individual element that follows the inserted or deleted one. This can be very time consuming, in both the coding stage and in execution.

Linked lists require much more code to perform simple operations, even though the code is simple to understand. But it is definitely more work than simply saying array[5], or whatever. However, where arrays are lacking, linked lists soar. Inserting and deleting an item in a list requires two simple changes to the rest of the list, unlike arrays that must change the entire remaining portion. The same goes for moving an item. It requires two actions to remove it, and two actions to put it back. Arrays, must do the 'change the entire list' thing twice, just to move one item.

For most purposes, arrays are quite satisfactory. Linked lists, like binary trees, which you will learn about in a moment, are mostly used when sorting the items is necessary.

Binary trees

And now for the biggie. Binary trees are one of the most efficient data structures for sorting data. I'll lay out a binary tree for you, visually:

      []
    /    \
  []      []
 /  \    /  \
[]  []  []  []

Each set of brackets represents a node, and as you can tell from the middle row of nodes, each one is linked to two different nodes, a left node and a right node. As I said, the main use of binary trees is for sorting. Let's say I have a linked list with a single node, storing the value of five.

          [5]

Now, I have a new number, entered by the user, that I need to sort. The number is three. Since that number is less than the value of my first node, I move to the left. Of course, there is no left node, so I simply add in a new one, containing the number three.

          [5]
        /
     [3]

If I get another number that is greater than the first node, say eight, I would go to the right. Again, there is no right node, so I go ahead and add in a new one.

          [5]
        /     \
     [3]       [8]

Next up I have a six. It is greater than five, so I move to the right. But this time, there is a node to the right, so I do the same checks on it. Six is less than eight, so I move left. Since there is no node there, I add it. (Get the pattern yet?)

          [5]
        /     \
     [3]       [8]
              /
            [6]

From here, I'll just skip the text. If you haven't picked it up yet, you will.

          [5]
        /     \
     [3]       [8]
        \     /
        [4] [6]
          [5]
        /     \
     [3]       [8]
    /   \     /
  [2]   [4] [6]
          [5]
        /     \
     [3]       [8]
    /   \     /
  [2]   [4] [6]
              \
              [7]
          [5]
        /     \
     [3]       [8]
    /   \     /   \
  [2]   [4] [6]   [9]
              \
              [7]
          [5]
        /     \
     [3]       [8]
    /   \     /   \
  [2]   [4] [6]   [9]
  /           \
[1]           [7]

And now we have a complete binary tree, that sorts the numbers one through nine. As you might have noticed, the actual structure of the tree depends upon how the numbers are entered. You could also have had...

     [3]
   /     \
[1]       [7]
   \     /   \
   [2] [5]   [8]
       / \     \
     [4] [6]   [9]

or...

      [6]
    /     \
 [1]       [7]
    \         \
    [4]       [9]
    / \       /
  [3] [5]   [8]
  /
[2]

or, you might have even had a balanced tree...

          [5]
        /     \
     [2]       [8]
    /   \     /   \
  [1]   [4] [6]   [9]
        /     \
      [3]     [7]

Although, technically, a "perfectly" balanced tree would also have a zero and a ten on the bottom corners. A balanced tree is just the term used for a tree in which each and every path is of the same length.

As I said, binary trees are a very efficient and fast way of sorting. However, this depends upon the structure of the tree. If you have a very unbalanced tree, it will take longer to sort something on the larger side, than on the smaller side. It's important to know this when using binary trees. And, as with linked lists, you can, if you desire, make yourself a doubly linked binary tree, where each node also points to the previous node. In fact, it would probably be preferable, for a link to the previous node makes outputting the sorted data, in a sorted order, much easier. The example I have provided incorporates this. But, it all depends on how you plan to use it.

When it comes to coding, there are only two things that you need to know how to do. Traversing the list and searching it. Both involve the idea of recursion, in which a function calls itself. I'll use the search function as an example, but again, the full code is available for download.

//Private, recursive, function for searching.

BTNode* p_search(unsigned int item, BTNode* node) 
 {
  if(node != NULL) //If the node is not NULL...

   {
    //If we have found the node...

    if(item == node->data()) 
      return node;    //Return it.

    //If the node's data is greater than the search item...

    if(item < node->data())  
      //Search the left node.

      return p_search(item, node->left());  
    //If the node's data is less than the search item...

    else                     
      //Search the right node.

      return p_search(item, node->right()); 
   }
  else
    //If the node is NULL, return NULL.

    return NULL; 
 }

With the comments, it should be self-explanatory. The code for traversing the list is slightly more complex, since you must be sure to traverse each and every node, rather than just those that exist down the path that you need to travel to find the node. To add a node, you would really just use the search function, but when a node is null, rather than failure, it means you have found the point where the node belongs, and you then insert it. Again, all the code is zipped for download.

History

Umm....none, yet. This is the first version.

Thanks

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralAbout some small error in the programs
cristitomi
22:32 19 Feb '07  
Hi!
If you get an error when you compile the example programs the first time, add the following line of code where the main function resides:
#include <string>

Then compile and everything is ok.


I am in love with VC++
GeneralRe: About some small error in the programs
Jaken Veina
18:26 6 Jun '07  
Thanks for all the input. I didn't spend an exceptional amount of time on that code. Mostly, I just wanted to put up something to relate the idea in code form, rather than just in theory. I figred there'd be some errors. Thanks for pointing them out.

"Never interrupt your opponent when he is making a mistake." ~Napolean Bonaparte

GeneralImprovement to the DoubleLinkedList
cristitomi
9:33 19 Feb '07  
Hi!
If you use a pointer to the last element of the list in the implementation of the double linked list, the application speed will decrease almost to nothing when you are using additem.
In order to do this, you have to make the following modifications to the program:
1) Add the declaration of the pointer to the last element to the double linked list:
DLLNode* p_last; // Last node in the list. Private

2) In the constructor of the list, add the following line of code:
p_last = NULL;

3) The additem now simply adds the next element after the last one, instead of going through all the items in the list, just to get to the last one:
void additem(unsigned int data)
{
if(p_first == NULL)
{
p_first = new DLLNode(data);
p_last = p_first;
}
else
{
DLLNode* newNode = new DLLNode(data);
p_last->setnext(newNode);
newNode->setprev(p_last);
p_last = newNode;
}
}

4) Also, in function insertfirst you must add this code line:
void insertfirst(unsigned int data)
{
if(p_first == NULL)
{
p_first = new DLLNode(data);
p_last = p_first; // add this line
}
.................................
}

Now you are ready to use the new version of the list.

And just to be clear about the speed of the new version, try a main like this:
int main(void)
{
DLList list;
for(int i = 0; i < 100000; i++)
list.additem(i);
return 0;
}
With the old version of the list, the total execution time of the sample main is:
321.712968 seconds
With the new version, the execution time is:
0.061139 seconds

The same observation goes with Linked List too (use a pointer to the last element of the list).

Best regards.

I am in love with VC++
General2 bugs in DoubleLinkedList
cristitomi
6:15 19 Feb '07  
Hi!
I discovered and corrected two bugs in the implementation of double linked list.
1) In function bool deleteitem(unsigned int data) modify code as indicated:
bool deleteitem(unsigned int data) 
{
if(p_first == NULL)
return 0;
DLLNode* current = p_first;
if(current->data() == data)
{
p_first = current->next();
if(current->next() != NULL) // add this line of code here
current->next()->setprev(NULL);
delete current;
return 1;
}

while(current->next() != NULL && current->next()->data() != data)
current = current->next();
if(current->next() != NULL)
{
DLLNode* prev = current;
current = current->next();
prev->setnext(current->next());
if(current->next() != NULL) // add this line of code here
current->next()->setprev(prev);
delete current;
return 1;
}

return 0;
}

Without adding the two lines:
a) the removal of the last element of the list
and
b) the removal of the first element of the list when the list has only one element
will fail.

2) The function void insertfirst(unsigned int data) should in fact look like:
void insertfirst(unsigned int data)
{
if(p_first == NULL)
{
p_first = new DLLNode(data);
}
else
{
DLLNode* current = new DLLNode(data);
current->setnext(p_first);
p_first->setprev(current);
p_first = current;
}
}

Otherwise, when trying to insert 0 as the first element of the list, the application crashes.

I am in love with VC++
GeneralStore the data of the list to a file
Lord_Vader
2:19 30 Mar '06  
Please help me on this. How can I save the data of a linked list to a file
and then retrieve the data using ofstream and ifstream objects?

-- modified at 7:19 Thursday 30th March, 2006
GeneralRe: Store the data of the list to a file
Jaken Veina
11:06 1 Apr '06  
Well, that all depends on your own personal style. Say you have a list that stores three intergers. If it was me, I would just write each number into the file, and then add a null terminator or some other value to signify the end of a single list element. Something like this pseudo-code.


/* Node contents
int a,
int b,
int c
*/


while(select next node)
{
fileout << node.a;
fileout << node.b;
fileout << node.c;
fileout << 0;
}


Then to retreive them back, you would do something like this.


while(more to read from file)
{
int tmpa;
int tmpb;
int tmpc;

filein >> tmpa;
filein >> tmpb;
filein >> tmpc;
ignore the next byte;

add_node(tmpa, tmpb, tmpc);
}


"Never interrupt your opponent when he is making a mistake." ~Napolean Bonaparte

-- modified at 16:07 Saturday 1st April, 2006
GeneralRe: Store the data of the list to a file
Lord_Vader
12:26 2 Apr '06  
Thanks a lot. I would like to learn whether I am able to do the same thing using the write and read methods of ofstream and ifstream objects that saves the data to a binary form.
GeneralRe: Store the data of the list to a file
Jaken Veina
14:54 3 Apr '06  
Well, yes of course. Simply replace << and >> operations with read() and write().

"Never interrupt your opponent when he is making a mistake." ~Napolean Bonaparte
Generaluse singly linked list for storing data
petesfish
12:04 27 Sep '05  
hi, nice article now i know how it works. I tried some modification on the sample code for storing some tchar's, but I realy don't know how to add chars to the nodes.

I'd like to store the data from a logical drive in a linked list in order to dynamically change the drive letters for some mount points.

Therefore I need to store three tchars:
TCHAR szDriveLetter[260];
TCHAR szVolumeName[260];
TCHAR szNtDeviceName[260];

Can someone tell me how to change the sample code in order to use it for my needs ?

Many thanks in advance.

br Peter
GeneralRe: use singly linked list for storing data
Jaken Veina
12:22 27 Sep '05  
Do you mean you want all three of those variables in each node, or you want to store the entire string of TCHARs as a linked list, with one TCHAR per node?

"Never interrupt your opponent when he is making a mistake." ~Napolean Bonaparte
GeneralRe: use singly linked list for storing data
petesfish
13:01 27 Sep '05  
Yes each node should store all three variables.

I'm a real noob in c++ ...Frown
currently I can't get the source running in visual c++ .net

GeneralRe: use singly linked list for storing data
petesfish
0:40 28 Sep '05  
I get it running on vc++ .net by adding
#include
and "unsigned" to the switch statements:
for(unsigned int index = 0; index < entry.length(); ++index)

now there is only a warning left:

"warning C4244: 'Argument': Conversion of '__int64' to 'unsigned int', possible data loss

Now I only need to know how to store my TCHAR's Wink
GeneralRe: use singly linked list for storing data
Jaken Veina
12:20 28 Sep '05  
Good, good. Although, I'd suggest taking care of that warning. Sometimes they can be ignored, if you know what you're doing, but they are there for a reason.

"Never interrupt your opponent when he is making a mistake." ~Napolean Bonaparte
GeneralSelf-Balancing tree
lcluck
5:53 17 Aug '05  
A red-black tree would provide a means of improving performance for large lists, but at the cost of an additional amount of complexity. See http://en.wikipedia.org/wiki/Red-black_tree for a description.

But that's probably beyond the scope of the article...Smile

Keep up the good work!


GeneralRe: Self-Balancing tree
Jaken Veina
13:39 17 Aug '05  
Excellent, excellent. Thank you very much for pointing out that article to me. It was very enlightening, even if I don't quite understand it yet. I'll keep at it and I'll definately have to add some sort of reference or explanation of the process. Thanks again.

"Never interrupt your opponent when he is making a mistake." ~Napolean Bonaparte
GeneralNice work
HatemMostafa
5:34 10 Aug '05  
here is my 5
GeneralRe: Nice work
Jaken Veina
7:36 10 Aug '05  
Heh. I don't know if I'd give it a five myself, but thanks for the compliment.
GeneralRe: Nice work
wasy
11:41 11 Mar '06  
speechless. really nice one.
GeneralSpeed comment
DavidCrow
3:53 10 Aug '05  
You comment, "So, thus, in terms of speed, arrays are the lacking and linked lists win out." is wrong. Accessing an item in an array (O(1)) is always faster than accessing an item in a linked list (O(N)).

Also, the LList::additem() method could be made much more efficient if you kept a pointer to the last node in the list. That way you would not have to traverse the entire list each time an item is added. See here for an example.


"One must learn from the bite of the fire to leave it alone." - Native American Proverb


GeneralRe: Speed comment
Jaken Veina
4:19 10 Aug '05  
True on both accounts, although, I did go into that issue furthur, showing that that statement was not entirely true.
GeneralThanks.
Jaken Veina
11:57 8 Aug '05  
Oh, thanks. I'll change that right away. And, yes, that is also true about arrays. I'll put that in.
Generalconfusion ?
Maximilien
1:59 8 Aug '05  
about your definition of an array.

the advantage of an array it that you can access every element with an index, not only with pointer arithmetics.

IMO, using arrays is preferred if you need direct access to a member; no need to loop every element to find the 5th element.




Maximilien Lincourt
Your Head A Splode - Strong Bad
GeneralLittle bug
AgeKay
0:58 8 Aug '05  
in the doubly linked list where it is
"p_next = NULL; //New node always has a NULL next node.
p_next = NULL; //New node always has a NULL previous node."

in the constructor. The comment says it right, but the second "p_next" should be "p_prev".


Last Updated 7 Aug 2005 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010