CSE 211: Data Structures Lecture Notes VII LINKED LISTS In the previous lectures we have seen the representation of ordered lists using an array and sequential mapping. These representations had the property that successive nodes of the data object were stored in a fixed distance apart. For example: If the element a ij of a table was stored at location L ij then a ij+1 was at the location L ij +c for a known constant c. Problems: The sequential storage schemes are adequate if one wished to access for given node in a table, insertion or deletions of nodes within a stack or queue. However insertions or deletions of arbitrary elements become expensive. If we have several ordered lists of varying sizes, by storing each list in a different array of maximum size, storage may be wasted. Additionally, in several cases we will need more than accessing the top item as in the stacks or inserting to the tail and accessing the head (queues). We need more general operations such as finding or removing any item in the list, inserting a new item at any point. Solution: The problem of data movement in sequential representations may be solved by using linked representations and dynamic memory allocation. Unlike a sequential representation where successive items of a list are located a fixed distance apart, in a linked representation each item of the list may be placed anywhere in memory. To access elements in the list in the correct order, with each element we store the location of the next element in that list. Associated with each data item in a linked representation there is a pointer to the next item. In modern languages providing built-in support for linked structures, the compiler associates a special storage with an object program which it leaves initially unassigned to any program variable. This area is usually called the heap or the dynamic storage pool. There is a heap manager program for the allocation of this storage from the heap at execution time. The allocated space can be referenced using pointers. Remember that pointer is just an abstraction for a machine address. Linked List Building Blocks: A linked list is an ordered sequence of elements called nodes. A list has a beginning, or head and an end, or tail. Every node on the list is the same type, although that type can take different forms. A central property of linked lists is their dynamic nature. Nodes can be added to or removed from a list at any time. Because the number of nodes cannot be predicted before run time. You should use pointers, rather than arrays, to achieve an efficient and reliable implementation. Nodes
Each node of a list consists of two parts. The first part holds the data. The first part holds the data. The data can be a simple variable or, more generally a structure ( or pointer to a structure) of some type. The second part is a pointer that indicatesthe location of the next node of the list. The nodes of a list can be implemented by a recursive structure. Consider the case where a single integer varaible is to be stored at each node of the list. struct simplenode int item; struct simplenode *next;}; typedef struct simplenode simple_t; class simplenode int item; simplenode *next;... }; In this case the integer variable item holds the actual data in each node, and the pointer variable next holds the address of the next node. item next In general, the item part may contain several pieces of data defined in a strcut type. We can generalize the list definition above as: struct generallist infotype item; struct generallist *next; }; typedef struct generallist glist_t; template <class infotype> class generallist infotype item; generallist *next;... }; We assume that info_type has been defined before. The problem with this kind of implementation is that for each type of list you must assign the individual components one by one when transfering information to and from the nodes. Ex. if s1 and s2 are both structures. In C, you cannot say s1=s2. You can assign only a single structure component in each statement. You can however write a function called assign and call it whenever needed. assign(s1,s2); info_type s1,s2; /* assign each component of struct 2 to struct 1 } This function carries out the detailed transfer of the individual structure components from 21 to s1. You can then build a series of generic list manipulation functions that use assign.
The problem with this approach is the performance. You will be transfering data among different memory locations. However you only need to transfer the address of the blocks of data corresponding to the structures. Solution: use a structure composed of a pair of pointers. The first pointer points to the data, the second points to the next item. struct node infotype * item; struct node *next; }; typedef struct node node_type; template <class infotype> class Node infotype * item; Node *next;... }; item next Manipulation of Linked Lists The manipulation of linked lists involves allocating storage for nodes (in C using malloc) and assigning data and/or pointers so that the list is properly formed. List's boundary conditions must be decided. In other words how are the head and tail of a list will be indicated. For the representation of the tail NULL pointer can be used (show it on the example) but for the head different approaches are possible. Define a pointer variable to hold the starting adress of the list. Define a special header node with the same structure. In this case the next element of the header node indicates the beginning of the list. (item part is unused - a small waste) Define a header node with a separate structure which holds exactly the information required. In this case other information related to the list such as number of elements in the list can be added to the header.
struct head int length; node_type *first, *last; } typedef struct head head_type; template <class infotype> class LinkedList int length; Node<infoType> *first,*last; int insert(infotype *data) int append(infotype *data) int delete(node *ptr) } If a symmetric header is used: If a custom head is used: (length, last, first) Operations on One-way Lists and their Implementations Allocating Memory When an array is defined, storage is automatically allocated by the compiler. When you are using pointers, however you must allocate the storage. In C, two standard library functions are provided for this purpose. malloc--- allocates a single piece of storage space and leaves it uninitialized calloc() -- allocates entire set of storage locations and fills the total storage with zero char *malloc(size) -------- allocates size number of bytes and returns a pointer to the first Although it returns a character pointer it can be used to allocate space for any type of element not just characaters by applying a simple type casting Size is the value returned by the sizeof operator sizeof expression sizeof(type name)
Type casting: new_variable = (type_name) old_variable We will define a fucntion called MALLOC and use it in the algorithms #define MALLOC(x) ((x *) malloc(sizeof(x))) float *place; /* A float pointer place = (float *) malloc (sizeof(float)); place = MALLOC(float); place = new float[1]; Creating a new list 1. allocate memory for header node 2. set length to 0 3. set first and last nodes to NULL head_type *create() head_type *new_node; if (new _node = MALLOC(head_type) ) new_node ->length = 0; new_node -> first = new_node -> last = NULL; } return(new _node); /* adress of new list } Inserting a new element at the beginning of a list A) Create a storage for the new node 1. Create storage for a new list node; 2. Assign appropriate pointer values to the new node 3. Assign new values to the components of the header node. Example given at the lecture. /* create a storage for the new node node_type *alloc_node(info_type * val, node_type * ptr) node_type *new_node; if (new_node = MALLOC(node_type)) new_node -> item = val; new_node -> next = ptr;} return(new_node); }
B) Inserting a new element at the beginning of a list 1. Allocate memory for the new node and set it (if possible) 2. Link the first pointer of header to the new node (?last) 3. Increment length by 1 int insert_node (info_type *data, head_type *list) node_type *new_node; if (new _node = alloc_node(data, list->first)) list->first = new_node; /* link in new node if (list->length == 0) /* if this is the first node list-> last = new_node; /* set last pointer to new list->length ++; return(1); /* success } else return(0); /* error } Appending a new item to the end of a list Another common operation appends a new item to the end of a list. The before and after situations are diagrammed below. You know where to find the current last node in the list by means of the last pointer, which is maintained in the header block. Thus you only need to allocate memory for a new node and rearrange pointers accordingly. int append (info_type *data, head_type *list) node_type *new_node;/* append data to end of list if (new_node=alloc_node(data, NULL) /* allocation OK, append data item if (list -> length) /* if the list is not empty list->last->next = new_node; /* link in new node else /* otherwise list->first = new_node; /* set first pointer to new list -> last = new_node; /* update last pointer list -> length++; /* update list length return(1); /* success } else /* allocation problem return(0); /* error } BEFORE: AFTER:
Deleting Nodes Study yourself. Doubly Linked Lists Finding the predecessor of a node in a one-way list requires a linear search. We can add a pointer to its predecessor, to each node. It will consume more space but search will be faster. Sometimes you may need to move through a list from the end to the beginning by following pointers. Inorder to be able to do this we can define two pointers in each node; One points to the next and one points to the previous node. Length Last First Previous Item Next (HEADER ) (NODE) (a doubly linked list) For doubly linked lists, you need to develop a new set of routines that are analogous to those for ordinary (singly linked) lists. The function to create a new doubly linked list is nearly the same as that for singly linked lists, in that you have to change only the data types of the header pointers. Let us first define the doubly linked list node: struct doubly info_type *item struct doubly *next, *prev; } typedef struct doubly doubly_type; template <infotype> class doubly infotype *item doubly *next, *prev;... } Header Node: It does not need to change. We only need to change the type of pointers from node_type to doubly_type.
Operations on Doubly Linked Lists (Two-Way Lists) Create operation head_db_type *create_db() head_db_type *new_node; /* attempt to allocate memory if (new_node =MALLOC(head_db_type)) /* allocation OK, initialize the values new_node -> length = 0; new_node -> first = NULL; new_node ->last = NULL; } /* return the address of the new lıst return(new_node); } DoublyLinkedList() first = NULL; last = NULL; length = 0; } We also need to write a function which creates a new node. /* create a storage for the new doubly linked node doubly_type *alloc_db_node(val, prev, next) info_type *val; doubly_type *prev, *next; doubly_type *new_node; if(new_node=malloc(doubly_type)) new_node -> item = val; new_node -> prev = prev; new_node -> next = next; } return(new_node); } doubly( info_type *val, doubly *prv, doubly *nxt) item = val; prev = prv; next = nxt; } Using this function we can write insert and append functions: /* Insert node at the beginning of a doubly linked list
int insert_dbl ( info_type *data, head_db_type *list ) doubly_type *new_node; if (new_node = alloc_db_node(data, NULL, list->first)) if (list->length == 0) /* if this is the first node list->last = new_node; /* set the last pointer to the new else /* if not the first node link it in list->first->prev = new_node; /* set prev field of first node list->first = new_node; /*set the first pointer to the new } list->length++; return(1); /* success }else return(0); } /* error Previous field of the first node is 0 Next field of the last node is 0 (before insertion) (after insertion) Append a node to the end of the list Home study-- do it yourself
Deleting first node from a doubly linked list We assume that before calling the delete function you have checked the length of the list. The function retıurns the data field of the deleted node and frees the memory used by this Node. info_type *del_dbl(head_db_type *list) doubly_type *temp; info_type *data; /* delete first node temp= list->first; /* save the pointer data = temp->item; /* save the data list->first = temp->next; /* reassign the first pointer list->length--; /* update list length if (list->length ) /* if new list is not empty list->first->prev = NULL; /* set prev field of first item to NULL else /* otherwise list->last = NULL; /* set last pointer to NULL ; free((char )temp); /* free node storage return(data); } (before deletion ) (after deletion) LINKED LIST ADT IMPLEMENTATION IN C / List ADT Type Defintions typedef struct node void* dataptr; struct node* link; } NODE;
typedef struct int count; NODE* pos; NODE* head; NODE* rear; int (*compare) (void* argu1, void* argu2); } LIST; // Prototype Declarations LIST* createlist (int (*compare) (void* argu1, void* argu2)); LIST* destroylist (LIST* list); int addnode (LIST* plist, void* datainptr); bool removenode bool searchlist (LIST* plist, void* keyptr, void** dataoutptr); (LIST* plist, void* pargu, void** pdataout); bool retrievenode (LIST* plist, void* pargu, void** dataoutptr); bool traverse (LIST* plist, int fromwhere, void** dataoutptr); int listcount (LIST* plist); bool emptylist (LIST* plist); bool fulllist (LIST* plist); static int _insert (LIST* plist, NODE* ppre, void* datainptr); static void _delete (LIST* plist, NODE* ppre, NODE* ploc, void** dataoutptr); static bool _search (LIST* plist, NODE** ppre, NODE** ploc, void* pargu); // End of List ADT Definitions /* =============== createlist ============== Allocates dynamic memory for a list head node and returns its address to caller Pre compare is address of compare function used to compare two nodes. Post head has allocated or error returned
Return head node pointer or null if overflow LIST* createlist (int (*compare) (void* argu1, void* argu2)) // Local Definitions LIST* list; list = (LIST*) malloc (sizeof (LIST)); if (list) list->head = NULL; list->pos = NULL; list->rear = NULL; list->count = 0; list->compare = compare; } // if return list; } // createlist /* ================== addnode ================= Inserts data into list. Pre plist is pointer to valid list datainptr pointer to insertion data Post data inserted or error Return -1 if overflow 0 if successful 1 if dupe key int addnode (LIST* plist, void* datainptr) // Local Definitions bool found; bool success; NODE* ppre; NODE* ploc; found = _search (plist, &ppre, &ploc, datainptr); if (found) // Duplicate keys not allowed return (+1); success = _insert (plist, ppre, datainptr); if (!success) // Overflow return (-1); return (0); } // addnode /* =================== _insert ================== Inserts data pointer into a new node. Pre plist pointer to a valid list
ppre pointer to data's predecessor datainptr data pointer to be inserted Post data have been inserted in sequence Return boolean, true if successful, false if memory overflow static bool _insert (LIST* plist, NODE* ppre, void* datainptr) // Local Definitions NODE* pnew; if (!(pnew = (NODE*) malloc(sizeof(node)))) return false; pnew->dataptr pnew->link = datainptr; = NULL; if (ppre == NULL) // Adding before first node or to empty list. pnew->link = plist->head; plist->head = pnew; if (plist->count == 0) // Adding to empty list. Set rear plist->rear = pnew; } // if ppre else // Adding in middle or at end pnew->link = ppre->link; ppre->link = pnew; // Now check for add at end of list if (pnew->link == NULL) plist->rear = pnew; } // if else (plist->count)++; return true; } // _insert /* ================= removenode ================ Removes data from list. Pre plist pointer to a valid list keyptr pointer to key to be deleted dataoutptr pointer to data pointer Post Node deleted or error returned. Return false not found; true deleted bool removenode (LIST* plist, void* keyptr, void** dataoutptr) // Local Definitions bool found;
NODE* ppre; NODE* ploc; found = _search (plist, &ppre, &ploc, keyptr); if (found) _delete (plist, ppre, ploc, dataoutptr); return found; } // removenode /* ================= _delete ================ Deletes data from a list and returns pointer to data to calling module. Pre plist pointer to valid list. ppre pointer to predecessor node ploc pointer to target node Post dataoutptr pointer to data pointer Data have been deleted and returned Data memory has been freed void _delete (LIST* plist, NODE* ppre, NODE* ploc, void** dataoutptr) *dataoutptr = ploc->dataptr; if (ppre == NULL) // Deleting first node plist->head = ploc->link; else // Deleting any other node ppre->link = ploc->link; // Test for deleting last node if (ploc->link == NULL) plist->rear = ppre; (plist->count)--; free (ploc); return; } // _delete /* ================== searchlist ================= Interface to search function. Pre plist pointer to initialized list. pargu pointer to key being sought Post pdataout contains pointer to found data -or- NULL if not found Return boolean true successful; false not found bool searchlist (LIST* plist, void* pargu, void** pdataout) // Local Definitions bool found;
NODE* ppre; NODE* ploc; found = _search (plist, &ppre, &ploc, pargu); if (found) *pdataout = ploc->dataptr; else *pdataout = NULL; return found; } // searchlist /* ================== _search ================= Searches list and passes back address of node containing target and its logical predecessor. Pre plist pointer to initialized list ppre pointer variable to predecessor ploc pointer variable to receive node pargu pointer to key being sought Post ploc points to first equal/greater key -or- null if target > key of last node ppre points to largest node < key -or- null if target < key of first node Return boolean true found; false not found bool _search (LIST* plist, NODE** ppre, NODE** ploc, void* pargu) // Macro Definition #define COMPARE \ ( ((* plist->compare) (pargu, (*ploc)->dataptr)) ) #define COMPARE_LAST \ ((* plist->compare) (pargu, plist->rear->dataptr)) // Local Definitions int result; *ppre = NULL; *ploc = plist->head; if (plist->count == 0) return false; // Test for argument > last node in list if ( COMPARE_LAST > 0) *ppre = plist->rear; *ploc = NULL; return false; } // if while ( (result = COMPARE) > 0 ) // Have not found search argument location *ppre = *ploc; *ploc = (*ploc)->link;
} // while if (result == 0) // argument found--success return true; else return false; } // _search /* ================== retrievenode ================ This algorithm retrieves data in the list without changing the list contents. Pre plist pointer to initialized list. pargu pointer to key to be retrieved Post Data (pointer) passed back to caller Return boolean true success; false underflow static bool retrievenode (LIST* plist, void* pargu, void** dataoutptr) // Local Definitions bool found; NODE* ppre; NODE* ploc; found = _search (plist, &ppre, &ploc, pargu); if (found) *dataoutptr = ploc->dataptr; return true; } // if *dataoutptr = NULL; return false; } // retrievenode /* ================= emptylist ================ Returns boolean indicating whether or not the list is empty Pre plist is a pointer to a valid list Return boolean true empty; false list has data bool emptylist (LIST* plist) return (plist->count == 0); } // emptylist /* ================== fulllist ================= Returns boolean indicating no room for more data. This list is full if memory cannot be allocated for another node. Pre plist pointer to valid list Return boolean true if full false if room for node
bool fulllist (LIST* plist) // Local Definitions NODE* temp; if ((temp = (NODE*)malloc(sizeof(*(pList->head))))) free (temp); return false; } // if // Dynamic memory full return true; } // fulllist /* ================== listcount ================== Returns number of nodes in list. Pre plist is a pointer to a valid list Return count for number of nodes in list int listcount(list* plist) return plist->count; } // listcount /* ================== traverse ================= Traverses a list. Each call either starts at the beginning of list or returns the location of the next element in the list. Pre plist pointer to a valid list fromwhere 0 to start at first element dataptrout address of pointer to data Post if more data, address of next node Return true node located; false if end of list bool traverse (LIST* plist, int fromwhere, void** dataptrout) if (plist->count == 0) return false; if (fromwhere == 0) //Start from first node plist->pos = plist->head; *dataptrout = plist->pos->dataptr; return true; } // if fromwhere else // Start from current position
if (plist->pos->link == NULL) return false; else plist->pos = plist->pos->link; *dataptrout = plist->pos->dataptr; return true; } // if else } // if fromwhere else } // traverse /* ================== destroylist ================= Deletes all data in list and recycles memory Pre List is a pointer to a valid list. Post All data and head structure deleted Return null head pointer LIST* destroylist (LIST* plist) // Local Definitions NODE* deleteptr; if (plist) while (plist->count > 0) // First delete data free (plist->head->dataptr); // Now delete node deleteptr = plist->head; plist->head = plist->head->link; plist->count--; free (deleteptr); } // while free (plist); } // if return NULL; } // destroylist