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

5.doubley Linkedlist and Recursion

A doubly linked list contains an extra pointer called the previous pointer in addition to the next pointer and data of a singly linked list. It allows traversal in both forward and backward directions. Insertion operations are more efficient with a doubly linked list if the node to be inserted or deleted is given. Every node requires extra space for the previous pointer, and all operations require maintaining the previous pointer. A circular linked list forms a circle by connecting the last node to the first node, allowing any node to be accessed without traversing from the beginning. It is useful for queue implementation and applications that repeatedly traverse the list. Nodes can be inserted into an empty circular linked list, at the beginning, end, or between nodes by adjusting the next pointers.

Uploaded by

Patil Vinayak
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
90 views

5.doubley Linkedlist and Recursion

A doubly linked list contains an extra pointer called the previous pointer in addition to the next pointer and data of a singly linked list. It allows traversal in both forward and backward directions. Insertion operations are more efficient with a doubly linked list if the node to be inserted or deleted is given. Every node requires extra space for the previous pointer, and all operations require maintaining the previous pointer. A circular linked list forms a circle by connecting the last node to the first node, allowing any node to be accessed without traversing from the beginning. It is useful for queue implementation and applications that repeatedly traverse the list. Nodes can be inserted into an empty circular linked list, at the beginning, end, or between nodes by adjusting the next pointers.

Uploaded by

Patil Vinayak
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 35

Doubly Linked List (Introduction and Insertion)

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.

// Class for Doubly Linked List


public class DLL {
Node head; // head of list

/* Doubly Linked list Node*/


class Node {
int data;
Node prev;
Node next;

// Constructor to create a new node


// next and prev is by default initialized as null
Node(int d) { data = d; }
}
}
5 May 2022 1
Advantages over singly linked list
1) A DLL can be traversed in both forward and backward direction.
2) The delete operation in DLL is more efficient if pointer to the node to be deleted is
given.
3) We can quickly insert a new node before a given node.

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.

Disadvantages over singly linked list


1) Every node of DLL Require extra space for an previous pointer.

2) All operations require an extra pointer previous to be maintained.

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.

1) Add a node at the front: (A 5 steps process)


• The new node is always added before the head of the given Linked List.
• And newly added node becomes the new head of DLL.
• Let us call the function that adds at the front of the list is push().
• The push() must receive a pointer to the head pointer, because push
must change the head pointer to point to the new 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);

/* 3. Make next of new node as head and previous as NULL */


new_Node.next = head;
new_Node.prev = null;

/* 4. change prev of head node to new node */


if (head != null)
head.prev = new_Node;

/* 5. move the head to point to the new node */


head = new_Node;
5 May 2022 4
}
2) Add a node after a given node.: (A 7 steps process)

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)
{

/*1. check if the given prev_node is NULL */


if (prev_Node == null) {
System.out.println("The given previous node cannot be NULL ");
return;
}

/* 2. allocate node
* 3. put in the data */
Node new_node = new Node(new_data);

/* 4. Make next of new node as next of prev_node */


new_node.next = prev_Node.next;

/* 5. Make the next of prev_node as new_node */


prev_Node.next = new_node;

/* 6. Make prev_node as previous of new_node */


new_node.prev = prev_Node;

/* 7. Change previous of new_node's next node */


if (new_node.next != null)
new_node.next.prev = new_node;
5 May 2022 6
}
Circular Linked List (Introduction and Applications)

• 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.

• This problem can be solved by slightly altering the structure of a


singly linked list.

• 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.

2) Useful for implementation of queue.

3) Circular lists are useful in applications to repeatedly go around the list.


For example, when multiple applications are running on a PC, it is common for the
operating system to put the running applications on a list and then to cycle through
them, giving each of them a slice of time to execute, and then making them wait while
the CPU is given to another application. It is convenient for the operating system to
use a circular list so that when it reaches the end of the list it can cycle around to the
front of the list.

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;

// Creating a node dynamically.


Node temp = new Node();

// Assigning the data.


temp.data = data;
last = temp;
// Note : list was empty. We link single node
// to itself.
temp.next = 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);

// Creating a node dynamically


Node temp = new Node();

// Assigning the data


temp.data = data;

// Adjusting the links


temp.next = last.next;
last.next = temp;

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);

// Creating a node dynamically.


Node temp = new Node();

// Assigning the data.


temp.data = data;

// Adjusting the links.


temp.next = last.next;
last.next = temp;
last = temp;

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,

After searching and insertion:

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);

System.out.println(item + " not present in the list.");


return last;
5 May 2022 18
}
Recursion

• Recursion means "defining a problem in terms of itself".

• This can be a very powerful tool in writing algorithms.

• Recursion comes directly from Mathematics, where there are


many examples of expressions written in terms of themselves.

• For example, the Fibonacci sequence is defined as:


F(i) = F(i-1) + F(i-2)

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:

Function find_max( list )

possible_max_1 = first value in list


possible_max_2 = find_max ( rest of the list );

if ( possible_max_1 > possible_max_2 )


answer is possible_max_1
else
answer is possible_max_2
end

end

5 May 2022 21
Parts of a Recursive Algorithm

All recursive algorithms must have the following:


1.Base Case (i.e., when to stop)
2.Work toward Base Case
3.Recursive Call (i.e., call ourselves)

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));
}

// Driven Program to check above


public static void main(String args[])
{
int num = 12345;
int result = sum_of_digit(num);
System.out.println("Sum of digits in " +
num + " is " + result);
}
}

5 May 2022 23
Simple Example: Fibonacci Series

public class fib {


int fibser(int n)
{
if(n==0)
{
return 0;
}
else if(n==1 || n==2)
{
return 1;
}
else
{
return ( fibser(n-1)+fibser(n-2));
}
}

5 May 2022 24
fib fibobj = new fib();

int maxNumber = 10;

System.out.print("Fibonacci Series of "+maxNumber+" numbers: ");

for(int i = 0; i < maxNumber; i++)


{
System.out.println(fibobj.fibser(i) +" ");
}

5 May 2022 25
How a particular problem is solved using recursion?

• The idea is to represent a problem in terms of one or more


smaller problems, and add one or more base conditions
that stop the recursion.

• For example, we compute factorial n if we know factorial


of (n-1).
• The base case for factorial would be n = 0. We return 1
when n = 0.

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;

System.out.print(" " + n);

// The last executed statement


// is recursive call
print(n - 1);
}

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.

• A recursive function calls itself, the memory for a called


function is allocated on top of memory allocated to calling
function and different copy of local variables is created for
each function call.

• When the base case is reached, the function returns its


value to the function by whom it is called and memory is
de-allocated and the process continues.

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

You might also like