Sentinel approach with Doubly Linked List - java

I am going through the doubly linked list in Java, I am reading about Sentinels in the doubly linked list from Book. Which states that
In order to avoid some special cases when operating near the
boundaries of a doubly-linked list, it helps to add special nodes at
both ends of the list: a header node at the beginning of the list, and
a trailer node at the end of the list. These “dummy” nodes are known
as sentinels (or guards), and they do not store elements of the
primary sequence
What are those special cases? Why we need Sentinels approach? Is it compulsory? If we use the normal approach (without sentinels) for doubly linked list wouldnt that save the memory of these extra nodes? When making double linked list with circularly approach in that way we have to remove sentinels?

The Wikipedia notes briefly mention using a sentinel node to simplify the implementation of linked lists.
A sentinel node is a dummy node that goes at the front of a list.
In a doubly-linked list, the sentinel node points to the first and last elements of the list. We no longer need to keep separate pointers for the head and tail of the list, like we had to do with singly-linked lists.
We also do not have to worry about updating the head and tail pointers, since as we shall see, this happens automatically if we insert after a sentinel node, hence prepending an item to the list, or insert before a sentinel node, hence appending an item to the list.
We could eliminate the container object that we used for singly linked lists, since the sentinel node can keep track of both the first and last elements in the list. If we did so, then we would return a pointer to the sentinel node to the user.
However, data structures are generally designed with a container object that mediates the communication between the user of the data structure and the implementation of the data structure, so we will retain the container object.
An answer by #6502 on How does a sentinel node offer benefits over NULL? is very helpful.
The following is the code for node deletion in a doubly-linked list of nodes where NULL is used to mark the end of the list and where two pointers first and last are used to hold the address of first and last node:
// Using NULL and pointers for first and last
if (n->prev) n->prev->next = n->next;
else first = n->next;
if (n->next) n->next->prev = n->prev;
else last = n->prev;
and this is the same code where instead there is a special dummy node to mark the end of the list and where the address of first node in the list is stored in the next field of the special node and where the last node in the list is stored in the prev field of the special dummy node:
// Using the dummy node
n->prev->next = n->next;
n->next->prev = n->prev;
The same kind of simplification is also present for node insertion; for example to insert node n before node x (having x == NULL or x == &dummy meaning insertion in last position) the code would be:
// Using NULL and pointers for first and last
n->next = x;
n->prev = x ? x->prev : last;
if (n->prev) n->prev->next = n;
else first = n;
if (n->next) n->next->prev = n;
else last = n;
and
// Using the dummy node
n->next = x;
n->prev = x->prev;
n->next->prev = n;
n->prev->next = n;
As you can see the dummy node approach removed for a doubly-linked list all special cases and all conditionals.

Related

Why am i entering into an infinite loop when partitioning a linked list?

This is a common problem of partitioning a linked list into two parts. The list with nodes smaller than x will come first and the list of nodes larger than x will come after.
My question is - why do i need to set after.next to null after creating my two separate linked lists? Without setting it to null, I enter an infinite loop when trying to print this list.
My debugger shows that before_head.next has a never-ending list of nodes attached to it...
public Node partition(Node head, int x){
Node before_head=new Node(0);
Node before=before_head;
Node after_head=new Node(0);
Node after=after_head;
while(head != null){
if(head.val < x){
before.next=head;
before=before.next;
else{
after.next=head;
after=after.next;
}
head=head.next;
}
after.next=null;
before.next=after_head;
return before_head.next;
}
why do i need to set after.next to null after creating my two separate linked lists?
The last node of a linked list doesn't have a next node. In a linked list representation such as yours, that takes the form of the last node's next reference being null.
You are rearranging the existing nodes of the list by changing their next references. At the end of that process, after is a reference to the last node, therefore its next reference needs to be null. If it was also the last node in the original order then all is well -- its next reference is already null. If after was not the last node in the original order, however, then after.next will refer to one of the other nodes until you set it null. And whatever other node that is, it comes before after in the new order, forming a loop.
Note also that
before.next=after_head;
appears to be wrong. after_head is the dummy head node of the second partition, so you do not want to include it in the new list. I think you want
before.next = after_head.next;

Deleting node in the middle and at the end

I was simply wondering if the following is the right way to go about it deleting nodes in specific locations in a linked list.
So if my "target" node is located between the head and the tail (middle):
1. set a "curr" node equal to the head
2. Iterate using "curr = curr.next" until curr.next = "target"
3. set curr.next = curr.next.next
If my target is located at the tail I do:
1. set "curr" node equal to the head
2.Iterate until curr.next.next = null
3. set curr.next = null
I also struggle to understand how changing a "curr" node i set equal to "head" can modify the actual linked list associated with "head" and not just the linked list attached to "curr".
Thank you I really need help with this:)
curr references the actual links in the linked list, not just copies of them, so modifying curr also modifies the LinkedList. This is because all java objects are passed by reference.
By the way I don't think thats the right way to distinguish the last item, should be like:
set a "curr" node equal to the head
Iterate using "curr = curr.next" until curr.next .value equals target
if target was found (the previous loop actually fails if target is not in the list)
3.1. if curr.next is the last item: curr.next = null
3.2. else curr.next = curr.next.next
This algorithm is more similar to real linkedlists because usually you don't know in advance if the target link is going to be the last one or not (or even if it's in the list)

Find the 3rd last element in the Singly Link List - Algorithm

I am thinking on the Algo to find the 3rd last element in the Singly Link List and I come up with one by myself(space in-efficient)
Put the Link List in a ArrayList using a loop with O(n)time complexity [a lot of space complexity]
then find the size of Arraylist and retrieve the element[required element] at (size-2) index location
Please guide me if my algo make sense
FYI
Other I searched is :
Put two pointers and keep 1st pointer on 1st element and 2nd pointer on 3rd element and move them parallel
When the second pointer reaches the end of LinkList, retrieve the node[required node] which is pointed by the 1st pointer
Use two pointers: pointer-1 and pointer-2
make pointer-1 points to third node in single linked list.
pointer-1 = node1->next->next; // point to 3rd nd
node1-->node2-->node3-->node4---> ...... -->node-last
^
|
pointer-1
Now set pointer-2 points to first-node
pointer-2 = node1; // point to 1st nd
node1-->node2-->node3-->node4---> ...... -->node-last
^ ^
| |
pointer-2 pointer-1
Now, travel in linked list in a loop till pointer-1 points to last node, in loop also update pointer-2 (every time to next node of itself )
while (pointer-1->next!=NULL){// points to last node
pointer-1 = pointer-1 -> next;
pointer-2 = pointer-2 -> next;
}
When loop ends pointer-1 points to last-node, pointer-2 points to third last
node1-->node2-->........ ->node-l3-->node-l2-->node-last
^ ^
| |
pointer-2 pointer-1
It works in O(n) times, where n is number of nodes in linked-list.
Since the linked list is singly linked, you need to traverse it from the beginning to end to know what the 3rd to last element is, os O(n) time is unavoidable (unless you maintain a pointer to the 3rd to last element in your linked list).
Your 2 pointers idea, will use constant space. This is probably the better option, since creating an ArrayList will have more overhead, and use more space.
Get two pointers. pA, pB
Move pA to 3 elements advance and pB to the head:
1->2->3->4-> .... ->99->100
| |
pB pA
After this, move both pointers in the same loop till pA reaches to the last element.
At that point pB will be pointing to the last 3rd element
1->2->3->4-> .... ->98->99->100
| |
pB pA
Edited:
traversal will be one:
while(pA->next!=null){
pA = pA->next;
pB = pB->next;
}
Here pB will be 3rd last
An alternative view to solve this even though it is O(n)
Create an empty array(n), start a pointer into this array at 0, and start from the beginning of the linked list as well. Every time you visit a node store it in the current index of the array and advance the array pointer. Keep filling the nodes in the array, and when you reach the end of the array please start from the beginning again so as to overwrite.
Now once you end the final end of the list the pointer nth elemtn from end :)
In practice, a Java LinkedList keeps track of its size, so you could just go to 'list.get(list.size()-2)', but with a constructed linked list, I'd do the following:
Keep an array of 3 values. As you traverse the list, add the update the value at array[i%3]. When there are no more values, return the value at array[(i-2)%3].
The first thing I'd do is ask you to reconsider your use of a singly linked list. Why not just store your data in an ArrayList? It does everything you seem to want, and it's a builtin so it's relatively efficient compared to what most people would write.
If you were bound and determined to use a linked list, I'd suggest keeping a direct pointer to the 3rd last element (tle). Insertions are easy - if they come before the tle, do nothing; if after, advance the tle pointer to the tle's next. Removals are more of a hassle, but probably not most of the time. If the removal occurs before the tle, it's still the tle. The annoying situation is if the element to be removed is the tle or later, in which case you get to iterate through from the beginning until the next() reference of the current node is the tle. The current node will become the new tle, and you can then proceed with the removal. Since there are only three nodes which invoke this level of work, you'll probably do all right most of the time if the list is sizable.
A final option, if removals from the end are a frequent occurrence, is to maintain your list from back to front. Then the tle becomes the third from the front for maintenance purposes.
//We take here two pointers pNode and qNode both initial points to head
//pNode traverse till end of list and the qNode will only traverse when difference
// between the count and position is grater than 0 and pthNode increments once
// in each loop
static ListNode nthNode(int pos){
ListNode pNode=head;
ListNode qNode=head;
int count =0;
while(qNode!=null){
count++;
if(count - pos > 0)
pNode=pNode.next;
qNode=qNode.next;
}
return pNode;
}

Adding a linked list to another in O(1)

Two Singly linked Lists, size m , r and want to insert the first linked list nodes after the head of the second linked list, and the time complexity has to be O(1) of the method.
This really an intereseting difficult problem for me. Eatch time I think of a solution, the Time complexity is O(m+r)
I need some hints to solve this. I consumed useless effort on this problem.
EDIT:
Let me share what I have so far:
Create a new Linked List
Add the HEAD of the 2nd list
Still O(1)
Add all the nodes of 1st list
Becomes (n)
Add the rest of the nodes from the 1st list
Becomes another (n-1)
UPDATE:
What do you think about this? I got inspired directly after I asked here :)
If you have two singly-linked lists and don't have the tail of the first already, this is only possible in O(n). If you have the tail you simply make it point to the head of the second list...
Edit: 2nd list head points to first list's head. Hold a reference to 2nd list's 2nd node. Iterate down first list - again this is O(n) if you don't have a reference to the tail to start - and have the tail of that point to the original 2nd element of the 2nd list.
Assuming you have these structures:
List
Head node
Tail node
Node
Value
Next node
Reminder to self: the goal is: "insert the first linked list nodes after the head of the second linked list".
Then all you've got to do is:
// Hook up the end of list1 to the original second element of list2
list1.tail.next = list2.head.next;
// Set the second element of list2 to be the first element of list1
list2.head.next = list1.head;
List2 still ends where it did before (its tail node is the same).
You've now got list1 with a "floating" head, which is generally bad news... but if you iterate over list1 you'll get all the elements from both original lists...

Question about recursive implementation of reversing a singly linked list

If I have created a linked list where order of insertion is 5,4,3. I use the head reference so the linked list gets stored as 3->4->5->null.
When I want to reverse the linked list == original order of insertion so it will be
5->4->3->null. Now if this is how my new list looks like, which node should my head reference be referring to so the new elements I add to the list will still have O(1) insertion time?
I think head, by definition, always points to the first element of a list.
If you want to insert to the end of a linked list in O(1) then keep two references, one to the first element and one to the last element. Then adding to the end is done by following the last reference to the last element, add the new element beyond the last element, update the last reference to point to the new last element.
Inserting to an empty list becomes a special case because you have to set both first and last references not just the last reference. Similarly for deleting from a list with one element.
If you want to the back of a singly linked list, you should keep a reference to the last element. When you want to insert a new element, you create a new link object with the new element as head, the tail of the element you insert it after as the tail, and the new link object as the new tail of the element you insert it afterward. This takes three pointer movements and thus constant time.
For any linked list to have an O(1) insertion time, you have to insert into the front of the list or some other arbitrary location completely disregarding any order. Being a singly linked list means that you can't point to the last element in the list because the list up until the last element will be inaccessible. A fix to this might be as Shannon Severance has stated in that you keep two pointers, a head and a tail. You can use the head to access the elements and the tail to arbitrarily add elements to the list.
Think of it this way:
The reverse of a list consisting of HEAD -> [the rest of the list] is precisely: reverse([the rest of the list]) -> HEAD.
Your base case would be if the list contains a single element. The reverse of such a list is just itself.
Here's an example (using executable pseudo-code, aka Python):
class Node(object):
def __init__(self, data, next_=None):
self._data = data
self._next = next_
def __str__(self):
return '[%s] -> %s' % (self._data,
self._next or 'nil')
def reverse(node):
# base case
if node._next is None:
return node
# recursive
head = Node(node._data) # make a [HEAD] -> nil
tail_reverse = reverse(node._next)
# traverse tail_reverse
current = tail_reverse
while current._next is not None:
current = current._next
current._next = head
return tail_reverse
if __name__ == '__main__':
head = Node(0,
Node(1,
Node(2,
Node(3))))
print head
print reverse(head)
Note that this is not in O(1) due to the lack of a last-element reference (only HEAD).

Categories

Resources