Although linked lists sounds kind of scary, don't worry they are really easy to use once you've got a little practice under your belt! When I first learned this odd way of storing data, I really thought that I wouldn't be using them again. I certainly learned differently! Linked lists form the foundation of many data storing schemes in my game!
They are really nice when you don't know how many of a data type you will need, and don't want to waste space. They are like having a dynamically allocated string that fluctuates in size as the program runs. Before I really confuse you lets get into a better explanation!
A linked list is a chain of structs or records called nodes. Each node has at least two members, one of which points to the next item or node in the list! These are defined as Single Linked Lists because they only point to the next item, and not the previous. Those that do point to both are called Doubly Linked Lists or Circular Linked Lists. Please note that there is a distinct difference betweeen Double Linked lists and Circular Linked lists. I won't go into any depth on it because it doesn't concern this tutorial. According to this definition, we could have our record hold anything we wanted! The only drawback is that each record must be an instance of the same structure. This means that we couldn't have a record with a char pointing to another structure holding a short, a char array, and a long. Again, they have to be instances of the same structure for this to work. Another cool aspect is that each structure can be located anywhere in memory, each node doesn't have to be linear in memory!
typedef struct List
{ long Data;
List* Next;
List()
{Next=NULL;
Data=0;
}
};
typedef List* ListPtr;
Notice that we define a default constructor for our structure that sets Next equal to NULL. This is because we need to know when we have reached the end of our linked list. Each Next item that is NOT equal to NULL means that it is pointing to another allocated instance. If it does equal NULL, then we have reached the end of our list.
Starting up
First off, we need to set our Link pointers to some know location in memory. We will create a temp pointer, allocate an instance, then assign our pointers. Something like this:
SLList:: SLList()
{ Head = new List;
Tail=Head;
CurrentPtr = Head;
}
We can forget about doing anything with temp after this because Head will always be pointing to the memory allocated by it, until it is deleted. Just like we discussed, we created a temp pointer, allocated an instance of our structure, then assigned both Head and Tail to our new instance. This beginning point is very crucial. We must allocate at least one instance right away so our pointers are actually pointing to something relevant! Well now we have the smallest possible linked list, where head = tail. Pointer usage in linked lists make them a little hard to learn at first, but once you think of the uses of pointers it starts to come together. Now that we have our Head and Tail pointers actually pointing to something that is physically there, let's cover how to add on additional nodes into our list. A linked list with one node is kind of boring :)
Adding a Node
We can actually add nodes in two possible places, the beginning or the end, although the standard seems to be the end. This makes our linked list act kind of like a que with the head node being the oldest and end pointing to the newest objects. This brings up an interesting subject also. How will we use our linked list? This is what makes the linked list so powerful. We could use it as a priority list where the oldest objects get a higher precedence until deleted from the list. We could also use it as a master listing of items that need to be kept track of at one time, deleting object when they need to be, without using any precedence scheme. Here's some code that will add a node onto the end of the list, and then move the end pointer so that it really does point at the end.
void SLList::AddANode()
{Tail->Next = new List;
Tail=Tail->Next;
}
Here we add a node onto the end of our list, then move the Tail pointer to point to the new instance! After this function we can always access our new node through Tail since we allocated a new instance, then made Tail point to it!
Traversing the List
This is actually the most difficult part in dealing with Singly Linked Lists. This is because we can't immediately access the previous node should we need to, like when we want to delete a node and reconnect the node before to the node after the one being deleted. One easier way is to create a function that will traverse through the list a given number of nodes. This way, we can keep track of which one we are on should we need to delete it, then we could pass the node to the function and get a pointer to the previous node! Something like this :
ListPtr SLList::Previous(long index)
{ ListPtr temp=Head;
for(long count=0;countNext;
}
return temp;
}
ListPtr SLList::Previous(ListPtr index)
{ListPtr temp=Head;
if(index==Head) //special case, index IS the head :)
{ return Head;
}
while(temp->Next != index)
{ temp=temp->Next;
}
return temp;
}
If we know that we've gone into our list a certain number of nodes, we can pass that number to our Previous function and get a pointer to the previous node. This works well, but is hard to debug should we be off in our counter etc. I created a second version which lets you pass the node you are currently at as an argument, then we can be absolutely certain that we will get the previous node! I use the second version a LOT more. Also notice that the second has error checking. If we are currently at the Head node and try to go back one, it simply returns Head instead of returning garbage.
While creating our neeto class, I decided to use a class node pointer which we declared at CurrentPtr. To that end, I created two functions that move our pointer forward and back one node. If we are at the head and try to go back one node (into nothing), then the function doesn't move our pointer. Likewise if we are at the end of the list and try to advance to the next node (nothing), it doesn't move our pointer.
void SLList::Advance()
{ if(CurrentPtr->Next != NULL)
{ CurrentPtr=CurrentPtr->Next;
}
}
void SLList::Rewind()
{ if(CurrentPtr != Head)
{ CurrentPtr=Previous(CurrentPtr);
}
}
Deleting a Node
When deleting nodes from a linked list, there are 3 different cases to decide from. The node to be deleted is the head node, it's a middle node (somewhere between the head or tail, but not either) or it could be the tail node. Each requires a small change to take into account when deleting the node. Let's go over each one in depth.
void SLList::DeleteANode(ListPtr corpse) //<-- i thought it was funny :) { ListPtr temp; if(corpse == Head) //case 1 corpse = Head {temp=Head; Head=Head->Next;
delete temp;
}
else if(corpse == Tail) //case 2 corpse is at the end
{ temp = Tail;
Tail=Previous(Tail);
Tail->Next=NULL;
delete temp;
}
else //case 3 corpse is in middle somewhere
{temp=Previous(corpse);
temp->Next=corpse->Next;
delete corpse;
}
CurrentPtr=Head; //Reset the class tempptr
}
CurrentNode is actually corpse
Case 1: CurrentNode = Head Node
In this case, the node to be deleted is actually the Head node! This is a special case because there is no previous node to connect. We simply use our temp pointer to remember where Head is pointing at, advance the Head to the next position, then delete our saved location! Simple huh!
Case 2: CurrentNode = End node
In this case, the node to be deleted is actually the Tail node! This is a special case because we have a previous node, but no node afterwards to connect to. We save the old location of Tail using temp, set Tail equal to the previous node, set the Next pointer of Tail equal to NULL since it is at the end, then delete our temp pointer!
Case 3: CurrentNode is somwhere in between
In this case, there is a node before and a node after our current node. All we need to do is connect the previous node to the node after our current node. We set temp equal to our previous node and set the Next pointer to the node after our current one (corpse). Once they are connected, we can simply delete our current pointer! That's all there is to deleting nodes!
Before Exit
Before we can exit our program, we have to make certain that all of our dynamically allocated structures or nodes are deleted, otherwise we will have a memory leak! To fix this, we can build a routine to de-allocate any remaining nodes. Let's make it automatic and place it in the class destructor!
SLList:: ~SLList()
{ ListPtr temp = Head;
CurrentPtr = Head;
while(CurrentPtr != NULL)
{CurrentPtr = CurrentPtr->Next;
delete temp;
temp=CurrentPtr;
}
}
They are really nice when you don't know how many of a data type you will need, and don't want to waste space. They are like having a dynamically allocated string that fluctuates in size as the program runs. Before I really confuse you lets get into a better explanation!
A linked list is a chain of structs or records called nodes. Each node has at least two members, one of which points to the next item or node in the list! These are defined as Single Linked Lists because they only point to the next item, and not the previous. Those that do point to both are called Doubly Linked Lists or Circular Linked Lists. Please note that there is a distinct difference betweeen Double Linked lists and Circular Linked lists. I won't go into any depth on it because it doesn't concern this tutorial. According to this definition, we could have our record hold anything we wanted! The only drawback is that each record must be an instance of the same structure. This means that we couldn't have a record with a char pointing to another structure holding a short, a char array, and a long. Again, they have to be instances of the same structure for this to work. Another cool aspect is that each structure can be located anywhere in memory, each node doesn't have to be linear in memory!
typedef struct List
{ long Data;
List* Next;
List()
{Next=NULL;
Data=0;
}
};
typedef List* ListPtr;
Notice that we define a default constructor for our structure that sets Next equal to NULL. This is because we need to know when we have reached the end of our linked list. Each Next item that is NOT equal to NULL means that it is pointing to another allocated instance. If it does equal NULL, then we have reached the end of our list.
Starting up
First off, we need to set our Link pointers to some know location in memory. We will create a temp pointer, allocate an instance, then assign our pointers. Something like this:
SLList:: SLList()
{ Head = new List;
Tail=Head;
CurrentPtr = Head;
}
We can forget about doing anything with temp after this because Head will always be pointing to the memory allocated by it, until it is deleted. Just like we discussed, we created a temp pointer, allocated an instance of our structure, then assigned both Head and Tail to our new instance. This beginning point is very crucial. We must allocate at least one instance right away so our pointers are actually pointing to something relevant! Well now we have the smallest possible linked list, where head = tail. Pointer usage in linked lists make them a little hard to learn at first, but once you think of the uses of pointers it starts to come together. Now that we have our Head and Tail pointers actually pointing to something that is physically there, let's cover how to add on additional nodes into our list. A linked list with one node is kind of boring :)
Adding a Node
We can actually add nodes in two possible places, the beginning or the end, although the standard seems to be the end. This makes our linked list act kind of like a que with the head node being the oldest and end pointing to the newest objects. This brings up an interesting subject also. How will we use our linked list? This is what makes the linked list so powerful. We could use it as a priority list where the oldest objects get a higher precedence until deleted from the list. We could also use it as a master listing of items that need to be kept track of at one time, deleting object when they need to be, without using any precedence scheme. Here's some code that will add a node onto the end of the list, and then move the end pointer so that it really does point at the end.
void SLList::AddANode()
{Tail->Next = new List;
Tail=Tail->Next;
}
Here we add a node onto the end of our list, then move the Tail pointer to point to the new instance! After this function we can always access our new node through Tail since we allocated a new instance, then made Tail point to it!
Traversing the List
This is actually the most difficult part in dealing with Singly Linked Lists. This is because we can't immediately access the previous node should we need to, like when we want to delete a node and reconnect the node before to the node after the one being deleted. One easier way is to create a function that will traverse through the list a given number of nodes. This way, we can keep track of which one we are on should we need to delete it, then we could pass the node to the function and get a pointer to the previous node! Something like this :
ListPtr SLList::Previous(long index)
{ ListPtr temp=Head;
for(long count=0;count
No comments:
Post a Comment