Home Page
The Basics
Arrays
Classes
Abstract Data Types
Dynamic Memory Allocation
Operator Overloading
Lists
C++ Style Strings

Stacks and Queues
Variable Data Types
Templates
Sorting
Linked List
Recall the properties of a list which were covered earlier.  There are a few drawbacks.
insertion into a list required moving items to accommodate the insert items.
in doing this it is possible to exceed the array size.

This leads to a new version of the list using DMA called a linked list.

The idea:


Node:
a node consists of data and structuring information( head, next, tail)  

Things to do with nodes:
- find
- create
- remove
- cleanup

Let's create a node in C++ form:

class Node
{
public:
     int data;
     Node *next;
     Node(int d=0, Node *p = 0);  // constructor with default args.
};

Note that one of your data members is Node which is the type that you are currently creating.  This is a special case where this is permitted.

Node::Node (int d, Node *p)
{
     data = d;          // this creates a completely
     next = p;          // initialized Node
}

The idea of Linked lists is that you can allocate memory as you need them "on the fly".
Nodes are pointers.  To implement you would use the following as an example:

.
.
Node *head = new Node;
head->next = 0;     // same as (*head).next
Node *n = new Node;
n->next = 0;
Node *t = head;     // declares and sets to Node head
while(t->next != 0)
     t = t->next;
t->next = n;

 There is one major difference between a class and a struct in C++ :

class -     by default, all members are private
struct -    by default, all members are public

knowing this information and wanting to be able to access the nodes within a list, lets write a struct called LNode:

struct LNode
{
     int data;
     LNode *next;
     LNode(int d=0, LNode *n)  // uses default arguments
};

LNode::LNode(int d, LNode *n)
{
     data = d;
     next = n;
}

Now let's write the List Class:

class List
{
     LNode *head;

publilc:
     List();
     ~List();
     int insert(int record);          // return is error code
     int retrieve(int record);     // return is error code
     int remove(int record);
     void clear();
     bool empty();
};

we won't write out the constructor or the empty functions again.  Just need to know that:

constructor - sets head to zero
empty - checks if *head is NULL.  If so, it's empty and returns true

clear( )
Let's first concentrate on the clear( ).  It will be responsible to get rid of all the elements in the List.  It will go down the list one by one and free up.  *** keep track of the pointers!! ***

set the additional pointer and point at each node.
use that pointer to get at the next thing in the list and mark head.
then delete the node

void List::clear( )
{
     LNode *rest;     // meaning rest of the list

     // check for empty list already
if (head==0)          
          return;
     // loop and delete
     while (head)     // NULL would return false and indicate empty
     rest = head->next;
     delete head;
     head = rest;
}


Note:  Once you delete memory it's free game for use by other persons or program executions on the computer.  Watch where you place your delete in this case.  It may not be a problem but if something else takes that memory space before you retrieve the information, you're toast.  It's good practice to get the pointers straightened out first and then to delete memory.

You also need a destructor that does the same thing.

List::~List()
{
     clear();
}

You can often code like this when you are using DMA

There is a positive side effect of using the head pointer in the function above.  By using it and "stepping back" as we delete, when we kick out of the loop head = NULL so it has re-initialized back to zero.

There is another way to implement the destructor using recursion.

Recursion:   recursion is an analytical tool of problem approach.  It is a function that calls itself.
an example of a recursive function is as follows:

void demo(int a)
{
     int b;
     demo (a+1);
}

Look closely.  This function will cause BIG problems if you were to run it.  What really happens is that when the function is called it will allocate memory.  The recursive nature will continue to call the function on itself and continue to allocate memory for each additional instance.  The last call will be executed first so the memory keeps being chunked away until all system resources are annihilated

 So how can we make recursion useful for our purposes??

A)    It allows you to break up problems into smaller problems of the same type.

Example:  n!

n! = n * (n-1) * (n-2) * (n-3) * ….* 3 * 2 * 1  so we could then say…
n! = n * (n-1)!

when coding this you must think about where you are going to stop and how to do it.  You will stop when you reach 1! which is equal to 1.  That is the smallest you can encounter.

implementation:
int factorial(int n)
{
     if(n==0)
          return 1;
     return n * factorial(n-1);
}

Why are we bringing up recursion now?  Because deleting a list can be turned into a recursion.

Recursive version of deleting list: (recursive clear)

List::r_clear(LNode *n)
{
     if(n==0)
          return;
     r_clear(n->next);
     delete n;
}

BUT.. to use this we must first re-write the clear ( ) too.

List::clear()
{
     r_clear(head);     // this jump starts the private method r_clear
     head = 0
}

Creating a List (inserting nodes)
When you want to insert a node into a list, you need to know these things:
is the list sorted or unsorted?
should you control the location of the insert?

Let's implement a version tacking on a node to the beginning of a list.  Remember that this operation is all about changing pointer values.

void List::insert(int record)
{
     LNode *p;

     p = new LNode(record)     // using default arguments here
p->next = head;
head = p;
}

What's really going on here?*
first you are creating a pointer and assigning it DMA for the size of the record
next you're telling the pointer to point to what *head points to
next you're telling head to point to the new pointer you created.





Inserting at the end of a list:
The only real difference for inserting at the end of the list is that you have to loop through the list and check to see when you are at the end.  You need to start at the beginning of the list and step down to the bottom.  This is done in the following steps:

You should create two pointers.  One as a search pointer and one for your previous location.
start off with search = head and pred = 0.
use the slinky effect to safeguard against Buss errors which are caused by "stepping off the end" of your list.

Once you get to the end of the list (where search ==0) then you know you are at the end of the list and can insert the node in the position  of the pred->next.  Always be careful of the order of switching the pointers so you don't lose your nodes.


Example:

Node *search = head;
Node *pred = 0;
Node n;
n = new Node(pass arguments here);

// check to see if DMA created
if (n==0)
     return ERROR_CODE;     // return error code

// search for end of list
while (search !=0)
{
     pred = search;
     search = search->next;
}

//check if head of list
if(pred==0)
     {
n->next = search;
head = n;
return OK;     // return error code
}

// tack onto end of list
n->next = search;
pred->n = n;

This code will take care of inserting a node at the end of a list.  It can be easily modified to insert at any given place in the list by changing the search condition and adding additional checks.  Always remember to check for errors and all possible conditions.  It's really easy to lose information when changing the pointers so be sure where you are before you change them.

Deleting a node from a list:
A deletion from a list is accomplished in much the same way as the insert.  The only difference is in your pointer manipulation once you find the node to remove.  Step through as follows:

create two pointers (search and pred) and step through list same as in the insert.
once you find the node to delete you set the pred->next to the search->next
delete the search

make sure you do it in this order otherwise you will lose all nodes after the one you are deleting.  The following is an example of deleting from the end of a list.

Example:
{
Node *search,  *pred;
search = head;
pred = 0;

//search for end of list
while(search!=0)
{
     pred = search;
     search = search->next;
}
// check for at head of list
if(pred == head)
     {
     head = search->next;
     delete search;
     return OK;     // Error code
}

pred->next = search->next;
delete search;
}


Doubly Linked Lists:

If you create a doubly linked list it is much the same as previously mentioned except for the fact that each node has a previous and well as a next pointer.  It looks like this:


Example:

struct Node
{
     T data;
     Node *next;
     Node *perv;
};

deleting an entire list is done recursively the same as with a singly linked list.

Inserting into Doubly Linked List:

Search through your list same as before until you establish the position you wish to insert.
establish links from the new node first
then and only then sever the original links.

Don't let it confuse you.  Just remember that each node now has a previous and a next pointer.

Inserting into the middle of a list takes making the  pointer switch like this:

// connect new node
n->next = search;
n->prev = search->prev;

//now sever ties
search->prev->next = n;
search->prev = n;

Inserting at the head of a list takes making the pointer switch like this:
n->next = head;
n->prev = 0;
head->prev = n;
head = n;
Inserting at the end of a list takes making pointer switch like this:
// search to find end
while(search!=0)
{
     pred = search;
     search = search->next;
}

n = new Node();
n->prev = search;
n->next = 0;
search->next = n;


Removing Node from Doubly Linked List:
When you are deleting a node from a doubly linked list you only have two pointers to take care of.
If you have three nodes named 1, 2, and 3 respectively and you want to remove node 2 from the list, you need to take the next pointer from node one to point to node 3 and the previous pointer from node three to point to node 1.  This is accomplished in the following manner using only one search pointer:

search->next->prev = search->prev;
search->prev->next = search->next;
delete search;

This is diagramed below: