5.doubley Linkedlist and Recursion
5.doubley Linkedlist and Recursion
A Doubly Linked List (DLL) contains an extra pointer, typically called previous
pointer, together with next pointer and data which are there in singly linked list.
In singly linked list, to delete a node, pointer to the previous node is needed. To get
this previous node, sometimes the list is traversed. In DLL, we can get the previous
node using previous pointer.
5 May 2022 2
Insertion
A node can be added in four ways
1) At the front of the DLL
2) After a given node.
3) At the end of the DLL
4) Before a given node.
5 May 2022 3
// Adding a node at the front of the list
public void push(int new_data)
{
/* 1. allocate node
* 2. put in the data */
Node new_Node = new Node(new_data);
5 May 2022 5
/* Given a node as prev_node, insert a new node after the given node */
public void InsertAfter(Node prev_Node, int new_data)
{
/* 2. allocate node
* 3. put in the data */
Node new_node = new Node(new_data);
• Circular linked list is a linked list where all nodes are connected to form a circle.
• There is no NULL at the end.
• A circular linked list can be a singly circular linked list or doubly circular linked list.
5 May 2022 7
Circular Linked List (Introduction and Applications)
Why Circular?
• In a singly linked list, for accessing any node of the linked list, we
start traversing from the first node.
• If we are at any node in the middle of the list, then it is not
possible to access nodes that precede the given node.
• In a singly linked list, the next part (pointer to next node) of last
node is NULL.
• If we utilize this link to point to the first node, then we can reach
the preceding nodes.
5 May 2022 8
Implementation
• To implement a circular singly linked list, we take an external pointer that
points to the last node of the list.
• If we have a pointer last pointing to the last node, then last -> next will
point to the first node.
5 May 2022 9
Advantages of Circular Linked Lists:
1) Any node can be a starting point. We can traverse the whole list by starting from
any point. We just need to stop when the first visited node is visited again.
4) Circular Doubly Linked Lists are used for implementation of advanced data
structures like Fibonacci Heap.
5 May 2022 10
Circular Singly Linked List | Insertion
Insertion
A node can be added in three ways:
•Insertion in an empty list
•Insertion at the beginning of the list
•Insertion at the end of the list
•Insertion in between the nodes
Insertion in an empty List
Initially, when the list is empty,
the last pointer will be NULL.
5 May 2022 11
After insertion, T is the
last node, so the
After inserting node T, pointer last points to
node T.
And Node T is the first
and the last node, so
T points to itself.
static Node addToEmpty(Node last, int data)
{
// This function is only for empty list
if (last != null)
return last;
return last;
}
5 May 2022 12
Insertion at the beginning of the list
To insert a node at the beginning of the list,
follow these steps:
1. Create a node, say T.
2. Make T -> next = last -> next.
3. last -> next = T.
After insertion:
5 May 2022 13
static Node addBegin(Node last, int data)
{
if (last == null)
return addToEmpty(last, data);
return last;
}
5 May 2022 14
Insertion at the end of the list
To insert a node at the end of the list, follow
these steps:
1. Create a node, say T.
2. Make T -> next = last -> next;
3. last -> next = T.
4. last = T.
After insertion:
5 May 2022 15
static Node addEnd(Node last, int data)
{
if (last == null)
return addToEmpty(last, data);
return last;
}
5 May 2022 16
Insertion in between the nodes:
To insert a node in between the two nodes,
follow these steps:
1. Create a node, say T.
2. Search for the node after which T needs
to be inserted, say that node is P.
3. Make T -> next = P -> next;
4. P -> next = T.
Suppose 12 needs to be inserted after the
node has the value 10,
5 May 2022 17
static Node addAfter(Node last, int data, int item)
{
if (last == null)
return null;
Node temp, p;
p = last.next;
do
{
if (p.data == item)
{
temp = new Node();
temp.data = data;
temp.next = p.next;
p.next = temp;
if (p == last)
last = temp;
return last;
}
p = p.next;
} while(p != last.next);
5 May 2022 19
• Recursion is the process of defining a problem (or the solution to a problem) in
terms of (a simpler version of) itself.
For example, we can define the operation "find your way home" as:
1.If you are at home, stop moving.
2.Take one step toward home.
3."find your way home“
Here the solution to finding your way home is two steps (three steps). First, we
don't go home if we are already home. Secondly, we do a very simple action that
makes our situation simpler to solve. Finally, we redo the entire algorithm.
The above example is called tail recursion. This is where the very last statement is
calling the recursive algorithm. Tail recursion can directly be translated into
loops.
5 May 2022 20
Another example of recursion would be finding the maximum value in a list of numbers. The
maximum value in a list is either the first number or the biggest of the remaining numbers.
Here is how we would write the pseudocode of the algorithm:
end
5 May 2022 21
Parts of a Recursive Algorithm
The "work toward base case" is where we make the problem simpler (e.g., divide list into
two parts, each smaller than the original).
The recursive call, is where we use the same algorithm to solve a simpler version of the
problem.
The base case is the solution to the "simplest" possible problem (For example, the base
case in the problem 'find the largest number in a list' would be if the list had only one
number... and by definition if there is only one number, it is the largest).
5 May 2022 22
Simple Example: Sum of digits
class sum_of_digits
{
// Function to check sum
// of digit using recursion
static int sum_of_digit(int n)
{
if (n == 0)
return 0;
return (n % 10 + sum_of_digit(n / 10));
}
5 May 2022 23
Simple Example: Fibonacci Series
5 May 2022 24
fib fibobj = new fib();
5 May 2022 25
How a particular problem is solved using recursion?
5 May 2022 26
Why Stack Overflow error occurs in recursion?
• If the base case is not reached or not defined, then the stack overflow
problem may arise.
• Let us take an example to understand this.
int fact(int n)
{ // wrong base case (it may cause
// stack overflow).
if (n == 100)
return 1;
else
return n*fact(n-1);
}
If fact(10) is called, it will call fact(9), fact(8), fact(7) and so on but the number
will never reach 100. So, the base case is not reached. If the memory is
exhausted by these functions on the stack, it will cause a stack overflow
error.
5 May 2022 27
What is the difference between direct and indirect recursion?
• A function fun is called direct recursive if it calls the same function fun.
• A function fun is called indirect recursive if it calls another function say
fun_new and fun_new calls fun directly or indirectly.
5 May 2022 28
Tail Recursion :
What is tail recursion?
A recursive function is tail recursive when a recursive call is the last thing
executed by the function.
// An example of tail recursive function
static void print(int n)
{
if (n < 0)
return;
5 May 2022 29
Why do we care?
• The tail recursive functions considered better than non tail recursive
functions as tail-recursion can be optimized by the compiler.
• Compilers usually execute recursive procedures by using a stack.
• This stack consists of all the pertinent information, including the
parameter values, for each recursive call.
• When a procedure is called, its information is pushed onto a stack, and
when the function terminates the information is popped out of the
stack.
• Thus for the non-tail-recursive functions, the stack depth (maximum
amount of stack space used at any time during compilation) is more.
• The idea used by compilers to optimize tail-recursive functions is
simple, since the recursive call is the last statement, there is nothing left
to do in the current function, so saving the current function’s stack
frame is of no use
5 May 2022 30
How memory is allocated to different function calls in
recursion?
• When any function is called from main(), the memory is
allocated to it on the stack.
5 May 2022 31
// A Java program to demonstrate working of
// recursion
class GFG {
static void printFun(int test)
{
if (test < 1)
return;
else {
System.out.printf("%d ", test);
printFun(test - 1); // statement 2
System.out.printf("%d ", test);
return;
}
}
// Driver Code
public static void main(String[] args)
{
int test = 3;
printFun(test);
}
5 May 2022 32
}
• When printFun(3) is called from main(), memory is allocated
to printFun(3) and a local variable test is initialized to 3 and
statement 1 to 4 are pushed on the stack as shown in below
diagram.
It first prints ‘3’.
• In statement 2, printFun(2) is called and memory is allocated
to printFun(2) and a local variable test is initialized to 2 and
statement 1 to 4 are pushed in the stack.
• Similarly, printFun(2) calls printFun(1) and printFun(1) calls
printFun(0).
printFun(0) goes to if statement and it return to printFun(1).
• Remaining statements of printFun(1) are executed and it
returns to printFun(2) and so on.
• In the output, value from 3 to 1 are printed and then 1 to 3 are
printed.
5 May 2022 33
5 May 2022 34
What are the disadvantages of recursive programming over iterative
programming?
• Note that both recursive and iterative programs have the same
problem-solving powers, i.e., every recursive program can be written
iteratively and vice versa is also true.
• The recursive program has greater space requirements than iterative
program as all functions will remain in the stack until the base case is
reached.
• It also has greater time requirements because of function calls and returns
overhead.
What are the advantages of recursive programming over iterative
programming?
• Recursion provides a clean and simple way to write code.
• Some problems are inherently recursive like tree traversals, Tower of Hanoi,
etc.
• For such problems, it is preferred to write recursive code.
• We can write such codes also iteratively with the help of a stack data
structure.
5 May 2022 35