Lesson 8: Linked List Deque A linked list deque will combine features that we have examined in previous lessons. The deque will maintain a pointer to both the front and the back, use double links, and once again make use of a sentinel. But this time will have a sentinel at both the head and tail of the list: tail = head = (Sentinel) 3 7 4 1 (Sentinel) A deque, you recall, allows insertions at both the beginning and the end of the container. The interface is shown at right. Sentinels ensure the list is never completely empty. They also mean that all instructions can be described as struct ListDeque { struct DLink *head; /* Sentinel at front of list. */ struct DLink *tail; /* Sentinel at back of list. */ int cnt; /* Number of data elements in list. */ ; void initlistdeque(struct ListDeque *d); int isemptylistdeque(struct ListDeque *d); void addfrontlistdeque(struct ListDeque *d, TYPE v); void addbacklistdeque(struct ListDeque *d, TYPE v); void removefrontlistdeque(struct ListDeque *d); void removebacklistdeque(struct ListDeque *d); TYPE frontlistdeque(struct ListDeque *d); TYPE backlistdeque(struct ListDeque *d); special cases of more general routines. Because we are using a sentinel on both ends (at the front, or head, and at the end, or tail, of the list), both additions to the front and additions to the back can be described as insert a new link immediately before an existing link, where the existing link is either the link after the current front of the list (i.e., head->next) or the end of the list (or tail). The use of the previous pointer allows us to find the element prior to a link. Similarly, removal at both ends can be implemented by a single method, _removelink., that removes the link after the front (head->next) for removefront and before the end (tail->prev) for removeback. Both _addlink and _removelink use the underscore convention to indicate they are internal methods i.e., they are not declared in the header file and are not meant to be called directly from the outside world. When a value is removed from a list make sure you free the associated link fields. Use an assertion to check that allocations of new links are successful, and that you do not attempt to remove a value from an empty list. Finish the implementation of the ListDeque based on these ideas: An Active Learning Approach to Data Structures using C 1
struct DLink { TYPE val; struct DLink *next; struct DLink *prev; ; struct ListDeque { struct DLink *head; /* Sentinel at front of list. */ struct DLink *tail; /* Sentinel at back of list. */ int cnt; /* Number of data elements in list. */ ; void initlistdeque(struct ListDeque *q) { q->head = (struct DLink *)malloc(sizeof(struct DLink)); assert(q->head!= 0); q->tail = (struct DLink *)malloc(sizeof(struct DLink)); assert(q->tail!= 0); q->head->prev = 0; q->head->next = q->tail; q->tail->next = 0; q->tail->prev = q->head; q->cnt = 0; /* Add before the link, l*/ void _addlink(struct DLink *l, TYPE v) { struct Dlink *newlink = (struct Link *) malloc(sizesof(struct DLink)); assert(newlink!= 0); newlink->val = v; newlink->next = l; newlink->prev = l->prev; l->prev->next = newlink; l->prev = newlink; void addfrontlistdeque(struct ListDeque *q, TYPE v) { _addlink(q->head->next, v); q->cnt++; void addbacklistdeque(struct ListDeque *q, TYPE v) { _addlink(q->tail, v); q->cnt++; An Active Learning Approach to Data Structures using C 2
void _removelink(struct DLink *l) { l->next->prev = l->prev; l->prev->next = l->next; free(l); void removefrontlistdeque(struct ListDeque *q) { assert(!isemptylistdeque(q)); _removelink(q->head->next); q->cnt--; void ListDequeRemoveBack(struct ListDeque *q) { assert(! ListDequeIsEmpty(q)); _removelink(q->tail->prev); q->cnt--; TYPE frontlistdeque(struct ListDeque *q) { assert(!isemptylistdeque(q)); return q->head->next->value; TYPE backlistdeque(struct ListDeque *q) { Assert(!isEmptyListDeque(q)); Return q->tail->prev->value; int isemptylistdeque(struct ListDeque *q) { return(q->cnt == 0)); An Active Learning Approach to Data Structures using C 3
On Your Own 1. Explain how the use of a tail sentinel allows us to view insertions at both the front and the back of the deque as special cases of the same operation. What is this operation? Since we have a tail, we can simply insertbefore it since we ll always have it. The tail also guarantees that we always have a next for the head, even when the list is empty, so we can always insert in front of head->next(); 2. What are the algorithmic time complexities of the deque operations in this implementation? They are all O(1) 3. Suppose you wanted to test your implementation of the linked list deque. What would be good boundary test cases? Write a test harness to execute the linked list deque using your test cases. As usual, you would want to test addfront, removefront, addback and removeback for empty lists as well as lists with at least one element in them. You would also want to test front() and back() with empty lists as well as lists with at least one element in them. You would also want to write test where you put several elements in the list, remove them all and then try to perform each of the above operations. An alternative to the use of both a head and tail sentinel is the use of a single sentinel value for both positions. This is shown in the following illustration. Now there are no null pointers whatsoever, as every link points to another link. Insertions and removals from the front of the deque are performed to one side of the sentinel, while insertions and removals from the back are performed on the opposite side. This structure is termed a circular deque. An Active Learning Approach to Data Structures using C 4
(Sentinel) An Active Learning Approach to Data Structures using C 5