My Notes Day 3
Linked Lists:
Series of nodes, each node has a single pointer to the next node and in the last node's case a null pointer representing that there are no more nodes in linked list.
Linked list will always maintain head and tail pointers so that insertion at either head or tail of the list is a constant time operation. Random insertion is excluded from this and will be a linear operation.
- Insertion - O(1)
- Deletion - O(n)
- Searching - O(n)
Here we chose to always maintain pointers to the nodes at the head and tail of the linked list and so performing a traditional insertion to either front or back of linked list is O(1) operation. An exception to this rule is performing insertion before a node that is neither the head nor the tail in a singly linked list. When the node we are inserting before is somewhere in the middle of the linked list (the random insertion) the complexity is O(n). In order to add before the designated node we need to traverse linked list to find that node's current predecessor.
The linked list is dynamically resized, thus it incurs no copy penalty like an array or vector which would eventually incur and insertion is O(1).
Singly linked list:
Each node that makes up singly linked list consists of a value and a reference to next node (if any) in the list.
*Insertion:
In general when people talk about the insertion with respect to linked lists of any form they implicitly refer to the adding of node to the tail of the list. When you use an API of DSA and you see a general purpose method that adds a node at the list you can assume that you are adding node to the tail of the list not head. Adding node to a singly linked list has only 2 cases:
- Head = NULL in which case the node we are adding is now both the head and the tail of the list or
- We simple need to append our node onto the end of the list updating the tail reference appropriately.
=>Algorithm
- Add(value)
- Pre: value is the value to add to the list
- Post: value has been placed at the tail of the list
- n <- node(value)
- if head = NULL
- head <- n
- tail <- n
- else
- tail.Next <- n
- tail <- n
- end if
- end Add
*Searching:
Searching a linked list is straight forward. We simply traverse the list checking the value we are looking for with the value of each node in the linked list.
=>Algorithm
- Contains(head, value)
- Pre: head is the head node in the list
- value is the value to search for
- Post: the item is either in the linked list, true; otherwise false
- n <- head
- while n != NULL && n.value != value
- n <- n.Next
- end while
- if n!= NULL
- return false
- end if
- return true
- end contains
*Deletion:
Deleting a node from a linked list is straightforward but there are a few cases we need to account for:
- The list is empty or
- the node to remove is only node in linked list or
- we are removing the head node or
- we are removing the tail node or
- the node to remove is somewhere in between head and tail or
- the item to remove doesn't exist in the linked list
The algorithm whose cases we have described will remove a node from anywhere within a list irrespective of whether node is head etc. If items will only ever be removed from the head or tail of the list then you can create much more concise algorithm.
=>Algorithm
- Remove(head, value)
- Pre: Head is the head node in the list
- value is the value to remove from the list
- Post: Value is removed from the list, true; otherwise false
- if head = NULL
- //case 1
- return false
- end if
- n <- head
- if n.value = value
- if head = tail
- //case 2
- Head <- NULL
- tail <- NULL
- else
- //case 3
- head <-head.Next
- end if
- return true
- end if
- while n.Next != NULL & n.Next.value != value
- n <- n.Next
- end while
- if n.Next != NULL
- if n.Next = tail
- //case 4
- tail <- n
- end if
- //this case exists only case 5 if conditional on line 25 was false
- n.Next <- n.Next.Next
- return true
- end if
- //case 6
- returm false
- end remove
*Traversing the list:
You start at the head of the lust and continue until you come accross that NULL. 2 cases
Case 1: node != NULL, we have exhausted all nodes in the linked list or
Case 2: We must update the node reference to be node.Next
The algorithm described is very simple one that makes use of while loop to check the first case.
=> Algorithm
- Traverse()head
- Pre: head is the node in the list
- Post: the items in the list have been traversed
- n <- head
- while n != 0
- yield n.value
- n <- n.Next
- end while
- end traverse
*Traversing the list in reverse order
Traversing a singly linked list in forward manner (left to right) is simple; if same has to be done in reverse way (right to left) This becomes O(n^2)
=> Algorithm
- Revers traversal(head, tail)
- Pre: head and tail belong to same list
- Post: the items in the list have been traversed in reverse order
- if tail != NULL
- curr<-tail
- while curr!=head
- prev <- head
- while prev.Next != curr
- prev<-prev.Next
- end while
- yield curr.value
- curr<-prev
- end while
- yield curr.value
- end if
- end reverse traversal
*Doubly linked list:
Traversal and reverse traversal is very easy in doubly linked list. Doubly linked lists are very similar to singly linked list. The only difference is that each node has a reference to both the next and previous nodes in the list. Similarly for the searching and traversal.
Summary:
Using data structures like an array would require you to specify the size upfront exceeding that size involves invoking a resizing algorithm which has linear run time. You should also use linked lists when you will only remove nodes at either the head or tail of the list to maintain a constant run time. This requires maintaining pointers to the nodes at the head and tail of the list but the memory overhead will pay for itself if this an operation you will be performing many times.
What linked lists are very good at is random insertion. Accessing nodes by index and searching, at the expense of little memory and a few more read writes you could maintain a count variable that tracks how many items are contained in the list so that accessing such a primitive property is a constant operation, you just need to update count during the insertion and deletion.
Comments
Post a Comment