Introduction to Linked lists

 

Objectives of this lecture

q       Learn the basic ideas about linked list, its structure and its implementation.

 

What is a linked list?

q       Linked list, like stack and queue is a homogeneous linear list consisting of nodes in which each node is linked to the next.

q       However, unlike stack and queue, an item can be deleted at any location in the list, and can be added (inserted) at any location provided the order of the items in the list is maintained.

q       Thus, linked list is often referred to as key-ordered structure, whereas, stack and queue are referred to as time-ordered structures.  i.e., in queue and stack, the position of an item depends on the time it is added but in linked list, the position of an item depends on the order of the item relative to other items in the list.

q       The following figures shows the format of a linked list and how it behaves on insertion and deletion.

 

 

 

 


q       If p is inserted, the list becomes:

 

 

 

 


q       If n is deleted, the list becomes:

 

 

 

 


q       Thus, a linked-list is very useful in applications that require data to be in some order.

q       Some of the basic uperations of a linked list are: CreateList, ClearList, EmptyList, FullList, Traverse (visit each node to process it), Insert and Delete.

q       The following are declarations and function prototypes that are required for the implementation of linked list:

 


User Interface:

#define MINKEY … /*this the smallest possible element – depends

 on the application.  It is placed at the header

node to simplify insert and delete process */

typedef … ListEntry;   //type of entry on the list.

typedef struct listnode{   ListEntry entry;

                                             struct listnode *next;

                                         } ListNode;

 

typedef ListNode *List;

typedef ListNode *NodePointer;

 

void CreateList(List *list);

void ClearList(List *list);

Boolean EmptyList(List *list);

Boolean FullList();

void Traverse(List *list);

void Insert(ListEntry newitem, List *list);

void Delete(ListEntry target, List *list);

 

q       Note:  The function Traverse is used to visit each node of the list to process it (say print it) without deleting it. 

 

Inplementation:

q       We still make use of the function MakeListNode to simplify insertion of new node.

 

/*  MakeListNode: creates a node and place entry item in it.

Pre:   The entry item is valid.

Post: The function creates a new ListNode, initializes it with item,

        and returns a pointer to the new node.

*/

ListNode *MakeListNode(ListEntry item)

{   ListNode *p = (ListNode *) malloc(sizeof(ListNode));

    if (p != NULL)

    {  p->entry = item;

        p->next  = NULL; 

    } else

        Error("cannot obtain space for additional node.");

    return p;

}

 

q       The CreateList function is slightly different from that of ADT stack and queue.  A special node called header is placed at the head of the list.  Its introduction makes the implementation of insert and delete functions easier.

 

/* CreateList: Initilizes a list

Pre: None

Post: Header Node has been created and MINKEY is placed in its

          info field and list is set to point to the header node.

*/

void CreateList(List *list)

{  *list = MakeListNode(MINKEY);

     if (*list == NULL)

         ERROR(“No enough memory to create the list”);

}

 

Checking for an empty list involves examining the node after the header node.  The list itself never points to NULL because of the header node.

 

/* EmptyList:  Checks if the list empty

Pre: the list has been created

Post: returns true or false depending on if the list is empty or not

*/

Boolean EmptyList(List *list);

{ if ((*list)->next == NULL)

       return 1;

   else

      return 0;

}

 

/* Traverse: Visit each node in the list and process its entry

Pre: list has been created

Post: each entry in the list has been processed

Uses: Process – depends on the application

*/

void Traverse(List *list)

{  ListNode *p=(*list)->next;

  

   while (p != NULL)

   {  Process(p->entry);  /* example of process is printf */

       p=p->next;

   }

}

 

q       For Delete, we use a helper function FindNode which returns two pointers; the address of the node to be deleted (current) and the address of the node that precedes it (previous).  The following diagram shows how deletion may be achieved.

 

 

 

 

 

 

 

 

 


/* Find Node: searches for a given node in a list

Pre: list has been created

Post: returns two pointers: the address of the node before the target

node and that of the target node if found or NULL if not found.

*/

void FindNode(List *list, ListEntry target, NodePointer *previous,

NodePointer *current)

{   *previous = *list;

     *current = (*list)->next;

 

    while ( (*current !=NULL) && (*current)->entry<target)

    {   *previous = *current;

         *current = (*current)->next;

    }

 

    if ( (*current != NULL) && (*current)->entry != target)

        *current = NULL;

}

 

/* Delete: Delete a node containing the target entry if it exists

Pre: the list is created and contains the target entry

Post: the node containing the target entry has been deleted

Uses: FindNode

*/

void Delete(ListEntry target, List *list)

{   ListNode *current, *previous;

   

    FindNode(list, target, &previous, &current);

  

   if (current ==NULL)

       ERROR(“Attempt to delete non-existent node”);

   else

   {   previous->next = current->next;

        free(current);

   }

}

 

q       We can also use FindNode to find a place to insert a node.  However, in this case, we are not interested in the second pointer, so we called it ignore.  The following figure illustrates the process.

 

 

 

 

 

 

 

 

 

 

 

 

 


/* Insert: Inserts a node at the appropriate place in the list

   Pre: The list has been created and newitem is a valid entry

   Post: The newitem has been inserted.

   Uses: FindNode, MakeListNode

*/

void Insert(ListEntry newitem, List *list)

{   ListNode *newnode, *previous, *ignore;

   

    newnode=MakeListNode(newitem);

   if (newnode == NULL)

      ERROR(“Attempt to insert into a full list”);

   else

   {   FindNode(list, newitem,&previous,&ignore);

        newnode->next = previous->next;

        previous->next = newnode;

   }

}

 

Other Linked List Representations

q       There are many other different variant representations of lists than we discussed. We shall mention two common ones namely, circular linked lists and  doubly linked lists.  Let us mention each of these briefly:

 

Circular linked lists

q       A circular linked list is one in which the next field pointer of the last node (is not NULL but instead) points to the first node of the list.

q       Circular linked lists have the advantage that every node can be accessed starting from any other node in the list.

q       The need to search from the beginning or test the end of the list is avoided in circular linked list.

 

Doubly linked lists

q       In a doubly linked list, each node has two pointers pointing to opposite directions.

q       The node of a doubly linked lists can be defined by the following:

 

typedef struct ListNode {

    ListEntry   entry;

    struct ListNode *next;

    struct ListNode *previous;

} ListNode;

 

 

q       Doubly linked lists are more space-expensive than simply linked lists. Why?

q       Doubly linked lists double the cost of insertions and deletions because there are more pointers to fix.

q       They are a good data structure in applications that frequently require moving forward and backward through a list. 

q        

 

Exercise:  Implement FullList and ClearList.