0% found this document useful (0 votes)
2 views

(03) Linked Lists. Impl. Stacks With Linked Lists - Copy

The document discusses the implementation of linked lists, stacks, and queues in Java, focusing on the structure and functionality of nodes and linked lists. It explains how to add, insert, and remove nodes, as well as the advantages and disadvantages of linked lists compared to arrays. Additionally, it covers the implementation of a stack and a queue using linked lists, highlighting the importance of maintaining references to nodes during these operations.

Uploaded by

fahoodies
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views

(03) Linked Lists. Impl. Stacks With Linked Lists - Copy

The document discusses the implementation of linked lists, stacks, and queues in Java, focusing on the structure and functionality of nodes and linked lists. It explains how to add, insert, and remove nodes, as well as the advantages and disadvantages of linked lists compared to arrays. Additionally, it covers the implementation of a stack and a queue using linked lists, highlighting the importance of maintaining references to nodes during these operations.

Uploaded by

fahoodies
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
You are on page 1/ 39

Linked Lists, and

implementing stacks
and queues with it
The “Node” class, and the Singly
Linked List
public class public class LinkedList
1 next Node {
{ Node front;
6
A single node looks like int val; …..
this. Node next; }
}

front The ‘next’ is a reference (address)


1 1 3 8 null to the next node. The variable
4 1 names of objects and arrays in Java
are references.
someNode = new Node(); //A node
front = null; //When the list has no nodes and is has been made (because of ‘new’), and
someNode is a reference
empty

front
1 null A linked list with just one
element Why is the front so important, and
4 kept separately?
The “Node” class, and the Singly
Linked List
What is an array? When we do, for instance: int[] arr = new int[100] . There are 100 integers
stored in there, but there is only one variable that gives us access to them: ‘arr’ (Which stores
the reference (address) of arr[0]).
arr
21
It’s all stored in the same place in
17 memory (consecutively). So, to
access the third element, I just do:
42
arr[2]. (i.e., jump two spots forward
91 from arr).
68

front This ‘front’ is like ‘arr’. It is a variable that gives us access


14 11 3 8 null to all the elements. Unlike arrays, we can’t do front[2].
Because we make the nodes one-by-one ….. each time
we do a ‘new Node’ it could be anywhere in memory.
We need to traverse one item at a
time

front This ‘front’ is like ‘arr’. It is the variable that gives us


14 11 3 8 null access to all the elements. Unlike arrays, we can’t do
front[6]. Because we make the nodes one-by-one ….. we
need to ‘traverse’ the linked list from the start, until we
reach our desired position.

Public int getItem(int indx)


nd1 Public int printAllItems() {
Class Node { Node curr = front;
{ Node curr = front;
front int currIndx = 0;
int val; while(curr != null) while(currIndx != indx && curr !
Node { = null)
next;
nd2 {
} System.out.println(curr.val); currIndx += 1;
curr = curr.next; curr = curr.next;
} }

if(curr == null) throw ….


return curr.val;
The node class can also contain another object
Class Node
{
Front
Student
st;
Node
next;
}

The figure shows that the node object contains two references now, one to the next node,
and another to its student st.
We could also make a ‘next’ as a private member variable inside the Student class, doing
away with the need off the node class. But that is bad design, as the Student class should
represent a Student, and should be independent of how it will be implemented. ( Think about it,
if you want to represent a student, then there is no concept of ‘next’ student ).

public class However, this would be an okay design if there is some next
Student person, that (in a natural way) belongs to the class person. Such as
{ if they are standing in a row … or … if the next person is the boss
String name; of this person etc.
//etc.
Student next;
Adding a node at the end of the Singly Linked
List

front
1 1 3 8 null
4 1
What if we want to add a node, say, to the end of this linked list. Well (we will see the details
later), we (1) just make a ‘new’ node, and then (2) make the ‘next’ of the last node (the ‘8’
node above), to point to this new node.

front
1 1 3 8
If we start from front, and go till null
4 1
(traverse the list), you will see that now
our list has one more item: 23
2 null
3
Node myNewNode = new
Node();
myNewNode.val = 23;
Adding a node at the end of the Singly Linked
List

front
1 1 3 8
If we start from front, and go till null
4 1
(traverse the list), you will see that now
our list has one more item: 23
2 null
3
addToLast(Node nd)
{
nd.next = null;

Node curr = front;


if(curr == null) front = nd;

else {
while(curr.next != null) curr =
curr.next;

curr.next = nd;
}
Adding the first node in the linked list

When the list is empty, front == null. So, check this condition, and then do:

front = nd;
nd.next = null;

front
front = 2 null
null 3
Inserting after a given Class LinkedList
{
node private Node front;
public void addAtLast(Node
nd)
public void insertAfter(Node
r) …
}
1 1 3 8 1 null
front 4 1
nd
r I have a reference for the node with ‘3’
2 ?? in it, and I want to insert the new node,
1 N, just after it

1 1 3 8 1 null
front
4 1
Step1
Step2 The order of the steps is important
2 (It couldn’t have been the other
1 way round). Let’s see why.
Inserting after a given node
r
1 1 3 8 1 null
front 4 1
Step1

2 nd.next =
1 nd r.next

1 1 3 8 1 null r.next =
front 4 1 nd
Step1
Step2
2
1 nd

Does it still work if r is the last node in the list? What if it is


the first?
Inserting after a given node (be
r careful)
1 1 3 ?? 8 1 Null
front 4 1

2 r.next =
1 nd nd

If you do it in the reverse order it would not work. For example, if you first do r.next = nd
… then look at the figure above. You lost all reference to the node with ‘8’ in it. You have
no way of accessing it, as you did not save the reference (address) of that node.

If you save it first, then indeed you can do it in the reverse order ( there is no point of it
though, other than to show you an example).

Node temp = r.next; //so we are first saving the address of that ‘8’ node
r.next = nd; //And now we can do it in the reverse order … but what’s the point! It is just to show you
another way.
nd.next = temp;
Inserting after a given node
Note that insertion after a node is quite efficient (it is O(1) ), however we are assuming that
we already have a reference to the node ‘r’. If we don’t, we have to do it the long way:
Traverse the list from the start, until you reach the point where we need to insert the new
node.
In an array, inserting into the middle of a long array is never efficient. We would need to
shift many elements.
(1) (2
)
21 16 14 23 62 91 21 16 14 23 62 91 91
21 16 14 23 62 91

4 4
4 5 5
5 (3 (4
) )
21 16 14 23 62 62 91 21 16 14 23 45 62 91

4
5
Advantage and disadvantages compared to
arrays
Class Node Class LinkedList
front { {
1 1 3 8 null int val; Node front;
4 1 Node …..
next; }
}

Advantage of linked lists over arrays:


• We don’t run out of space: We don’t have to copy all items into some new array. Here,
we just keep on adding another Node if needed.
• We can add in the middle of the list quite easily (We don’t have to shift all items). But
note you should have the reference of the node, after which you want to insert. Else
you will have to ‘traverse’.

Disadvantage compared to arrays:


• Takes a bit more space (We are storing the ‘next’ references, and not just the values).
• There is no easy way to access the middle item immediately. Either you already have a
reference for it, or you need to traverse from the front, until you find it.
Implementing a stack with a linked list:
The need to add/remove at the front of a
linked list
An attempted implementation of
Push

Class Node Class Stack


1 1 3 8 null { {
front 4 1 int val; private Node front;
Node void push(int x) ….
next; void pop(int x) ….
front = Wrong! As you will } }
6 ??
lose the entire
4 nd nd;
linked list

Remember, we need to add and remove from the same end. So, we can do it
at the ‘front’.
A correct implementation of Push

1 1 3 8 null 1 1 3 8 null
front
4 1 4 1

6 Step 1 6 Step 2
4 front
n 4
n
d nd.next =
d
front;
front = nd;
You can of course do it in some other sequence, just make sure you don’t lose reference to
something you would need later. So, this is the way we can implement “push”
An implementation of pop

1 1 3 8 null 1 1 3 8 null
front 4 1 front 4 1

retVal = front.val; //storing it in retVal, as I need to return this value to the calling function (I am
popping from a stack)
front = front.next; //I have deleted the first node. (What happens if there was only one node?)
return retVal;
If I would have not saved it in ‘retVal’, then after ‘front = front.next’, I would have lost all
contact (reference) to the node which stores 14, and I am supposed to pop and return that
value.
Q: Does this code still work if the linked list contains only 1 item, and I
am popping??
Q: How would you handle pop if the stack is empty (i.e., if front
== null)?
Easy to implement a stack, if you already
have a LinkedList class
public class Stack<T>
{
LinkedList<T> list;

public T pop()
{
//I am skipping steps for exceptions etc.
list.removeFirst();
}

public T push(T element)


{
list.addFirst(element);
}

public T peek()
{
//suppose list doesn’t provide this.
//how would you do it using push and pop?
}
}
An implementation of pop

1 1 3 8 null
front 4 1

So, what happens to the node we ‘deleted’, the node with ‘14’? Well, we didn’t
actually delete it, we just made it no longer part of the sequence if we start from
‘front’ and keep going forward.

If there are no references to that node from anywhere else in the code, the Java
Garbage Collector would clean it up, and return the memory to the OS .

By the way C++ does not have a garbage collector. The programmer has to write the
code such as ‘delete(var)’ to return the memory to the OS. Disadvantage: You have to
be more careful when programming to de-allocate whatever memory you have
stopped using. Advantage: There is no garbage collector program which will halt your
program every now and then.
A tailed linked list: Works well for the queue
ADT
tail
Class Node Class TLinkedList
{ {
front int val; Node front;
Node Node tail;
1 1 3 8 null next; void enqueue(int)…
4 1 } int dequeue() ….
}

In a queue, we add and remove from opposite ends (It is First-in-First-out, FIFO). So,
a tailed linked list seems like a good way of implementing a queue.

What if we did not have ‘tail’? Then I have


no quick access to the last item. So, if I
want to remove the tail of the linked list, I
would have to start from ‘front’ and
add traverse the entire linked list, before
remove
reaching the end and removing the last
item.
A tailed linked list
tail
Note you can add a node to the tail in
front O(1) time, but you cannot remove
from the tail in O(1) time.
1 1 3 8 null
Why? Because you just don’t want
4 1 the tail, but you want the 2nd last
node too, as you need to changes its
front = ‘next’ to ‘null’. As you can’t access
null;
When the list is the 2nd last node from the tail, you
tail = null; empty would have to traverse the whole list
… hence O(n) time is required
tail
Note that front == tail when the list has just
front one item
1 null
4
Dequeuing (similar to the pop earlier)

tail

front public int removeFirst()


{
1 1 3 8 null if(front == null) return
4 1 null;

retVal = front.val;
tail front = front.next;
front return retVal;
}
1 3 8 null But wait. Is this code correct? What happens
1 if there is only one node in the linked list
(see next slide)

(But still, it works for the general case, and


we should work from the general case, and
think about the edge cases later … as we are
Dequeuing (similar to the pop of
earlier)
Public int removeFirst()
tail Public int {
front removeFirst() if(front == null) return
{ null;
1 null retVal = front.val; retVal = front.val;
4 front = front.next; front = front.next;
return retVal; if(front == null) tail =
} null
return retVal;
tail }
So, yes, it is similar to pop earlier. But for the
stack implementation we were using non-
front = tailed linked list, so we did not have to think
1 null null about the tail when we were
4
popping/pushing.

‘tail’ should not be referencing a node no longer


in the linked list. So, we must set the tail to null
as well.
Enqueuing (Adding to the tail)
tail

front 1 1 3 8 null
4 1
2
public void addLast(int
tail
3 val)
n
d {
Node nd = new
Node(val);
front 1 1 3 8
nd.next = null;
4 1 tail.next = nd;
2 ??
tail = tail.next;
3 }
n
d Remember, with a non-linked list,
adding to the tail needed a
tail
traversal, and hence it was taking
O(n) time. With a ‘tail’ we can now
do it in O(1) time.
front 1 1 3 8 2 Null
4 1 3n
d
Enqueuing (Again, we need to handle some
special cases)
Note, we need extra checks to make it work when we are enqueuing
the first item.
Null
front public void addLast(int public void addLast(int
tail Null val)
val)
{ {
Node nd = new Node nd = new
2 Node(val); Node(val);
3 nd.next = null; nd.next = null;
n
d tail.next = nd;
tail = tail.next; if(tail == Null)
There is no tail, so how {
}
can we access its ‘next’ ? front = tail = nd;
(null.next is an error). }

else
{
tail.next = nd;
tail = tail.next;
}
Easy to implement a stack, if you already
have a LinkedList class
public class Queue<T>
{
LinkedList<T> list;

public T dequeue()
{
list.removeFirst();
}

public T enqueue(T element)


{
list.addLast(element);
}

public T peek()
{
//You can easily implement the peek() inside the linked
list class, using
// ‘return front.val’ (assuming front is not null; else throw
an exception)
}
LinkedList in Java API
As you can imagine, the LinkedList is very useful, in a lot of places, so Java has a pre-built
class of LinkedList.
Just as we did with stacks, let’s read some online documentation of this class in Java.
https://docs.oracle.com/javase/8/docs/api/java/util/LinkedLi
st.html

The hierarchy: It’s parent class, grand parent, great grand


parent etc.

Note that many of the interfaces also needed to be generic


LinkedList in Java

There are many more. Such as removeFirst, removeLast, afterFirst, afterLast etc.
Using these Linked Lists
Our own linked list - We should make it a
generic class
It can be a linked list of anything.
Notice the toString() function and it’s importance.
Let’s see some code It is what is used by the System.out.println to print
…. objects.
MyLinkedList.jav
a
Node.java
Note we implemented a generic class.

Other than that it just has the functions that we talked about: addFirst, addLast,
removeFirst (It doesn’t have a removeLast, because that cannot be implemented in
O(1) time. Although we can add the O(n) algorithm for it i we wanted (The code is
for a tailed singly list).

Also note, that in the LinkedList class, there is a private member variable size, that
keeps track of the size of the LinkedList. So that we have an O(1) implementation for
getSize().
By the way, how would we remove the end
efficiently?
tail By ‘efficiently’, I mean O(1), and not O(n).
(For some problems O(n) might be very good,
but not for this one).
front 1 1 3 8 null
4 1

In our tailed linked list, we can add to the tail easily (We already did when implementing
dequeue.

But note there is no efficient way to remove the tail, because we need it to then look like
the following: tail So, we need to change the ‘next’ of the 2nd last node above
(the node storing ‘3’), to null.
The problem is, we don’t have quick access to a 2nd last node in
a singly linked list. We have quick access to the tail, but we can
front 1 1 3 null
only go forward from the tail. We also have quick access to
4 1 ‘front’, but we will have to traverse the entire list before getting
to the 2nd last node (So, it would take O(n) time).

You might think that having two tails: References to the last, as
well as the 2nd last node, might help. But it doesn’t. Why? (Try
deleting twice).
Deletion of the last element is not efficient in
a singly linked list (tailed or non-tailed)
tail
int removeLast() //With a tailed singly-
linked list
{
front 1 1 3 8 null Node curr = front;
4 1 if(curr == null) removeFirst(); //i.e., just
pop
else
{
while(curr.next != tail) curr =
curr.next();

int toRet = tail.val;


tail = curr;
curr.next = null;
return toRet;
It’s the while loop that performs the unfortunate traversal
} of the entire list. ( Note in an array,
we have direct access to every element using an index. But then arrays have their own disadvantages
too --- need to be copied to a bigger array when they get full, and not easy to delete an item from the
middle)
Deletion of the last element is not efficient
in a singly linked list
Remember, ‘addition’ of an element at the tail was indeed efficient. This tells us, if we are
implementing a queue using a tailed singly-linked list, we should add at the tail and
remove from the front (and not the other way round).

Of course, it is not efficient to implement a queue with a linked-list without tail (Because
we need to access both ends of the linked list, and in O(1) time if we want to be efficient).
We have already seen that.

In the removeFirst function, it is returning the node that is deleted (rather than just the
value of the node, the way we returned). So, it first saves the reference of this node in a
variable, before changing the links (and effectively deleting it).
Doubly Linked List in Java
It says at the
top:

Let’s see what that is, and what, if any, advantages it


has
(tailed) Doubly Linked List
Class Node Class
{ DLinkedList
1 next
pre int val; {
v 6 Node Node front;
A single node looks like next; Node tail;
this. Node }
prev;
}
front tai
l

null
null 1 1 2 3
6 1 4
A (tailed) linked list of these nodes (i.e., a tailed doubly linked list),
looks like above

front tail
When there is only one node in this liked list, ‘front’ = ‘tail’, and they
reference the only node in the list. Also, both the next and the ‘prev’
1 null of this node, are null (see the figure on the left).
null
6
Note, when there is no node in the list, we have front = null, as well
as prev = null
(tailed) Doubly Linked List –
removing Last item (the general
case)
front tai
l

null
null 1 1 2 3
6 1 4
Note, we can now efficiently remove the last item (the tail) from a
(doubly) linked list.
public int removeLast()
{
int retVal = tail.val; Where would this code throw an exception if
there is only one node in the linked list?
Node nd = tail.prev; //It is the node storing ‘2’ in the
figure above Also, if there is only one node in the linked
nd.next = null; list, what else would you have to change
tail = nd; other than ‘tail’?
return retVal;
}
(tailed) Doubly Linked List – removing last
item (the general case)
(0) (1) Node nd =
front tai front tail.prev; tai
l l

null null
null 1 1 2 3 null 1 1 2 3
6 1 4 6 1 4
nd

(2) nd.next = (3 int retVal =


null; ) front tai
front tai tail.val; nul
nul l
l l
l
1 1 2 null
1 1 2 null null 3
null 3 6 1
6 1 nd 4
nd 4 retVal =
34
(4) tail = tai
front nd; nul
l
l
1 1 2 null
null 3 Work for Java
6 1 4 Garbage
nd
Collector
(tailed) Doubly Linked List
We are not going over other functions of doubly linked list. You should know the variety exists. It is not
that hard compared to the singly linked list, though you must keep track of more references. Because
you now need to think about ‘tail’, ‘next’, and ‘front’, as we had to earlier. But now you also must
consider the ‘prev’ and maintain its values in a consistent manner (Including in the special cases)

We will give one quick example of it, and skip the rest of the details: When you delete a node nd, you
need to change the ‘next’ of the node before ‘nd’, and you need to change the ‘prev’ of the node after
nd.

front tai
l

null Node nextNode = nd.next;


null 1 1 2 3 Node prevNode =
6 1 4 nd.prev;
nextNode.prev =
prevNode;
Of course this is the ‘general’ case. If there are only prevNode.next =
one or two nodes, some part of this code would throw nextNode;
an exception.
In Summary
(1)A singly linked list (without tail) can:
(a) Add and remove items from the front in O(1) time
(b) Remove items from the middle given the reference of the node immediately
earlier

(2)A singly linked list with tail can, in addition to above, add items at the tail in O(1) time

(3)A doubly linked list with tail can, in addition to above, remove items at the tail in O(1)
time
If one decides to implement Stack using Linked List, then (1) above is good enough. But
treat the front of the list as the ‘top’ of the stack, if we want the push/pop operations to
be O(1)

If one decides to implement the queue using Linked List, then (2) above is good enough.
But we should add at the tail, and remove from the front, if we want to enqueue and
dequeue in O(1) time.

Java Linked List is a tailed doubly linked list, so that it can also remove from the tail in
O(1) time. (But a doubly linked list needs another ‘prev’ reference variable.

None of the linked list can return the element stored at an arbitrary index in O(1) time.
Other varieties
There are other varieties too. What we have seen are:

1. Singly linked list (Tailed and not tailed)


2. Doubly linked list (Tailed) – of course there can also be non-tailed doubly linked list.

Another variety is, where the ‘next’ reference of the tail goes back to the head. Of
course, you can make the following varieties now.
3. Circular linked list (Tailed and not tailed).
4. Circular doubly linked list

At any rate, the main varieties are singly and doubly linked lists (tailed and non-tailed). It’s
not about what you can make, as one can make any crazy variety ( You can make any crazy
variety – say there is a third reference, ‘next’, ‘prev’, and ‘fourAgo’, the last one pointing to the node 4
spots earlier). It is about what is useful for some given application ( Although crazy varieties
have a great use in quizzes and homeworks).

You might also like