Variation of Linked List

In this tutorial we are going to discuss some variations of linked list.

Doubly-linked List
If you look at single link list, the chain is seen formed in a way that every node has a field next that point to the next node. This continues till the last node where we set the next to NULL i.e. the end of the list. There is a headNode pointer that points to the start of the list. We have seen that moving forward is easy in single link list but going back is difficult. For moving backward, we have to go at the start of the list and begin from there. Do you need a list in which one has to move back or forward or at the start or in the end very often? If so, we have to use double link list.
In doubly-link list, a programmer uses two pointers in the node, i.e. one to point to next node and the other to point to the previous node. Now our node factory will create a node with three parts.

clip_image005
First part is prev i.e. the pointer pointing to the previous node, second part is element, containing the data to be inserted in the list. The third part is next pointer that points to the next node of the list. The objective of prev is to store the address of the previous node.
Let’s discuss the code of the node of the doubly-link list. This node factory will create nodes, each having two pointers. The interface methods are same as used in singly link list. The additional methods are getPrev and setPrev. The method getPrev returns the address of the previous node. Thus its return type is Node*. The setPrev method sets the prev pointer. If we have to assign some address to prev pointer, we will call this method. Following is the code of the doubly-linked list node.

/* this is the doubly-linked list class, it uses the next and prev pointers */
class Node {
public:
int get() { return object; }; // returns the value of the element
void set(int object) { this->object = object; }; // set the value of the element
Node* getNext() { return nextNode; }; // get the address of the next node
void setNext(Node* nextNode) // set the address of the next node
{ this->nextNode = nextNode; };
Node* getPrev() { return prevNode; }; // get the address of the prev node
void setPrev(Node* prevNode) // set the address of the prev node
{ this->prevNode = prevNode; };
private:
int object; // it stores the actual value of the element
Node* nextNode; // this points to the next node
Node* prevNode; // this points to the previous node
};


Most of the methods are same as those in singly linked list. A new pointer prevNode is added and the methods to get and set its value i.e. getPrev and setPrev. Now we will use this node factory to create nodes.
You have to be very cautious while adding or removing a node in a doubly linked list. The order in which pointers are reorganized is important. Let’s have a pictorial view of doubly-link list. The diagram can help us understand where the prevNode and nextNode are pointing.

clip_image006
 
This is a doubly link list. The arrows pointing towards right side are representing nextNode while those pointing towards left side are representing prevNode. Suppose we are at the last node i.e. the node with value 1. In case of going back, we usually take the help of prevNode pointer. So we can go to the previous node i.e. the node with value 7 and then to the node with value 8 and so on. In this way, we can traverse the list from the end to start. We can move forward or backward in doubly-link list very easily. We have developed this facility for the users to move in the list easily.
Let’s discuss other methods of the doubly-linked list. Suppose we have created a new node from the factory with value 9. We will request the node factory to create a new object using new keyword. The newly created node contains three fields i.e. object, prevNode and nextNode. We will store 9 into object and connect this new node in the chain. Let’s see how the pointers are manipulated to do that. Consider the above diagram, the current is pointing at the node with value 6. The new node will be inserted between the node with value 6 and the one with value 8.
In the first step, we assign the address of the node with value 8 to the nextNode of the new node.
newNode->setNext( current->getNext() );

clip_image007
In the next step, a programmer points the prevNode of the newNode to the node with value 6.
newNode->setprev( current );

clip_image008
In the third step, we will set the previous node with value 8 to point to the newNode.
(current->getNext())->setPrev(newNode);
clip_image009
Now the prevNode of the node with value 8 is pointing to the node with value 9.
In the fourth step, the nextNode of the node with value 6 is pointing to the newNode i.e. the node with value 9. Point the current to the newNode and add one to the size of the list.
current->setNext( newNode );
current = newNode;
size++;
clip_image010
Now the newNode has been inserted between node with value 6 and node with value 8.

Circularly-linked lists

Let’s talk about circularly linked list. The next field in the last node in a singly-linked list is set to NULL. The same is the case in the doubly-linked list. Moving along a singly-linked list has to be done in a watchful manner. Doubly-linked lists have two NULL pointers i.e. prev in the first node and next in the last node. A way around this potential hazard is to link the last node with the first node in the list to create a circularly-linked list.
The next method in the singly-linked list or doubly-linked list moves the current pointer to the next node and every time it checks whether the next pointer is NULL or not. Similarly the back method in the double-linked list has to be employed carefully if the current is pointing the first node. In this case, the prev pointer is pointing to NULL. If we do not take care of this, the current will be pointing to NULL. So if we try to access the NULL pointer, it will result in an error. To avoid this, we can make a circularly linked list.
We have a list with five elements. We have connected the last node with the first node. It means that the next of the last node is pointing towards the first node.

clip_image011
The same list has been shown in a circular shape.

clip_image012
 
You have noticed that there is no such node whose next field is NULL. What is the benefit of this? If you use the next or back methods that move the current pointer, it will never point to NULL. It may be the case that you keep on circulating in the list. To avoid this, we get help from the head node. If we move the head node in the circularly linked list, it will not be certain to say where it was pointing in the start. Its advantages depend on its use. If we do not have to move too much in the list and have no problem checking the NULL, there is little need a circularly-linked list. But this facility is available to us.
In this example, we made a circular linked list from a singly link list. In a singly link list we move in one direction. We point the next pointer of the last node to the first node. We can do the same with the doubly-linked list. The prev pointer of the first node will point to the last node and the next pointer of the last node will point to the first node. If you arrange all the nodes in a circle, one of the pointers (i.e. next pointer) will move in clockwise direction while the prev pointers in anti-clockwise direction. With the help of these pointers, you can move in the clockwise direction or anti-clockwise direction. Head node pointer will remain at its position. You don’t need to change it. If there is a need to remove the node pointed by head node than you have to move the head pointer to other node. Now we don’t have any NULL pointer in the doubly-linked list. We will not get any exception due to NULL pointers.


Benefits of using circular list

While solving the Josephus problem, it was witnessed that the usage of circular linked list helped us make the solution trivial. We had to just write a code of some lines that solved the whole problem. In the program, we included the class CList (which is of our data structure i.e. circular linked list) and used all of its methods according to the requirements. There was no problem regarding the working of the methods. We just called these methods and their definition in the class CList worked well.

Now we will see what happens if we solve the Josephus problem by using an array instead of the class in our program. In this case, we have to define an array and write code to move back and forth in the array and to remove different elements properly in a particular order. A programmer needs to be very careful while doing this, to reach the solution of the problem. Thus our code becomes very complex and difficult for someone to understand and modify it. Moreover we cannot use this code in some other problem. There is no need to be worried whether an array, singly linked list, doubly linked list is used or circular linked list being employed internally in implementing the list in defining the class of list data type. We only want that it should create objects of list. The usage of the class of a data structure simplifies the code of the program. We can also use this class wherever needed in other programs. This shows that the choice of appropriate data structures can simplify an algorithm. It can make the algorithm much faster and efficient.


Reactions:

1 comment:

  1. This comment has been removed by a blog administrator.

    ReplyDelete