Mike Lam, Professoreduc.jmu.edu/~chaoaj/cs240fall2015/slides/06-linked_lists.pdf · Doubly-Linked...

Preview:

Citation preview

CS240

Mike Lam, Professor

Linked Lists

Retrospective

● Arrays are great

– O(1) access time to any element

– Amortized O(1) insertion and removal

– Referential arrays allow arbitrary-sized objects

● There are still disadvantages

– Occasional O(n) worst-case costs

– Requires large chunks of reserved memory

– Insertion/removal in the middle is expensive

Retrospective

● Goal: Do less work when inserting and removing in themiddle of our lists

2 3 5 8

Retrospective

● Goal: Do less work when inserting and removing in themiddle of our lists

● Let's "pull apart" the array

2 3 5 8

2 3 5 8

Retrospective

● Goal: Do less work when inserting and removing in themiddle of our lists

● Let's "pull apart" the array

● And add links between all the items

2 3 5 8

2 3 5 8

2 3 5 8

Linked Lists

● This is a "linked list"

● Every item has a "next" pointer/reference

– Last item has a NULL "next" pointer

● Add and remove items by manipulating the pointers

● Keep external pointers to the beginning ("head") andend ("tail") of the list

2 3 5 8

Singly-Linked Lists

● Singly-linked list:

"a" "b" "c" NULL

head tail

single linkper node

Singly-Linked Lists

● Node:

typedef struct linknode {

data_t data;

struct linknode *next;

} linknode_t;

● Linked list:

typedef struct {

linknode_t *head;

linknode_t *tail;

size_t size;

} linklist_t;

Singly-Linked Lists

● Creating new linked-list nodes:

linknode_t* malloc_node(data_t value)

{

linknode_t *new_node = (linknode_t*)malloc(sizeof(linknode_t));

// TODO: check for null

new_node->value = value;

new_node->next = NULL;

return new_node;

}

Singly-Linked Lists

● Inserting at the head:

linknode_t *newest = malloc_node(x);

newest->next = head;

head = newest;

Singly-Linked Lists

● Inserting at the tail:

linknode_t *newest = malloc_node(x);

newest->next = NULL;

tail->next = newest;

tail = newest;

Singly-Linked Lists

● Removing from the head:

if (head == NULL)

ERROR!

old_head = head;

head = head->next;

free(old_head);

Singly-Linked Lists

● Removing from the tail:

if (tail == NULL)

ERROR

old_tail = tail;

tail = ???

● Problem: Can't access previous node

Singly-Linked Lists

● Removing from the tail:

if (tail == NULL)

ERROR

old_tail = tail;

tail = ???

● Problem: Can't access previous node

– Solution: Track previous nodes as well● (doubly-linked lists)

Challenge

● Given a singly-linked list called "data", write asnippet of code that will print out all of thevalues in the list

Singly-Linked Lists

● Insert: O(1)

– if you have a reference to the location

– O(n) if the new location is index-based or the listneeds to be sorted

● Delete: O(1)

– if you have a reference to the item

– O(n) if you have to look for it

● Indexed access or search: O(n)

Linked Stack

● Consider stack implementation using a singly-linked list

Linked Stack

● Consider stack implementation using a singly-linked list

– Insert and remove at the head

– Push, pop, and top are O(1)

Linked Queue

● Consider queue implementation using a singly-linked list

Linked Queue

● Consider queue implementation using a singly-linked list

– Insert at tail, remove from head● Can't remove from the tail!

– Enqueue, dequeue, and first are O(1)

Looking ahead

● What if we kept two pointers?

– "next" and "prev"

– This is a "doubly-linked list"

● What if tail.next pointed to the head?

– This is a "circularly-linked list"

● What if we kept multiple pointers to placesfurther down the list?

– This is a "skip list"

Doubly-Linked Lists

● Two references: prev and next

– To predecessor and successor nodes

● Allows insert and remove at both ends

– Can now implement stacks, queues, and deques

– “Deque” = “double-ended queue”

Circularly-Linked Lists

● Keep a single node reference

● Useful for round-robin scheduling

– New operation: rotate()

● Can be used to implement regular list

– No need to track both head and tail

– head = tail.next

Sentinels

● Placeholder (“fake”) nodes at head and/or tail

2head tail

head tailEmpty list:

After append(2):

2 3head tailAfter append(3):

2 3 5head tailAfter append(5):

Sentinels

● Simplifies logic of insertion and removal

void list_append(list_t *a, x){ linknode_t *new = malloc_node(x); new->prev = a->tail; new->next = NULL; if (list->head == NULL) { a->head = new; } else { a->tail->next = new; } a->tail = new;}

void list_append(list_t *a, x){ linknode_t *new = malloc_node(x); new->prev = a->tail->prev; new->next = a->tail; a->tail->prev->next = new; a->tail->prev = new;}

2 3 5head tail

head tailEmpty list:

Populated list:

Deques

● Double-ended queue

● Two sets of insert/remove methods:

– addfirst and removefirst

– addlast and removelast

● Implementation using doubly-linked list w/sentinels

Tradeoffs

● Advantages of linked lists

– Worst-case O(1) bounds● No amortized bounds

– O(1) insertions and removals at arbitrary positions● No need to shift elements● This is a HUGE advantage!

Tradeoffs

● Advantages of arrays

– O(1) access to elements by index

– Proportionally fewer actual operations● Calculation and dereference vs. memory allocation and

reference re-arranging

– Proportionally less memory usage● Both arrays and linked lists can be referential● Arrays require at most 2n space overhead, while linked

lists are at least 2n (or 3n for doubly-linked lists)

Recommended