0% found this document useful (0 votes)
20 views73 pages

Ds Lab File

Uploaded by

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

Ds Lab File

Uploaded by

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

Data structures (SE201)

LAB MANUAL
Submitted by:
Akshath Dubey
2K20/EN/11

Submitted to:
Ms. Shweta Meena
Assistant Professor
Department of Software Engineering
Delhi Technological University
Shahbad Daulatpur, Main Bawana Road, Delhi-110042
Novemeber, 2022

INDEX

S. No. Experiment Date Signature


Write a program to implement
1 8/8/2022
Selection Sort.

Write a program to implement Bubble


2 8/8/2022
Sort.

Write a program to implement


3 8/8/2022
Insertion Sort.

Write a program to perform different


4 22/8/2022
operations on stack as an array.
Write a program to implement various
5 5/9/2022
operations on a singly linked list.

Write a program to perform basic


6 12/9/2022
operations on stack stored as linked list.

Write a program to perform various


7 19/9/2022
operations on doubly linked list.

Write a program to perform various


8 10/10/2022
operations on an array.

Write a program to find the equivalent


9 postfix expression for given infix 17/10/2022
expression.
Write a program to find the equivalent
10 prefix expression for given infix 31/10/2022
expression.

Write a program to perform following


11 7/11/2022
various on binary search tree.

EXPERIMENT-1

• Program Objective: Write a program to implement Selection Sort.

• Introduction: The selection sort algorithm sorts an array by repeatedly


finding the minimum element (considering ascending order) from the
unsorted part and putting it at the beginning. The algorithm maintains two
subarrays in a given array:
• The subarray which is already sorted.
• The remaining subarray which is unsorted.
In every iteration of the selection sort, the minimum element from the
unsorted subarray is picked and moved to the sorted subarray.
• Algorithms:
• Initialise minimum value(min_idx) and step variables to location 0.
• Traverse the array from the location i = step +1 till the end to find the
minimum element in the array.
• While traversing, if any element smaller than array[min_idx] is found
then update min_idx to that element’s location.
• Swap elements present at min_idx and step.
• Increment step and min_idx by 1.
• Repeat steps 2 to 5 till step = array size – 1.

• Code:
#include <iostream>
using namespace std;
// function to print an array
void printArray(int *arr, int size) {
for (int k=0; k<size; k++){
cout<<arr[k]<<" ";
}
}
// selection sort function
void selectionSort(int *arr, int size) {
for (int step=0; step<size; step++) {
int min_idx=step;
for (int i=step+1; i<size; i++) {
if (arr[i]<arr[min_idx]) {
min_idx=i;
}
}
int temp=arr[step];
arr[step]=arr[min_idx];
arr[min_idx]=temp;
}
}
// driver code
int main()
{
int size;
cout<<"Enter size of array : "<<endl;
cin>>size;
int *arr = new int[size];
cout<<"Enter elements"<<endl;
for (int j=0; j<size; j++) {
cin>>arr[j];
}
selectionSort(arr, size);
cout<<"Sorted array is : "<<endl;
printArray(arr, size);
return 0;
}

• Output:
• Learning: Amongst various sorting techniques, we learnt the
implementation of selection sort in this experiment. Since we used 2 nested
loops in the code, we can say that the time complexity of selection sort is
O(N2) (same in all cases since checking all elements is compulsory) and the
only extra memory required for the implementation is that taken up by
temporary variables, hence space complexity becomes O(1). We can
conclude that selection sort can be used when a small list is to be sorted
and where the cost of swapping doesn’t matter.
EXPERIMENT-2

• Program Objective: Write a program to implement Bubble Sort.

• Introduction: Bubble sort is a simple comparison-based algorithm which


works by repeatedly swapping the adjacent elements if they are in the
wrong order. After each iteration, the largest element among the unsorted
elements is placed at the end. A real-world example of a bubble sort
algorithm is how the contact list on our phones is sorted in an alphabetical
order or the sorting of files according to the time they were added.

• Algorithms:
• Set i and j = 0.
• Traverse the array using j till j = array size-i-2.
• During the traversal, if array[j]>array[j+1], swap these adjacent
elements.
• Increment i by 1.
• Repeat steps 2 to 4 till i = array size -1.

• Code:
#include <iostream>
using namespace std;
// function to print an array
void printArray(int *arr, int size) {
for (int k=0; k<size; k++){
cout<<arr[k]<<" ";
}
}
// bubble sort function
void bubbleSort(int *arr, int size) {
for (int i=0; i<size; i++) {
for (int j=0; j<size-i-1; j++) {
if (arr[j]>arr[j+1]) {
int temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}

}
}
// driver code
int main()
{
int size;
cout<<"Enter size of array : "<<endl;
cin>>size;
int *arr = new int[size];
cout<<"Enter elements"<<endl;
for (int j=0; j<size; j++) {
cin>>arr[j];
}
bubbleSort(arr, size);
cout<<"Sorted array is : "<<endl;
printArray(arr, size);
return 0;
}

• Output:
• Learning: Just like selection sort, the time complexity of the above
implemented bubble sort is also O(N2) in all cases. But we can optimize
bubble sort by stopping the algorithm incase the inner loop doesn’t cause
any swap i.e., when bubble sort is applied to an already sorted array, the
time complexity in this case would be reduced to O(N). The extra memory
required is only for storing temporary variables, hence space complexity is
O(1).
EXPERIMENT-3

• Program Objective: Write a program to implement Insertion Sort.

• Introduction: In insertion sort, the array is virtually split into a sorted and
an unsorted part. Values from the unsorted part are picked and placed at
their correct position in the sorted part. With each iteration, the sorted
portion increases in size whereas the unsorted portion reduces. We start by
assuming that first element is already sorted, move to the first element of
the unsoted portion and insert it on its right place in sorted subarray and so
on with the remaining elements.

• Algorithms:
• Mark first element as sorted.
• Set step = 1.
• Extract the element present at step position and store it in key.
• Traverse the sorted subarray using j starting from its last index till 0. If
the sorted element is greater than key, shift its position to right by 1.
• Repeat step 4 untill we find an element smaller than key value
present in the sorted portion.
• Place key at the position j+1.
• Increment step by 1.
• Repeat steps 3 to 7 till step = array size-1.

• Code:
#include <iostream>
using namespace std;
// function to print an array
void printArray(int *arr, int size) {
for (int k=0; k<size; k++){
cout<<arr[k]<<" ";
}
}
// insertion sort function
void insertionSort(int *arr, int size) {
for (int step=1; step<size; step++) {
int key=arr[step]; //first element of the unsorted subarray
int j=step-1; //last index within the sorted subarray
while (j>=0 && key<arr[j]) {
arr[j+1]=arr[j];
j--;
}
arr[j+1]=key; //placing key at its correct position in the sorted portion
}
}
// driver code
int main()
{
int size;
cout<<"Enter size of array : "<<endl;
cin>>size;
int *arr = new int[size];
cout<<"Enter elements"<<endl;
for (int j=0; j<size; j++) {
cin>>arr[j];
}
insertionSort(arr, size);
cout<<"Sorted array is : "<<endl;
printArray(arr, size);
return 0;
}

• Output:
• Learning: From the insertion sort implementation, we get the intution that
maximum time would be needed to sort an array which is in reverse sorted
form and it takes minimum time when elements are already sorted. Thus,
best case time complexity is O(N) and average or worst case being O(N2).
Insertion sort can be highly useful when input array is almost sorted, only
few elements are misplaced in complete big array. Just like selection sort
and bubble sort, it is an in-place sorting algorithm.
EXPERIMENT-4

• Program Objective: Write a program to perform different operations on


stack as an array.
• Introduction: Stack is a dynamic, linear data structure that follows LIFO
(Last in First Out) principle, according to which, the last item to be inserted
into a stack is the first one to be accessed/deleted from it. There are many
real-life examples of a stack, one such being-plates stacked over one
another in the canteen. The plate which is at the top is the first one to be
removed, i.e. the plate which has been placed at the bottommost position
remains in the stack for the longest period of time. The operations
associated with stacks are as follows:
• push(g) – Adds the element ‘g’ at the top of the stack.
• pop() – Deletes the top most element of the stack.
• top() – Returns a reference to the top most element of the stack.
• empty() – Returns whether the stack is empty.
• size() – Returns the size of the stack.
Time Complexity of all these operations is O(1). Implementation of stack
can be done via 2 data structures-namely array and linked list. In this
experiment we will be using an array as the container and perform the
above operations in O(1) time.
• Algorithms:
[Assuming we keep 3 members in the stack class: data (pointer to the array
that we are using for stack implementation, nextIndex (the index at which
an element would be placed within the data array during push operation)
and capacity (the number of elements that our data array can hold)].

• Push operation:
• If nextIndex=capacity:
i. Initialise a new array (newData) on the heap memory of twice
the current capacity.
ii. Copy the elements of previous data array to the newData array
using loop.
iii. Update the capacity with twice its initial value.
iv. Delete the previous data array from heap.
v. Assign newData pointer value to data member.
• Place the element given by user at nextIndex location of data array.
• Update nextIndex to nextIndex+1.

• Pop operation:
• If nextIndex=0:
i. Print “Stack is empty”.
ii. Exit.
• Update nextIndex to nextIndex-1.

• Accessing top element:


• If nextIndex=0:
i. Print “Stack is empty”.
ii. Return null value (0) and exit.
• Return element present at nextIndex-1 location of data array.

• Checking if stack is empty:


• If nextIndex=0, return True.
• Else return False.

• Accessing size of the stack:


• Return nextIndex.

• Code: (Template is used in the stack class in order to be able to add any
data type in the stack)
Stack Class:
template <typename T> // Templates initialised
class StackUsingArray {
// //Privately declared class members
T *data; // Template type of data used
int nextIndex; // To keep the track of current top index
int capacity; // To keep the track of total capacity of data array
public :
//constructor to initialise the values
StackUsingArray() {
data = new T[4]; // Dynamic array created serving as stack
nextIndex = 0;
capacity = 4;
}
//To return number of elements present in stack
int size() {
return nextIndex;
}
//To check if stack is empty
bool empty() {
return nextIndex == 0;
}
// insert element
void push(T element) {
if(nextIndex == capacity) {
T *newData = new T[2 * capacity]; //Capacity
doubled
for(int i = 0; i < capacity; i++) {
newData[i] = data[i]; //Elements copied
}
capacity *= 2;
delete [] data;
data = newData;
}
data[nextIndex] = element;
nextIndex++; //Size incremented
}
// delete element
void pop() {
//Before deletion we check if stack is not empty to prevent underflow
if (empty()) {
cout << "Stack is empty " << endl;
}
nextIndex--;
}
//To return the top element of the stack
T top() {
if (empty()) { // checked for empty stack to prevent
overflow
cout << "Stack is empty " << endl;
return 0;
}
return data[nextIndex - 1];
}
};
Driver code:
#include <iostream>
using namespace std;
#include "StackUsingArray.h"
int main()
{
StackUsingArray<int> s;
s.push(10);
s.push(20);
s.push(30);
s.push(40);
s.push(50);
cout<<s.top()<<endl;
s.pop();
cout<<s.top()<<endl;
s.pop();
cout<<s.top()<<endl;
cout<<s.size()<<endl;
cout<<s.empty()<<endl;
return 0;
}

• Output:
• Learning: Through this experiment we learnt the implementation of
different stack operations using array as a container. We used a dynamic
array for the implementation so as to avoid experiencing “Stack Overflow”
and add as many elements as we wish to. We also understood the utility of
templates that help us make our class more generic in terms of handling
different data types.

EXPERIMENT-5

• Program Objective: Write a program to implement following operations on


a singly linked list:
• Create ()
• Insert element at beginning
• Insert element at end
• Insert element at position entered by user
• Delete element at the beginning
• Delete element at the end
• Delete element at the position entered by user
• Display elements of linked list
• Display size of the linked list

• Introduction: A linked list is a linear data structure, in which the elements


are not stored at contiguous memory locations. It consists of nodes where
each node contains a data field and a pointer to the next node in the list.
Building a ‘Node’ class is crucial for the implementation of linked list.

• Algorithms:
• Create
• Set choice=1, head=NULL and tail=NULL.
• Build a node from the data element provided by user and
store its pointer in newnode variable.
• Check if linked list is empty, if head=NULL:
• Update head and tail to newnode.
• Else
• Link newnode to the tail node.
• Update tail to newnode.
• Ask the user if they wish to continue adding elements.
• If choice entered by user=1, repeat steps 2 to 5.
• Else exit the loop.
• Return head of the linked list.

• Insert
• Build a node from the data element provided by user and
store its pointer in newnode variable.
• Set temp=head.
• If position entered by the user is 1 (means element is to be
added in the starting of the linked list):
• Attach head of the linked list after newnode.
• Update head with newnode.
• Return the updated head and exit.
• Set i=1.
• Repeat steps 6 and 7 while temp!=NULL and i<position-1.
• Move temp to temp->next.
• Increment i by 1.
• If temp!=NULL:
• Attach newnode to the Nodes present at temp and
temp->next.
• Return head and exit.

• Delete
• Set temp=head.
• If position entered by the user is 1 (means element present at
head is to be deleted):
• Move head to head->next.
• Delete node present at temp.
• Return the updated head and exit.
• Set i=1.
• Repeat steps 5 and 6 while temp!=NULL and i<position-1.
• Move temp to temp->next.
• Increment i by 1.
• If temp!=NULL and temp->next!=NULL:
• Attach the node present at temp->next->next to the
Node present at temp.
• Delete the Node present at temp->next.
• Return head and exit.

• Display size and elements


• Set count=0 and temp=head.
• Repeat steps 3 to 5 while temp!=NULL:
• Print data of the Node present at temp.
• Move temp to temp->next.
• Increment count by 1.
• Print the value of ‘count’ as the size of linked list and exit.

• Code:
#include <iostream>
using namespace std;
// node class
class Node {
public:
int data;
Node* next;
Node (int data) {
this->data=data;
next=NULL;
}
};
// function to print elements of a linked list as well as its size
void printLL(Node* head) {
int count=0;
Node*temp=head;
while (temp!=NULL) {
cout<<temp->data<<" ";
temp=temp->next;
count++;
}
cout<<"\nSize of linked list is "<<count<<"\n";
}
// create linked list function
Node* createLL() {
int element;
int choice=1;
Node* head=NULL;
Node* tail=NULL;
while(choice==1) {
cout<<"Enter element\n";
cin>>element;
Node *newnode=new Node(element);
if (head==NULL) {
head=newnode;
tail=newnode;
}
else {
tail->next=newnode;
tail=tail->next;
}
cout<<"Do you wish to continue?\n1: yes, 0: no\n";
cin>>choice;
}
return head;
}
//insert element function
Node* insert(Node* head, int pos, int data) {
Node* newnode=new Node(data);
Node* temp=head;
if (pos==1){
newnode->next=head;
head=newnode;
return head;
}
int i=1;
while (temp!=NULL && i<pos-1) {
temp=temp->next;
i++;
}
if (temp!=NULL) {
newnode->next=temp->next;
temp->next=newnode;
}
return head;
}
// delete element function
Node* deduct(Node* head, int pos) {
Node*temp=head;
if (pos==1) {
head=head->next;
delete temp;
return head;
}
int i=1;
while (temp!=NULL && i<pos-1) {
temp=temp->next;
i++;
}
if (temp!=NULL && temp->next!=NULL) {
Node* a=temp->next;
Node* b=a->next;
temp->next=b;
delete a;
}
return head;
}
// driver code
Node*head=NULL;
int main()
{ int choice;
int pos, data;
while(true) {
cout<<"Choose from the following:\n1: Create a linked list\n2: Print the
elements and size of the linked list\n3: Insert an element\n4: Delete an
element\n";
cin>>choice;
if (choice==1) {
head=createLL();
}
else if (choice==2) {
printLL(head);
}
else if (choice==3) {
cout<<"At which position do you want the element to be entered?\n";
cin>>pos;
cout<<"Which element you want to be entered?\n";
cin>>data;
head=insert(head, pos, data);
}
else if (choice==4) {
cout<<"Which position element is to be deleted?\n";
cin>>pos;
head=deduct(head, pos);
}
else {break;}
}
return 0;
}
• Outputs:
• Learning: Through this experimment, we learnt that linked list overcomes
the following shortcomings of arrays:
• To create a linked list, we don’t need to fix its size beforehand, the
user can enter as many elements as he/she wants at the runtime. On
the other hand, arrays have fixed size that is required to be initialised
at the time of declaration. Any addition of extra elements would
throw an error (index out of range).
• Even deletion of elements from the array is an expensive task as it
would require the complete shift of further elements by some
positions to the left as the data is stored in the form of contiguous
memory blocks.
EXPERIMENT-6

• Program Objective: Write a program to perform basic operations on stack


stored as linked list.

• Introduction: Stack being an abract data type, hides its actual


implementation from the user. The user is just provided with the
functionality of performing selected operations over the data type without
having to know how it is built. In experiment 4, we implemented stack using
array as a container. But as discussed earlier, we can also create a stack with
the help of linked lists. All the five functions that stacks can perform could
be made using linked lists having execution time equal to O(1) only.

• Algorithms: [Assuming we keep 2 members in the stack class: head (pointer


to the linked list that we are using for stack implementation and len
(number of elements present in the linked list)].

• P HYPERLINK "https://www.geeksforgeeks.org/stack-push-and-pop-
in-c-stl/"ush
• Create node containing the provided element and store its
address in newnode variable.
• Attach head after this new node created.
• Update head to newnode.
• Increment len by 1 and exit.

• Pop
• Check for stack underflow condition; if len=0:
• Print “Stack is empty” and exit.
• Set temp=head.
• Move head to head->next.
• Delete node present at temp.
• Decrement len by 1 and exit.

• Top
• Check for stack underflow condition; if len=0:
• Print “Stack is empty”.
• Return 0 and exit.
• Return data present at head node and exit.

• Empty
• If len=0, return true.
• Else return false.

• size() – Returns the size of the stack.


• Return len and exit.
• Code:
#include <iostream>
using namespace std;
// Node class
template <typename T>
class Node {
public:
T data;
Node<T> *next;
Node(T data){
this->data=data;
this->next=NULL;
}
};
// Stack class using linked list
template <typename T>
class Stack {
Node<T> *head;
int len; // number of elements present in stack
public:
Stack() { // Constructor
this->head=NULL;
this->len=0;
}
int size() {
return len;
}
bool empty() {
return len==0;
}
void push(T element) {
Node<T>* newnode=new Node<T>(element);
newnode->next=head;
head=newnode;
len++;
}
void pop() {
if (len==0) {
cout<<"Stack is empty"<<endl;
return;
}
Node<T> *temp=head;
head=head->next;
delete temp;
len--;
}
T top() {
if (len==0) {
cout<<"Stack is empty"<<endl;
return 0;
}
return head->data;
}
};
// driver code
int main () {
int choice;
Stack<int> s;
while (true)
{
cout <<"Choose from the following operations:\n1: Push 2: Pop 3: Top
4: Size 5: Empty\n";
cin >> choice;
if (choice == 1) {
int element;
cout << "Enter element\n";
cin>>element;
s.push(element);
}
else if (choice == 2) {
s.pop();
}
else if (choice == 3){
cout<<s.top()<<endl;

}
else if (choice == 4) {
cout<<"Size of the stack is "<<s.size()<<endl;
}
else if (choice==5) {
cout<<s.empty()<<endl;
}
else {break;}
}
return 0;
}

• Output:
• Learning: In this experiment, we implemented all the operations of stack
using a linked list which execute in O(1) time. For the user, everything
remains same whether the container used is array or linked list i.e. no
difference is created in the utility of functions.

Experiment 7
I. Program Objective: Write a program to perform the following operations
on doubly linked list.
I. Create ()
ii. Insert element at beginning
iii. Insert element at end
iv. Insert element at position entered by user
v. Delete element at the beginning
vi. Delete element at the end
vii. Delete element at the position entered by user
viii. Display elements of linked list
ix. Display elements in reverse order
x. Reverse the elements
xi. Display size of the linked list
II. Introduction:Doubly linked list is a type of data structure that is made up of
nodes that are created using self-referential structures. Each of these nodes
contain three parts, namely the data and the reference to the next list node
and the reference to the previous list node.
Only the reference to the first list node is required to access the whole
linked list. This is known as the head. The last node in the list points to
nothing so it stores NULL in that part. Also, the doubly linked list can be
traversed in both directions as each node points to its previous and next
node.
III. ALGORITHM-Insertion
1) Add a node at the front:
The new node is always added before the head of the given Linked List. And
newly added node becomes the new head of DLL. For example, if the given
Linked List is 1->0->1->5 and we add an item 5 at the front, then the Linked
List becomes 5->1->0->1->5. Let us call the function that adds at the front of
the list push(). The push() must receive a pointer to the head pointer
because the push must change the head pointer to point to the new node
Time Complexity: O(1)
Auxiliary Space: O(1)
2) Add a node after a given node:
We are given a pointer to a node as prev_node, and the new node is
inserted after the given node.
Time Complexity: O(1)
Auxiliary Space: O(1)

3) Add a node at the end:


The new node is always added after the last node of the given Linked List.
For example, if the given DLL is 5->1->0->1->5->2 and we add item 30 at the
end, then the DLL becomes 5->1->0->1->5->2->30. Since a Linked List is
typically represented by its head of it, we have to traverse the list till the
end and then change the next of last node to the new node.
Time Complexity: O(n)
Auxiliary Space: O(1)

4) Add a node before a given node:


Follow the below steps to solve the problem:
Let the pointer to this given node be next_node and the data of the new
node be added as new_data.
Check if the next_node is NULL or not. If it’s NULL, return from the function
because any new node can not be added before a NULL
Allocate memory for the new node, let it be called new_node
• Set new_node->data = new_data
• Set the previous pointer of this new_node as the previous node of
the next_node, new_node->prev = next_node->prev
• Set the previous pointer of the next_node as the new_node,
next_node->prev = new_node
• Set the next pointer of this new_node as the next_node, new_node-
>next = next_node;
• If the previous node of the new_node is not NULL, then set the next
pointer of this previous node as new_node, new_node->prev->next =
new_node
• Else, if the prev of new_node is NULL, it will be the new head node.
So, make (*head_ref) = new_node.
Time Complexity: O(n)
Auxiliary Space: O(1)
ALORITHM: Deletion
Let the node to be deleted be del.
▪ If node to be deleted is head node, then change the head
pointer to next current head.
if headnode == del then
headnode = del.nextNode
▪ Set prev of next to del, if next to del exists.
if del.nextNode != none
del.nextNode.previousNode = del.previousNode
▪ Set next of previous to del, if previous to del exists.
if del.previousNode != none
del.previousNode.nextNode = del.next

Deletion after a node-


1. Get the pointer to the node at position n by traversing the doubly linked
list up to the nth node from the beginning.
2. Delete the node using the pointer obtained in Step 1.

Algorithm: Reverse a linked list


• Step 1: Set LEFT to head of list
• Step 2: Traverse the list and set RIGHT to end of the list
• Step 3: Repeat following steps while LEFT != RIGHT and
▪ LEFT->PREV != RIGHT
• Step 4: Swap LEFT->DATA and RIGHT->DATA
• Step 5: Advance LEFT pointer by one, LEFT = LEFT->NEXT
• Step 6: Recede RIGHT pointer by one, i.e RIGHT = RIGHT->PREV
[END OF LOOP]
Time Complexity: O(n)
Auxiliary Space: O(1)
IV. CODE
include <bits/stdc++.h>
using namespace std;

class Node {
public:
int data;
Node* next;
Node* prev;
};

void push(Node** head_ref, int new_data)


{
Node* new_node = new Node();

new_node->data = new_data;

new_node->next = (*head_ref);
new_node->prev = NULL;

if ((*head_ref) != NULL)
(*head_ref)->prev = new_node;

(*head_ref) = new_node;
}
void insertAfter(Node* prev_node, int new_data)
{
if (prev_node == NULL) {
cout << "the given previous node cannot be NULL";
return;
}
Node* new_node = new Node();
new_node->data = new_data;
new_node->next = prev_node->next;
prev_node->next = new_node;
new_node->prev = prev_node;
if (new_node->next != NULL)
new_node->next->prev = new_node;
}
void append(Node** head_ref, int new_data)
{
Node* new_node = new Node();
Node* last = *head_ref;

/* 2. put in the data */


new_node->data = new_data;

new_node->next = NULL;

if (*head_ref == NULL) {
new_node->prev = NULL;
*head_ref = new_node;
return;
}

while (last->next != NULL)


last = last->next;

last->next = new_node;
new_node->prev = last;
return;
}
void deleteNode(Node** head_ref, Node* del)
{

if (*head_ref == NULL || del == NULL)


return;

if (*head_ref == del)
*head_ref = del->next;

if (del->next != NULL)
del->next->prev = del->prev;

if (del->prev != NULL)
del->prev->next = del->next;

free(del);
return;
}
void deleteNodeAtGivenPos(struct Node** head_ref, int n)
{
if (*head_ref == NULL || n <= 0)
return;

struct Node* current = *head_ref;


int i;

for (int i = 1; current != NULL && i < n; i++)


current = current->next;

if (current == NULL)
return;

deleteNode(head_ref, current);
}
void reverseList(Node** head)
{
Node* left = *head, * right = *head;

while (right->next != nullptr)


right = right->next;

while (left != right && left->prev != right) {

swap(left->data, right->data);

left = left->next;

right = right->prev;
}
}
int findSize(struct Node *node)
{
int res = 0;
while (node != NULL)
{
res++;
node = node->next;
}
return res;
}
void printList(Node* node)
{
Node* last;
cout << "\nTraversal in forward direction \n";
while (node != NULL) {
cout << " " << node->data << " ";
last = node;
node = node->next;
}
cout << "\nTraversal in reverse direction \n";
while (last != NULL) {
cout << " " << last->data << " ";
last = last->prev;
}
}
int main()
{
Node* head = NULL;

append(&head, 6);
push(&head, 7);
push(&head, 1);
append(&head, 9);
push(&head, 5);
push(&head, 2);
append(&head, 4);
insertAfter(head->next, 8);

cout << "Created DLL is: ";


printList(head);

deleteNode(&head, head);
deleteNode(&head, head->next);
deleteNode(&head, head->next);
printList(head);

int n=2;
deleteNodeAtGivenPos(&head, n);

cout << "\nDoubly linked list after deletion:n";


printList(head);

cout << "\nList After Reversing" << endl;


reverseList(&head);
printList(head);
cout << "\nsize is equal to"<<findSize(head);

return 0;
}
V. OUTPUT

VI. LEARNING OUTCOME-


A doubly linked list is a variation of the singly linked list. It differs from the
singly linked list in that where each node contains an extra pointer to the
previous node along with the next pointer.

This presence of an extra pointer facilitates insert, delete operations on the


doubly linked list but at the same time requires extra memory to store
these extra pointers.

EXPERIMENT-8
• Program Objective: Write a program to perform the following operations
on an array.
• Create an array
• Insert an element in an array
• Delete an element from the array
• Print elements of array in reverse order
• Reverse elements of array
• Display elements of array
• Display size of the array
• Introduction: An array is a collection of items of same data type stored at
contiguous memory locations. This makes it easier to calculate the position
of each element by simply adding an offset to a base value, i.e., the
memory location of the first element of the array (generally denoted by the
name of the array). Just like the case of linked lists, various operations can
be performed on arrays as well which are shown below.
• Algorithms:
• Create an array
• Ask for the array capacity and number of elements to be
entered from the user.
• If number of elements entered exceeds the capacity of array,
exit.
• Create an array of the provided capacity value and store its
pointer in arr.
• Run a loop for the total number of elements entered by the
user and ask for the input at each position.
• Return arr pointer and exit.
• Insert an element in an array
• Ask for position and the element to be entered from the user.
• If number of elements present in the array = capacity of the
array, print “Array is full” and exit.
• If position entered by the user is greater than the number of
elements present in the array, ask user to give a valid position
and exit.
• Shift elements from the end to their right position by 1 uptill
the element present at the position entered by user.
• Insert provided element at the position asked.
• Increment the number of elements by 1 and exit.
• Delete an element from the array
• If number of elements present in the array=0, print “Array is
empty” and exit.
• If position provided by the user at which the element is to be
deleted is greater than or equal to the total number of
elements present in the array currently, ask the user to give a
valid position and exit.
• Starting from the element present at position+1, shift all
elements to the left by 1 till the last element.
• Decrement total number of elements present by 1 and exit.
• Print elements of array in reverse order
• If number of elements present in the array=0, print “Array is
empty” and exit.
• Starting from i=number of elements present in array-1 till i=0,
print element present at the ith position of array.
• Exit.
• Reverse elements of array
• If number of elements present in the array=0, print “Array is
empty” and exit.
• Initialise i=0 and j= number of elements present in array-1.
• Repeat step 4, 5 while i< number of elements present in
array/2.
• Swap elements present at i and jth position.
• Increment i by 1 and decrement j by 1.
• Exit.
• Display elements of array
• If number of elements present in the array=0, print “Array is
empty” and exit.
• Initialise i=0 and traverse the array till i= number of elements
present in the array-1.
• Print the elements present at the respective ith positions.
• Exit.
• Display size of the array
• Print the value of global variable num.
• Code:
#include <iostream>
using namespace std;
// global variables
int *arr = NULL;
int size=0; //capacity of the array
int num=0; //number of elements within the array
// create function
int* createArr(int &size, int &num) {
if (num>size) {
cout<<"No. of elements exceed the capacity of array"<<endl;
return NULL;
}
arr=new int[size];
cout<<"Enter elements "<<endl;
for (int i=0; i<num; i++) {
cin>>arr[i];
}
cout<<"Array created!"<<endl;
return arr;
}
// insert element function
void insert(int pos, int element, int &num, int &size, int *arr) {
if (num==0) {
cout<<"Create an array first"<<endl;
return;
}
else if (num==size) {
cout<<"Array is full"<<endl;
return;
}
else if (pos>num) {
cout<<"Enter valid position"<<endl;
return;
}
for (int i=num; i>=pos; i--) {
arr[i]=arr[i-1];
}
arr[pos]=element;
num++;
cout<<"Element entered!"<<endl;
}
// delete element function
void deduct(int pos, int &num, int* arr){
if (num==0) {
cout<<"Array is empty"<<endl;
return;
}
else if (pos>=num) {
cout<<"Enter valid position"<<endl;
return;
}
for (int i=pos;i<num;i++) {
arr[i]=arr[i+1];
}
num--;
cout<<"Element deleted!"<<endl;
}
// print in reverse function
void printRev(int &num, int* arr){
if (num==0) {
cout<<"Array is empty"<<endl;
return;
}
for (int i=num-1; i>=0; i--) {
cout<<arr[i]<<" ";
}
cout<<endl;
}
// reverses original array
void revArr(int &num, int *arr){
if (num==0) {
cout<<"Array is empty"<<endl;
return;
}
int i, j;
for (i=0, j=num-1; i<num/2; i++, j--) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
cout<<"Array reversed!"<<endl;
}
// prints array elements, total number of elements and the current capacity
of the array
void printArr(int &num, int* arr, int &size){
if (num==0) {
cout<<"Array is empty"<<endl;
return;
}
for (int i=0; i<num; i++) {
cout<<arr[i]<<" ";
}
cout<<endl<<"Number of elements within the array: "<<num<<" and
current capacity of the array: "<<size<<endl;
}
// driver code
int main()
{
int choice, pos, element;
while (true) {
cout<<"Choose from the following: \n1: Create an array 2: Insert an
element 3: Delete an element 4: Print elements in reverse order 5:
Reverse elements of array 6: Print elements and size of array"<<endl;
cin>>choice;
if (choice==1) {
cout<<"Enter capacity of the array: ";
cin>>size;
cout<<"No. of elements to be entered: ";
cin>>num;
arr=createArr(size, num);
}
else if (choice==2) {
cout<<"At which position you want to enter the element? ";
cin>>pos;
cout<<"Enter element ";
cin>>element;
insert(pos, element, num, size, arr);
}
else if(choice==3) {
cout<<"Which position element is to be deleted? ";
cin>>pos;
deduct(pos, num, arr);
}
else if(choice==4) {
printRev(num, arr);
}
else if(choice==5) {
revArr(num, arr);
}
else if(choice==6) {
printArr(num, arr, size);
}
else {break;}
}
return 0;
}
• Output:
• Learning: Through this experiment we performed various operations on an
array having fixed capacity. We saw that, for insert and delete operations,
elements need to be shifted rendering the worst case time complexity of
these operations to be O(N). However, accessing an element present at a
specific position ‘i’ in the array is a super-fast operation unlike in the case of
linked lists where we have to traverse the list to get the desired data
element.

EXPERIMENT-10
Objective : WAP to transform a infix expression to postfix
expression
Theory:
Infix: The typical mathematical form of expression that we encounter
generally is known as
infix notation. In infix form, an operator is written in between two operands.
For example:
An expression in the form of A * ( B + C ) / D is in infix form. This expression
can be simply
decoded as: “Add B and C, then multiply the result by A, and then divide it by
D for the final
answer.”
Postfix: In postfix expression, an operator is written after its operands. This
notation is also
known as “Reverse Polish notation”.
For example, The above expression can be written in the postfix form as A B
C + * D /. This type
of expression cannot be simply decoded as infix expressions.
Algorithm:
• Step 1 : Scan the Infix Expression from left to right.
• Step 2 : If the scanned character is an operand, append it with final Infix to
Postfix string.
• Step 3 : Else,
o Step 3.1 : If the precedence order of the scanned(incoming) operator is
greater than the precedence order of the operator in the stack (or the
stack is empty or the stack contains a ‘(‘ or ‘[‘ or ‘{‘), push it on stack.
• Step 3.2 : Else, Pop all the operators from the stack which are greater than
or
equal to in precedence than that of the scanned operator. After doing that
Push
the scanned operator to the stack. (If you encounter parenthesis while
popping
then stop there and push the scanned operator in the stack.)
• Step 4 : If the scanned character is an ‘(‘ or ‘[‘ or ‘{‘, push it to the stack.
• Step 5 : If the scanned character is an ‘)’or ‘]’ or ‘}’, pop the stack and and
output
it until a ‘(‘ or ‘[‘ or ‘{‘ respectively is encountered, and discard both the
parenthesis.
• Step 6 : Repeat steps 2-6 until infix expression is scanned.
• Step 7 : Print the output
• Step 8 : Pop and output from the stack until it is not empty.
Code :
#include <iostream>
#include <stack>
using namespace std;
int precedence(char c)
{
if (c == '^')
{
return 3;
}
else if (c == '*' || c == '/')
{
return 2;
}
else if (c == '+' || c == '-')
{
return 1;
}
else
{
return -1;
}
}
int main()
{
string s = "(x+y*z)+b/c" ;
stack<char> input ;
string pfix ;
for (int i = 0; i < s.length(); i++)
{
if((s[i] >= 'a' && s[i] <= 'z') || (s[i] >= 'A' && s[i] <= 'Z'))
{
pfix += s[i];
}
else if (s[i] == '(')
{
input.push(s[i]);
}
else if (s[i] == ')')
{
while (!input.empty() && input.top() != '(')
{
pfix += input.top();
input.pop();
}
if (!input.empty())
{
input.pop();
}
}
else
{
while (!input.empty() && precedence(input.top()) > precedence(s[i]))
{
pfix += input.top();
}
input.push(s[i]);
}
}
while (!input.empty())
{
pfix += input.top();
input.pop();
}
cout << "Final postfix expression : "<<pfix <<endl ;
}
Input/Output :
EXPERIMENT-11
Objective : WAP to transform a infix expression to prefix expression
Theory:
Infix: The typical mathematical form of expression that we encounter
generally is known as
infix notation. In infix form, an operator is written in between two operands.
For example:
An expression in the form of A * ( B + C ) / D is in infix form. This expression
can be simply
decoded as: “Add B and C, then multiply the result by A, and then divide it by
D for the final
answer.”
Prefix: In prefix expression, an operator is written before its operands. This
notation is also
known as “Polish notation”.
For example, The above expression can be written in the prefix form as / * A
+ B C D. This type
of expression cannot be simply decoded as infix expressions.
Algorithm:
Step 01: reverse the infix expression
Step 02: for each character ch of reversed infix expression, do
if ch = opening parenthesis, then
convert ch to closing parenthesis
else if ch = closing parenthesis, then
convert ch to opening parenthesis
Step 03: postfix := find transformed infix expression to postfix expression
prefix := reverse recently calculated postfix form
Step 04: print prefix
Step 05: End
Code :
#include <iostream>
#include <stack>
#include <algorithm>
using namespace std;
int precedence(char c)
{
if (c == '^')
{
return 3;
}
else if (c == '*' || c == '/')
{
return 2;
}
else if (c == '+' || c == '-')
{
return 1;
}
else
{
return -1;
}
}
int main()
{ string s = "(a+b/c)+d/e";
reverse(s.begin(), s.end());
stack<char> input;
string prefix;
for (int i = 0; i < s.length(); i++)
{
if ((s[i] >= 'a' && s[i] <= 'z') || (s[i] >= 'A' && s[i] <= 'Z'))
{
prefix += s[i];
}
else if (s[i] == ')')
{
input.push(s[i]);
}
else if (s[i] == '(')
{
while (!input.empty() && input.top() != ')')
{
prefix += input.top();
input.pop();
}
if (!input.empty())
{
input.pop();
}
}
else
{
while (!input.empty() && precedence(input.top()) > precedence(s[i]))
{
prefix += input.top();
input.pop();
}
input.push(s[i]);
}
}
while (!input.empty())
{prefix += input.top();
input.pop();
}
reverse(prefix.begin(), prefix.end());
cout <<"Final Prefix expression : "<< prefix << endl;
return 0;
}
Input/Output :

EXPERIMENT-12

• Program Objective: Write a program to perform following operations on


binary search tree:
• Search a given node
• Insertion of node
• Deletion of node

• Introduction: Binary Search Tree is a special type of binary tree that has a
specific order of elements in it. It follows three basic properties:-
• All elements in the left subtree of a node should have a value lesser
than the node’s value.
• All elements in the right subtree of a node should have a
value greater than the node’s value.
• Both the left and right subtrees should be binary search trees too.
Since BST is a type of Binary Tree, the same operations are performed in
BST too. Although, insertion and deletion in BST are much stricter with
predetermined conventions so that even after performing an operation, the
properties of BST are not violated.
• Algorithms:
• Search a given node
• Check if the root is NULL, return False if it is NULL.
• Else, compare the data present at root node with the search
query.
• element == root->data: return True
• element > root->data: recurse for right subtree
• element < root->data: recurse for left subtree
• Insertion of node
• If the root is NULL, create a new node with value element and
return it.
• Else, Compare element with root->data.
• element > root->data: recurse for right subtree
• element < root->data: recurse for left subtree
• Deletion of node
• Check if the root is NULL, if it is, just return the root itself. It's
an empty tree.
• element > root->data: recurse for right subtree
• element < root->data: recurse for left subtree
• If both above conditions above false, this means root->data=
item.
• Now we first need to check how many children does root have.
• CASE 1: No Child → Just delete root or deallocate space
occupied by it
• CASE 2: One Child →Replace root by its child
• CASE 3: Two Children
• Find the inorder successor of the root (Its the smallest
element of its right subtree). Let's call it new_root.
• Replace root by its inorder successor.
• Now recurse to the right subtree and delete new_root.
• Return the root and exit.

• Code:
#include <iostream>
using namespace std;
// tree node class
class BinaryTreeNode {
public:
int data;
BinaryTreeNode *left;
BinaryTreeNode *right;
BinaryTreeNode (int data) {
this->data=data;
left=NULL;
right=NULL;
}
};
// BST class
class BST {
BinaryTreeNode* root;
bool search(BinaryTreeNode* root, int data) {
if (root==NULL) {return false;}
if (root->data==data) {return true;}
else if (root->data>data) {return search(root->left, data);}
else {return search(root->right, data);}
}
BinaryTreeNode* insert(BinaryTreeNode* root,int data) {
if (root==NULL) {
BinaryTreeNode* node = new BinaryTreeNode (data);
return node;
}
if (root->data>=data) {
BinaryTreeNode* l = insert(root->left, data);
root->left = l;
}
else {
BinaryTreeNode* r = insert(root->right, data);
root->right = r;
}
return root;
}
// finds minimum element of a BST
int minimum (BinaryTreeNode* root) {
if (root->left !=NULL) {
return minimum (root->left);
}
else if (root->left ==NULL) {
return root->data;
}
}
BinaryTreeNode* deduct (BinaryTreeNode* root, int data) {
if (root==NULL) {return NULL;}
if (root->data>data) {
BinaryTreeNode* l = deduct(root->left, data);
root->left = l;
}
else if (root->data<data) {
BinaryTreeNode* r = deduct(root->right, data);
root->right = r;
}
else {
if (root->left==NULL && root->right==NULL) {
delete root;
return NULL;
}
else if (root->left!=NULL && root->right==NULL) {
BinaryTreeNode* temp = root->left;
delete root;
return temp;
}
else if (root->left==NULL && root->right!=NULL) {
BinaryTreeNode* temp = root->right;
delete root;
return temp;
}
else {
int mini=minimum(root->right);
root->data = mini;
BinaryTreeNode* ans = deduct(root->right, mini);
root->right=ans;
}
}
return root;
}
void print(BinaryTreeNode* root) {
if (root==NULL) {return;}
if (root->left!=NULL && root->right!=NULL)
{cout<<root->data<<":"<<"L"<<":"<<root->left-
>data<<","<<"R"<<":"<<root->right->data<<endl;
print(root->left);
print(root->right);}

else if (root->left!=NULL && root->right==NULL)


{cout<<root->data<<":"<<"L"<<":"<<root->left->data<<","<<endl;
print(root->left);}

else if (root->left==NULL && root->right!=NULL)


{cout<<root->data<<":"<<"R"<<":"<<root->right->data<<endl;
print(root->right);}

else {cout<<root->data<<":"<<endl;}
}
// public functions of BST class
public:
BST (){
this->root=NULL;
}
void deduct(int data) {
BinaryTreeNode* new_root = deduct(root, data);
this->root= new_root;
return;
}
void print() {
print(this->root);
}
void insert(int data) {
BinaryTreeNode* new_root = insert(root, data);
this->root = new_root;
return;
}
bool search(int data) {
return search(root, data);
}
};

// driver code
int main()
{ BST b;
int choice, element;
while (true) {
cout<<"Choose from the following: \n1: Insert 2: Search 3: Delete 4:
Print BST"<<endl;
cin>>choice;
if (choice==1) {
cout<<"Enter element ";
cin>>element;
b.insert(element);
}
else if (choice==2) {
cout<<"Enter element to be searched ";
cin>>element;
if (b.search(element)) {cout<<"Element is present"<<endl;}
else {cout<<"Element is not present"<<endl;}
}
else if(choice==3) {
cout<<"Which element is to be deleted? "<<endl;
cin>>element;
b.deduct(element);
}
else if(choice==4) {
b.print();
}
else {break;}
}
return 0;
}

• Output:
• Learning: In a normal binary tree, if we need to search for an element, we
need to traverse the entire tree (in the worst case). But in BST, we can
optimize this by using its search property to our advantage. Time
complexity of all the operations performed on BST is O(h) where ‘h’ is the
height of BST. In the worst case this can become O(N) for a skewed BST. On
the other hand if the BST is balanced, height would be log(N) and time
complexity for all operations would be then be O(log(N)).

You might also like