Linked Lists
LAST TODAY NEXT Interfaces Implementing stacks and queues Better arrays • Stacks Linked lists • Queues
How to implement queues • Data structure with a flexible size • Recursive structure • Base case: empty list • Recursive case: something followed by a list
Linked list implementation (for integers) struct list_node { int data; struct list_node* next; }; What if there was no pointer here?
Linked list implementation (for integers) struct list_node { int data; struct list_node* next; }; Let us introduce a shorter name for this structure type
Linked list implementation (for integers) typedef struct list_node list; struct list_node { int data; list* next; };
Class activity There is an embedded linked list in the class!
Fixed element type typedef struct list_node list; struct list_node { int data; list* next; };
Linked list implementation (general) typedef string elem; // This goes in the client's code typedef struct list_node list; struct list_node { elem data; list* next; }; How should we indicate the end of a linked list?
typedef struct list_node list; List segments [start, end) struct list_node { elem data; list* next; }; bool is_segment(list* start, list* end)
typedef struct list_node list; List segments [start, end) struct list_node { elem data; list* next; }; bool is_segment(list* start, list* end) { if (start == NULL) return false; recursive if (start == end) return true; implementation return is_segment(start->next, end); }
typedef struct list_node list; List segments [start, end) struct list_node { elem data; list* next; }; bool is_segment(list* start, list* end){ for (list* p = start; p != NULL; p = p->next) { iterative if (p == end) return true; implementation } return false; }
Observations about is_segment • Always returns true when [start, end) is a valid segment • Does it always return false when it is [start, end) is not a valid segment? • Only if start is NULL-terminated • Loops forever if it contains a cycle How can we detect a cycle?
Tortoise and hare bool is_acyclic(list* start); tortoise hare
Adding and removing elements
Delete (remove) from the start of a list segment Consider a non-empty list segment [start, end) elem x = start->data; start = start->next; return x; Cost: O(1)
Delete (remove) from the end of a list segment Consider a non-empty list segment [start, end) start end 3 4 5 before 3 4 after Cost: O(n)
Insertion at the start of a list segment Consider a list segment [start, end) list* l = alloc(list); l->data = x; l->next = start; start = l; Cost: O(1)
Insertion at the end of a list segment start end 3 4 5 set the data field of end 3 4 5 6 to the value to add new node set its next field to 3 4 5 6 a new dummy node end set end to it start Cost: O(1)
Insertion at the end of a list segment (code) Consider a list segment [start, end) end->data = x; list* new_dummy = alloc(list); end->next = new_dummy; end = new_dummy; Cost: O(1)
Insertion at the end of a list segment Consider the alternative sequence of steps • create a new node • set its data field to the value to add • set its next field to end • point the old last node to it •
Insertion at the end of a list segment Consider the alternative sequence of steps to add 6 start end 3 4 5 3 4 5 new node 6 Cost: O(n)
Implementing Queues with Linked Lists
Queue implementation Interface Implementation // typedef ______* queue_t; bool queue_empty(queue_t Q) /* O(1) */ /*@requires Q != NULL; @*/; Code using linked lists queue_t queue_new() /* O(1) */ /*@ensures \result != NULL; @*/ /*@ensures queue_empty(\result); @*/; void enq(queue_t Q, string e) /* O(1) */ /*@requires Q != NULL; @*/; string deq(queue_t Q) /* O(1) */ /*@requires Q != NULL; @*/ /*@requires !queue_empty(Q); @*/ ;
• Enqueue from back • Dequeue from front header { struct queue_header { list* front; list* back; };
// typedef ______* queue_t; bool queue_empty(queue_t Q) /* O(1) */ /*@requires Q != NULL; @*/; queue_t queue_new() /* O(1) */ /*@ensures \result != NULL; @*/ /*@ensures queue_empty(\result); @*/; void enq(queue_t Q, string e) /* O(1) */ /*@requires Q != NULL; @*/; string deq(queue_t Q) /* O(1) */ /*@requires Q != NULL; @*/ /*@requires !queue_empty(Q); @*/ ; Considering cost of insert and delete for linked lists what should be front and back?
Implementing queues as list segments • Insert at the back and remove from front [start, end) of a list segment becomes [front, back) of a queue
Queue implementation Interface Implementation struct queue_header { // typedef ______* queue_t; list* front; list* back; bool queue_empty(queue_t Q) /* O(1) */ }; /*@requires Q != NULL; @*/; typedef struct queue_header queue; queue_t queue_new() /* O(1) */ /*@ensures \result != NULL; @*/ typedef queue* queue_t; /*@ensures queue_empty(\result); @*/; … void enq(queue_t Q, string e) /* O(1) */ queue_t queue_new() /*@requires Q != NULL; @*/; // @ensures is_queue(\result); //@ensures queue_empty(\result); string deq(queue_t Q) /* O(1) */ /*@requires Q != NULL; @*/ … /*@requires !queue_empty(Q); @*/ ;
Representation invariant Q bool is_segment(list* start, list* end); bool is_queue(queue* Q) { return Q != NULL && is_acyclic … && is_segment … }
Detecting emptiness Interface struct queue_header { list* front; list* back; }; // typedef ______* queue_t; typedef struct queue_header queue; bool queue_empty(queue_t Q) /* O(1) */ /*@requires Q != NULL; @*/; typedef queue* queue_t; queue_t queue_new() /* O(1) */ /*@ensures \result != NULL; @*/ /*@ensures queue_empty(\result); @*/; bool queue_empty(queue* Q) void enq(queue_t Q, string e) /* O(1) */ //@requires is_queue(Q); /*@requires Q != NULL; @*/; { return Q->front == Q->back; string deq(queue_t Q) /* O(1) */ /*@requires Q != NULL; @*/ } /*@requires !queue_empty(Q); @*/ ; Cost: O(1)
Creating a new queue Interface struct queue_header { list* front; list* back; }; // typedef ______* queue_t; typedef struct queue_header queue; bool queue_empty(queue_t Q) /* O(1) */ typedef queue* queue_t; /*@requires Q != NULL; @*/; queue_t queue_new() /* O(1) */ /*@ensures \result != NULL; @*/ /*@ensures queue_empty(\result); @*/; queue* queue_new() //@ensures is_queue(\result); void enq(queue_t Q, string e) /* O(1) */ //@ensures queue_empty(\result); /*@requires Q != NULL; @*/; { string deq(queue_t Q) /* O(1) */ queue* Q = alloc(queue); /*@requires Q != NULL; @*/ list* l = alloc(list); /*@requires !queue_empty(Q); @*/ ; Q->front = l; Q->back = l; return Q; Cost: O(1) }
Implementing stacks with Linked Lists
Implementing stacks as list segments • Insert and remove from the beginning [start, end) of a list segment becomes [top, floor) of a stack
[start, end) of a list segment becomes [top, floor) of a stack
typedef struct stack_header stack; struct stack_header { list* top; list* floor; };
Linked Lists versus Arrays UNSORTED ARRAYS LINKED LISTS • self-resizing • O(1) insertion (given • O(1) access PROS right pointers) • built in support • O(1) deletion (given right pointers) • Fixed size • No built-in support CONS • O(n) insertion • O(n) access
Recommend
More recommend