0% found this document useful (0 votes)
5 views61 pages

ACA Lab

The document provides an overview of various array operations including linear search, binary search, interpolation search, inserting and deleting elements, finding a subarray with a given sum, and checking if an array is sorted. Each section includes algorithms, time and space complexities, and sample code in C++. The document is structured to offer clear explanations and examples for each operation.
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)
5 views61 pages

ACA Lab

The document provides an overview of various array operations including linear search, binary search, interpolation search, inserting and deleting elements, finding a subarray with a given sum, and checking if an array is sorted. Each section includes algorithms, time and space complexities, and sample code in C++. The document is structured to offer clear explanations and examples for each operation.
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/ 61

Name: Satwik Kamath

USN: 1MS21EC098
Section: A Section

1 Arrays
1.1 Linear Search
Algorithm:
1. Start from the first element of the array.
2. Compare the target element with the current element.
3. If the current element matches the target, return the index of the current element.
4. If the end of the array is reached without finding the target, return -1 to indicate that the element is not present.
Time Complexity:
• Best case: O(1) (when the element is found at the first position)
• Worst case: O(n) (when the element is not present or is found at the last position)
• Average case: O(n) (on average, the element might be somewhere in the middle)
Space Complexity:
• The space complexity is O(1) because constant space is used, as no extra space is required apart from the input
array and the target element.
Code:
# include < iostream >
using namespace std ;

int linearSearch ( int arr [] , int n , int target )


{
for ( int i = 0; i < n ; i ++)
if ( arr [ i ] == target )
return i ;
return -1;
}

int main ()
{
int n , target ;

cout << " Enter the number of elements in the array : " ;
cin >> n ;

int arr [ n ];

cout << " Enter " << n << " elements of the array : " ;
for ( int i = 0; i < n ; i ++)
cin >> arr [ i ];

cout << " Enter the target element to search for : " ;
cin >> target ;

int result = linearSearch ( arr , n , target ) ;


if ( result != -1)
cout << " Index of " << target << " : " << result << endl ;
else
cout << " Element not found " << endl ;

return 0;
}

Output 1:

1
Enter the number of elements in the array: 5
Enter 5 elements of the array: 5 3 9 1 6
Enter the target element to search for: 9
Index of 9: 2

Output 2:
Enter the number of elements in the array: 5
Enter 5 elements of the array: 5 3 9 1 6
Enter the target element to search for: 4
Element not found

1.2 Binary Search


Algorithm:
1. Initialize two variables, left and right, to represent the bounds of the array.
2. While left is less than or equal to right, find the middle element mid.
3. If the middle element is the target, return mid.
4. If the target is smaller than the middle element, update right to mid - 1.
5. If the target is larger than the middle element, update left to mid + 1.
6. If the loop ends without finding the target, return -1 to indicate that the element is not present.
Time Complexity:
• Best case: O(1) (when the element is found at the middle)
• Worst case: O(log n) (each comparison halves the search space)
• Average case: O(log n) (same reasoning as worst case)
Space Complexity:
• The space complexity is O(1) because the algorithm only uses a constant amount of space, aside from the input
array.
Code:
# include < iostream >
using namespace std ;

int binarySearch ( int arr [] , int n , int target )


{
int left = 0 , right = n - 1;
while ( left <= right )
{
int mid = left + ( right - left ) / 2;

if ( arr [ mid ] == target )


return mid ;

if ( arr [ mid ] > target )


right = mid - 1;
else
left = mid + 1;
}
return -1;
}

int main ()
{
int n , target ;

2
cout << " Enter the number of elements in the array : " ;
cin >> n ;

int arr [ n ];

cout << " Enter " << n << " elements of the array ( sorted in ascending order ) : " ;
for ( int i = 0; i < n ; i ++)
cin >> arr [ i ];

cout << " Enter the target element to search for : " ;
cin >> target ;

int result = binarySearch ( arr , n , target ) ;

if ( result != -1)
cout << " Element found at index " << result << endl ;
else
cout << " Element not found " << endl ;

return 0;
}

Output 1:
Enter the number of elements in the array: 10
Enter 10 elements of the array (sorted in ascending order): 1 3 5 7 9 11 13 15 17 19
Enter the target element to search for: 7
Element found at index 3
Output 2:
Enter the number of elements in the array: 5
Enter 5 elements of the array (sorted in ascending order): 2 4 6 8 10
Enter the target element to search for: 3
Element not found

1.3 Interpolation Search


Algorithm:
1. Start with two pointers, low and high, representing the bounds of the array.
2. Calculate the position pos using the formula:

(target − arr[low]) × (high − low)


pos = low +
arr[high] − arr[low]

3. If the element at pos is the target, return pos.


4. If the element at pos is smaller than the target, set low = pos + 1.
5. If the element at pos is larger than the target, set high = pos - 1.

6. If low exceeds high, the target is not in the array, so return -1.
Time Complexity:
• Best case: O(1) (when the element is found at the first position)

• Worst case: O(n) (when the element is not present or search space is large)
• Average case: O(log log n) (under ideal conditions)
Space Complexity:
• O(1) (constant space used as no extra space is required other than the input array and the target)

Code:

3
# include < iostream >
using namespace std ;

int i n t e r p o l a t i o n S e a r c h ( int arr [] , int n , int target )


{
int low = 0 , high = n - 1;

while ( low <= high && target >= arr [ low ] && target <= arr [ high ])
{
int pos = low + (( target - arr [ low ]) * ( high - low ) ) / ( arr [ high ] - arr [ low ]) ;

if ( arr [ pos ] == target )


return pos ;

if ( arr [ pos ] < target )


low = pos + 1;
else
high = pos - 1;
}
return -1;
}

int main ()
{
int n , target ;

cout << " Enter the number of elements in the array : " ;
cin >> n ;

int arr [ n ];

cout << " Enter " << n << " elements of the array ( sorted in ascending order ) : " ;
for ( int i = 0; i < n ; i ++)
cin >> arr [ i ];

cout << " Enter the target element to search for : " ;
cin >> target ;

int result = i n t e r p o l a t i o n S e a r c h ( arr , n , target ) ;

if ( result != -1)
cout << " Element found at index " << result << endl ;
else
cout << " Element not found " << endl ;

return 0;
}

Output 1:
Enter the number of elements in the array: 10
Enter 10 elements of the array (sorted in ascending order): 1 3 5 7 9 11 13 15 17 19
Enter the target element to search for: 7
Element found at index 3
Output 2:
Enter the number of elements in the array: 5
Enter 5 elements of the array (sorted in ascending order): 2 4 6 8 10
Enter the target element to search for: 3
Element not found

1.4 Inserting an Element in an array


Algorithm:
1. Take the input array, element to be inserted, and the position where the element is to be inserted.
2. Shift all elements from the position to the right by one place to make space for the new element.
3. Insert the new element at the specified position.

4
4. Return the updated array.
Time Complexity:

• Best case: O(1) (when the element is inserted at the last position)
• Worst case: O(n) (when the element is inserted at the beginning or middle, causing a shift of all elements)
• Average case: O(n) (shifting elements for insertion)
Space Complexity:

• O(1) (constant space used, no extra space needed apart from the input array)
Code:
# include < iostream >
using namespace std ;

void insertElement ( int arr [] , int &n , int element , int pos )
{
for ( int i = n - 1; i >= pos ; i - -)
arr [ i + 1] = arr [ i ];

arr [ pos ] = element ;


n ++;
}

int main ()
{
int n , element , pos ;

cout << " Enter the number of elements in the array : " ;
cin >> n ;

int arr [ n + 1];

cout << " Enter " << n << " elements of the array : " ;
for ( int i = 0; i < n ; i ++)
cin >> arr [ i ];

cout << " Enter the element to insert : " ;


cin >> element ;
cout << " Enter the position to insert the element (0 - based index ) : " ;
cin >> pos ;

insertElement ( arr , n , element , pos ) ;

cout << " Array after insertion : " ;


for ( int i = 0; i < n ; i ++)
cout << arr [ i ] << " " ;
cout << endl ;

return 0;
}

Output 1:
Enter the number of elements in the array: 5
Enter 5 elements of the array: 1 2 3 4 5
Enter the element to insert: 6
Enter the position to insert the element (0-based index): 2
Array after insertion: 1 2 6 3 4 5
Output 2:

Enter the number of elements in the array: 4


Enter 4 elements of the array: 10 20 30 40
Enter the element to insert: 25
Enter the position to insert the element (0-based index): 1
Array after insertion: 10 25 20 30 40

5
1.5 Deleting an Element from an Array
Algorithm:
1. Take the input array, the element to be deleted, and the position of the element to be deleted.
2. If the position is valid, shift all elements from the position to the left by one place.
3. Decrease the size of the array.
4. Return the updated array.
Time Complexity:
• Best case: O(1) (when the element to be deleted is at the last position)
• Worst case: O(n) (when the element is deleted from the beginning or middle, causing a shift of all elements)
• Average case: O(n) (shifting elements for deletion)
Space Complexity:
• O(1) (constant space used, no extra space needed apart from the input array)
Code:
# include < iostream >
using namespace std ;

void deleteElement ( int arr [] , int &n , int pos )


{
for ( int i = pos ; i < n - 1; i ++)
arr [ i ] = arr [ i + 1];
n - -;
}

int main ()
{
int n , pos ;

cout << " Enter the number of elements in the array : " ;
cin >> n ;

int arr [ n ];

cout << " Enter " << n << " elements of the array : " ;
for ( int i = 0; i < n ; i ++)
cin >> arr [ i ];

cout << " Enter the position to delete the element from (0 - based index ) : " ;
cin >> pos ;

deleteElement ( arr , n , pos ) ;

cout << " Array after deletion : " ;


for ( int i = 0; i < n ; i ++)
cout << arr [ i ] << " " ;
cout << endl ;

return 0;
}

Output 1:
Enter the number of elements in the array: 5
Enter 5 elements of the array: 1 2 3 4 5
Enter the position to delete the element from (0-based index): 2
Array after deletion: 1 2 4 5
Output 2:
Enter the number of elements in the array: 4
Enter 4 elements of the array: 10 20 30 40
Enter the position to delete the element from (0-based index): 1
Array after deletion: 10 30 40

6
1.6 Sub array with a given sum
Algorithm:
1. Initialize two pointers, start and end, both set to 0. Also, initialize a variable currentSum to store the sum of
elements in the current window.
2. Move the end pointer to expand the window by adding elements to currentSum.
3. If currentSum becomes equal to the target sum, return the indices of the subarray.
4. If currentSum exceeds the target sum, move the start pointer to shrink the window until currentSum becomes
less than or equal to the target sum.
5. If no subarray is found that satisfies the condition, return a message indicating no such subarray exists.
Time Complexity:
• Best case: O(n) (when the subarray is found early)
• Worst case: O(n) (the end pointer moves through the entire array and start only moves to the right as needed)
• Average case: O(n) (linear time complexity)
Space Complexity:
• O(1) (constant space used, only a few variables are required)
Code:
# include < iostream >
using namespace std ;

bool f i n d S u b a r r a y W i t h S u m ( int arr [] , int n , int targetSum )


{
int currentSum = 0 , start = 0;

for ( int end = 0; end < n ; end ++)


{
currentSum += arr [ end ];

while ( currentSum > targetSum && start <= end )


{
currentSum -= arr [ start ];
start ++;
}

if ( currentSum == targetSum )
{
cout << " Subarray with the given sum found between indices " << start << " and " << end <<
endl ;
return true ;
}
}

cout << " No subarray with the given sum exists . " << endl ;
return false ;
}

int main ()
{
int n , targetSum ;

cout << " Enter the number of elements in the array : " ;
cin >> n ;

int arr [ n ];

cout << " Enter " << n << " elements of the array : " ;
for ( int i = 0; i < n ; i ++)
cin >> arr [ i ];

cout << " Enter the target sum : " ;

7
cin >> targetSum ;

f i n d S u b a r r a y W i t h S u m ( arr , n , targetSum ) ;

return 0;
}

Output 1:
Enter the number of elements in the array: 7
Enter 7 elements of the array: 1 4 20 3 10 5 2
Enter the target sum: 33
Subarray with the given sum found between indices 2 and 4
Output 2:
Enter the number of elements in the array: 5
Enter 5 elements of the array: 1 2 3 7 5
Enter the target sum: 12
Subarray with the given sum found between indices 2 and 3

1.7 Check if array is sorted in ascending or descending order


Algorithm:
1. Initialize a variable isAscending and isDescending as true to track whether the array is sorted in ascending or
descending order.
2. Traverse the array from left to right:
• If an element is greater than the next element, set isAscending to false.
• If an element is less than the next element, set isDescending to false.
3. After completing the traversal:
• If both isAscending and isDescending are false, the array is unsorted.
• If isAscending is true, the array is sorted in ascending order.
• If isDescending is true, the array is sorted in descending order.
4. Return the result based on the flags.
Time Complexity:
• Best case: O(n) (when the array is already sorted)
• Worst case: O(n) (traversing through the entire array to check order)
• Average case: O(n) (linear time complexity)
Space Complexity:
• O(1) (constant space used, no extra space required apart from input variables)
Code:
# include < iostream >
using namespace std ;

void c he ck Ar r ay Or de r ( int arr [] , int n )


{
bool isAscending = true , isDescending = true ;

for ( int i = 0; i < n - 1; i ++)


{
if ( arr [ i ] > arr [ i + 1])
isAscending = false ;
if ( arr [ i ] < arr [ i + 1])
isDescending = false ;
}

8
if ( isAscending )
cout << " The array is sorted in ascending order . " << endl ;
else if ( isDescending )
cout << " The array is sorted in descending order . " << endl ;
else
cout << " The array is not sorted . " << endl ;
}

int main ()
{
int n ;

cout << " Enter the number of elements in the array : " ;
cin >> n ;

int arr [ n ];

cout << " Enter " << n << " elements of the array : " ;
for ( int i = 0; i < n ; i ++)
cin >> arr [ i ];

c he ck Ar r ay Or de r ( arr , n ) ;

return 0;
}

Output 1:
Enter the number of elements in the array: 5
Enter 5 elements of the array: 1 2 3 4 5
The array is sorted in ascending order.
Output 2:

Enter the number of elements in the array: 5


Enter 5 elements of the array: 9 7 5 3 1
The array is sorted in descending order.

1.8 Merge Sorted Arrays


Algorithm:
1. Initialize three pointers: i for the first array, j for the second array, and k for the merged array.
2. Compare the elements at arr1[i] and arr2[j]:

• If arr1[i] is smaller, assign it to merged[k] and increment i.


• If arr2[j] is smaller, assign it to merged[k] and increment j.
3. If any array still has remaining elements, copy them to the merged array.
4. Return the merged array.

Time Complexity:
• Best case: O(n + m) (when elements are evenly distributed and no additional operations are needed)
• Worst case: O(n + m) (we have to traverse all elements in both arrays)

• Average case: O(n + m) (linear time complexity)


Space Complexity:
• O(n + m) (space for the merged array)
Code:

9
# include < iostream >
using namespace std ;

void mergeArrays ( int arr1 [] , int arr2 [] , int n , int m )


{
int merged [ n + m ];
int i = 0 , j = 0 , k = 0;

while ( i < n && j < m )


{
if ( arr1 [ i ] < arr2 [ j ])
merged [ k ++] = arr1 [ i ++];
else
merged [ k ++] = arr2 [ j ++];
}

while ( i < n )
merged [ k ++] = arr1 [ i ++];

while ( j < m )
merged [ k ++] = arr2 [ j ++];

cout << " Merged array : " ;


for ( int i = 0; i < n + m ; i ++)
cout << merged [ i ] << " " ;
cout << endl ;
}

int main ()
{
int n , m ;

cout << " Enter the number of elements in the first array : " ;
cin >> n ;
int arr1 [ n ];
cout << " Enter the elements of the first array : " ;
for ( int i = 0; i < n ; i ++)
cin >> arr1 [ i ];

cout << " Enter the number of elements in the second array : " ;
cin >> m ;
int arr2 [ m ];
cout << " Enter the elements of the second array : " ;
for ( int i = 0; i < m ; i ++)
cin >> arr2 [ i ];

mergeArrays ( arr1 , arr2 , n , m ) ;

return 0;
}

Output 1:

Enter the number of elements in the first array: 3


Enter the elements of the first array: 1 4 7
Enter the number of elements in the second array: 4
Enter the elements of the second array: 2 5 6 8
Merged array: 1 2 4 5 6 7 8

Output 2:
Enter the number of elements in the first array: 2
Enter the elements of the first array: 3 10
Enter the number of elements in the second array: 3
Enter the elements of the second array: 1 5 8
Merged array: 1 3 5 8 10

1.9 Reversing an array


Algorithm:

10
1. Initialize two pointers: left at the start of the array and right at the end of the array.
2. Swap the elements at the left and right pointers.
3. Increment the left pointer and decrement the right pointer.
4. Continue swapping elements until the left pointer is no longer less than the right pointer.
5. Return the reversed array.
Time Complexity:
• Best case: O(n) (the array is reversed in a single pass)
• Worst case: O(n) (the array is reversed in a single pass)
• Average case: O(n) (linear time complexity)
Space Complexity:
• O(1) (constant space used, only two pointers are required)
Code:
# include < iostream >
using namespace std ;

void reverseArray ( int arr [] , int n )


{
int left = 0 , right = n - 1;

while ( left < right )


{
swap ( arr [ left ] , arr [ right ]) ;
left ++;
right - -;
}

cout << " Reversed array : " ;


for ( int i = 0; i < n ; i ++)
cout << arr [ i ] << " " ;
cout << endl ;
}

int main ()
{
int n ;

cout << " Enter the number of elements in the array : " ;
cin >> n ;

int arr [ n ];

cout << " Enter " << n << " elements of the array : " ;
for ( int i = 0; i < n ; i ++)
cin >> arr [ i ];

reverseArray ( arr , n ) ;

return 0;
}

Output 1:
Enter the number of elements in the array: 5
Enter 5 elements of the array: 1 2 3 4 5
Reversed array: 5 4 3 2 1
Output 2:
Enter the number of elements in the array: 4
Enter 4 elements of the array: 10 20 30 40
Reversed array: 40 30 20 10

11
1.10 Count Inversions
Algorithm:
1. An inversion is a pair of elements in the array where the first element is greater than the second element and the
first element appears before the second.
2. Use a modified merge sort algorithm to count inversions:
• While merging two halves, count how many elements in the left half are greater than elements in the right half.
• For every element in the left half that is greater than the current element in the right half, there is an inversion.
3. Recursively divide the array into two halves, count inversions in the left and right halves, and then merge them,
while counting inversions during the merge.
Time Complexity:
• Best case: O(n log n) (when the array is already partially sorted)
• Worst case: O(n log n) (divide and merge operations for each element)
• Average case: O(n log n) (merge sort complexity)
Space Complexity:
• O(n) (for the auxiliary space used during merge operations)
Code:
# include < iostream >
using namespace std ;

int mergeAndCount ( int arr [] , int temp [] , int left , int right )
{
int mid , i , j , k ;
int inv_count = 0;

if ( left < right )


{
mid = ( left + right ) / 2;
inv_count += mergeAndCount ( arr , temp , left , mid ) ;
inv_count += mergeAndCount ( arr , temp , mid + 1 , right ) ;
inv_count += merge ( arr , temp , left , mid , right ) ;
}
return inv_count ;
}

int merge ( int arr [] , int temp [] , int left , int mid , int right )
{
int i = left , j = mid + 1 , k = left , inv_count = 0;

while ( i <= mid && j <= right )


{
if ( arr [ i ] <= arr [ j ])
temp [ k ++] = arr [ i ++];
else
{
temp [ k ++] = arr [ j ++];
inv_count += ( mid - i + 1) ;
}
}

while ( i <= mid )


temp [ k ++] = arr [ i ++];

while ( j <= right )


temp [ k ++] = arr [ j ++];

for ( i = left ; i <= right ; i ++)


arr [ i ] = temp [ i ];

return inv_count ;
}

12
int main ()
{
int n ;

cout << " Enter the number of elements in the array : " ;
cin >> n ;

int arr [ n ] , temp [ n ];

cout << " Enter " << n << " elements of the array : " ;
for ( int i = 0; i < n ; i ++)
cin >> arr [ i ];

int result = mergeAndCount ( arr , temp , 0 , n - 1) ;

cout << " Number of inversions in the array : " << result << endl ;

return 0;
}

Output 1:
Enter the number of elements in the array: 5
Enter 5 elements of the array: 1 3 2 3 1
Number of inversions in the array: 4
Output 2:
Enter the number of elements in the array: 6
Enter 6 elements of the array: 5 4 3 2 1 0
Number of inversions in the array: 15

2 Stacks and Queues


2.1 Paranthesis Checker
Algorithm:
1. Initialize an empty stack.
2. Traverse each character in the given expression:
• If the character is an opening parenthesis ((, { or [), push it onto the stack.
• If the character is a closing parenthesis (), } or ]), check if the stack is empty or the top of the stack is not
the corresponding opening parenthesis. If true, return false (the parentheses are unbalanced).
3. If the stack is empty at the end of the traversal, return true (the parentheses are balanced). Otherwise, return false
(unbalanced).
Time Complexity:
• Best case: O(n) (the expression is valid and all elements are processed in one pass)
• Worst case: O(n) (all characters are processed, and the stack operations are constant time)
• Average case: O(n) (linear time complexity)
Space Complexity:
• O(n) (in the worst case, the stack may store all the opening parentheses)
Code:
# include < iostream >
# include < stack >
# include < unordered_map >
using namespace std ;

13
bool isBalanced ( string expr )
{
stack < char > s ;
unordered_map < char , char > m a t c h i n g P a r e n t h e s e s = {{ ’) ’ , ’( ’} , { ’] ’ , ’[ ’} , { ’} ’ , ’{ ’ }};

for ( char c : expr )


{
if ( c == ’( ’ || c == ’[ ’ || c == ’{ ’)
s . push ( c ) ;
else if ( c == ’) ’ || c == ’] ’ || c == ’} ’)
{
if ( s . empty () || s . top () != m a t c h i n g P a r e n t h e s e s [ c ])
return false ;
s . pop () ;
}
}
return s . empty () ;
}

int main ()
{
string expr ;

cout << " Enter the expression : " ;


cin >> expr ;

if ( isBalanced ( expr ) )
cout << " The expression is balanced . " << endl ;
else
cout << " The expression is not balanced . " << endl ;

return 0;
}

Output 1:
Enter the expression: {[(a + b) * c] / d}
The expression is balanced.
Output 2:
Enter the expression: {(a + b) * c] / d}
The expression is not balanced.

2.2 Stack using two queues


Algorithm:
1. Push Operation: Enqueue the new element into the first queue (queue1).
2. Pop Operation:
• If queue1 is empty, return an error or indicate that the stack is empty.
• Dequeue all elements except the last one from queue1 and enqueue them to the second queue (queue2).
• The last remaining element in queue1 is the top element of the stack. Dequeue this element and return it.
• Swap queue1 and queue2 after the operation, so queue1 always holds the elements of the stack.
3. Top Operation: Same as pop, but instead of dequeuing the last element, return the element at the front of queue1.
Time Complexity:
• Push Operation: O(1) (only enqueuing one element into queue1)
• Pop Operation: O(n) (since all elements in queue1 must be moved to queue2)
• Top Operation: O(n) (similar to pop, as we need to process all elements)
Space Complexity:
• O(n) (for the two queues used to simulate the stack)

14
Code:
# include < iostream >
# include < queue >
using namespace std ;

class S t a c k U s i n g Q u e u e s
{
private :
queue < int > queue1 , queue2 ;

public :
void push ( int x )
{
queue1 . push ( x ) ;
}

int pop ()
{
if ( queue1 . empty () )
{
cout << " Stack is empty ! " << endl ;
return -1;
}

while ( queue1 . size () > 1)


{
queue2 . push ( queue1 . front () ) ;
queue1 . pop () ;
}

int topElement = queue1 . front () ;


queue1 . pop () ;

swap ( queue1 , queue2 ) ;

return topElement ;
}

int top ()
{
if ( queue1 . empty () )
{
cout << " Stack is empty ! " << endl ;
return -1;
}

while ( queue1 . size () > 1)


{
queue2 . push ( queue1 . front () ) ;
queue1 . pop () ;
}

int topElement = queue1 . front () ;


queue2 . push ( queue1 . front () ) ;
swap ( queue1 , queue2 ) ;
return topElement ;
}

bool isEmpty ()
{
return queue1 . empty () ;
}
};

int main ()
{
S t a c k U s i n g Q u e ue s stack ;
int choice , value ;

while ( true )
{
cout << " \ n1 . Push \ n2 . Pop \ n3 . Top \ n4 . Check if Empty \ n5 . Exit \ n " ;
cout << " Enter your choice : " ;

15
cin >> choice ;

switch ( choice )
{
case 1:
cout << " Enter value to push : " ;
cin >> value ;
stack . push ( value ) ;
break ;
case 2:
cout << " Popped value : " << stack . pop () << endl ;
break ;
case 3:
cout << " Top value : " << stack . top () << endl ;
break ;
case 4:
cout << " Is stack empty ? " << ( stack . isEmpty () ? " Yes " : " No " ) << endl ;
break ;
case 5:
return 0;
default :
cout << " Invalid choice ! Please try again . " << endl ;
}
}

return 0;
}

Output 1:
1. Push
2. Pop
3. Top
4. Check if Empty
5. Exit
Enter your choice: 1
Enter value to push: 10

1. Push
2. Pop
3. Top
4. Check if Empty
5. Exit
Enter your choice: 1
Enter value to push: 20

1. Push
2. Pop
3. Top
4. Check if Empty
5. Exit
Enter your choice: 3
Top value: 20

1. Push
2. Pop
3. Top
4. Check if Empty
5. Exit
Enter your choice: 2
Popped value: 20

1. Push
2. Pop
3. Top

16
4. Check if Empty
5. Exit
Enter your choice: 4
Is stack empty? No

Output 2:
1. Push
2. Pop
3. Top
4. Check if Empty
5. Exit
Enter your choice: 1
Enter value to push: 5

1. Push
2. Pop
3. Top
4. Check if Empty
5. Exit
Enter your choice: 1
Enter value to push: 15

1. Push
2. Pop
3. Top
4. Check if Empty
5. Exit
Enter your choice: 2
Popped value: 15

1. Push
2. Pop
3. Top
4. Check if Empty
5. Exit
Enter your choice: 3
Top value: 5

2.3 First non repeating character


Algorithm:

1. Create an empty queue to store characters in the order they appear, and a hash map (or array) to store the frequency
of each character.
2. Traverse the string:
• For each character, increment its frequency in the hash map.
• If the character’s frequency is 1, enqueue it into the queue.
• If the character’s frequency is more than 1, ignore it.
3. After traversing the string, dequeue characters from the front of the queue until you find one with a frequency of 1.
4. Return the first non-repeating character.

5. If no such character exists, return a special value (e.g., -1 or ’ ’) to indicate that all characters are repeating.
Time Complexity:

17
• Best case: O(n) (when the first non-repeating character is found early in the queue)
• Worst case: O(n) (requires traversal of the string and queue operations)
• Average case: O(n) (linear time complexity)
Space Complexity:
• O(k) (where k is the number of unique characters in the string, assuming we use a hash map and a queue)
Code:
# include < iostream >
# include < queue >
# include < unordered_map >
# include < string >
using namespace std ;

char f i r s t N o n R e p e a t i n g C h a r a c t e r ( string str )


{
unordered_map < char , int > frequencyMap ;
queue < char > q ;

for ( char c : str )


frequencyMap [ c ]++;

for ( char c : str )


if ( frequencyMap [ c ] == 1)
q . push ( c ) ;

while (! q . empty () )
{
char front = q . front () ;
q . pop () ;
if ( frequencyMap [ front ] == 1)
return front ;
}

return ’ ’;
}

int main ()
{
string input ;
cout << " Enter a string : " ;
getline ( cin , input ) ;

char result = f i r s t N o n R e p e a t i n g C h a r a c t e r ( input ) ;


if ( result == ’ ’)
cout << " No non - repeating character found . " << endl ;
else
cout << " The first non - repeating character is : " << result << endl ;

return 0;
}

Output 1:
Enter a string: geeky
The first non-repeating character is: g
Output 2:
Enter a string: aabbcc
No non-repeating character found.

3 Linked List
3.1 Reverse a Linked List
Algorithm:

18
1. Initialize three pointers: prev, curr, and next.
2. Traverse the linked list, and for each node:

• Set the next pointer to the next node.


• Reverse the next pointer of the curr node to point to prev.
• Move prev to curr and curr to the next node.
3. After the traversal, the prev pointer will point to the new head of the reversed list.

4. Return the new head of the reversed list.


Time Complexity:
• Best case: O(n) (requires traversing the entire list once)
• Worst case: O(n) (requires a full traversal of the list)

• Average case: O(n) (linear time complexity)


Space Complexity:
• O(1) (constant space, as no extra space is used except for the pointers)

Code:
# include < iostream >
using namespace std ;

struct ListNode
{
int val ;
ListNode * next ;
ListNode ( int x ) : val ( x ) , next ( NULL ) {}
};

ListNode * reverseList ( ListNode * head )


{
ListNode * prev = NULL ;
ListNode * curr = head ;
ListNode * next = NULL ;

while ( curr != NULL )


{
next = curr - > next ;
curr - > next = prev ;
prev = curr ;
curr = next ;
}

return prev ;
}

ListNode * createNode ( int value )


{
return new ListNode ( value ) ;
}

void printList ( ListNode * head )


{
while ( head != NULL )
{
cout << head - > val << " " ;
head = head - > next ;
}
cout << endl ;
}

int main ()
{
ListNode * head = createNode (1) ;
head - > next = createNode (2) ;

19
head - > next - > next = createNode (3) ;
head - > next - > next - > next = createNode (4) ;
head - > next - > next - > next - > next = createNode (5) ;

cout << " Original Linked List : " ;


printList ( head ) ;

ListNode * reversedHead = reverseList ( head ) ;

cout << " Reversed Linked List : " ;


printList ( reversedHead ) ;

return 0;
}

Output 1:
Original Linked List: 1 2 3 4 5
Reversed Linked List: 5 4 3 2 1
Output 2:
Original Linked List: 10 20 30 40
Reversed Linked List: 40 30 20 10

3.2 Merge Two Sorted Linked Lists


Algorithm:
1. Create a dummy node that acts as the starting point for the merged list.
2. Initialize two pointers, l1 and l2, which point to the heads of the first and second sorted linked lists, respectively.
3. Compare the elements at l1 and l2:
• If l1->val is smaller, set current->next to point to l1, then move l1 to the next node.
• Otherwise, set current->next to point to l2, then move l2 to the next node.
4. After the traversal, one of the lists might still have remaining nodes. Attach the rest of the nodes from the non-empty
list to mergedArray.
5. Return the merged list.
Time Complexity:
• Best case: O(n + m) (where n and m are the lengths of the two linked lists)
• Worst case: O(n + m) (requires traversal of both lists)
• Average case: O(n + m) (linear time complexity)
Space Complexity:
• O(1) (constant space, as we are modifying the lists in place)
Code:
# include < iostream >
using namespace std ;

struct ListNode
{
int val ;
ListNode * next ;
ListNode ( int x ) : val ( x ) , next ( NULL ) {}
};

ListNode * mergeLists ( ListNode * l1 , ListNode * l2 )


{
ListNode dummy (0) ;
ListNode * current = & dummy ;

20
while ( l1 != NULL && l2 != NULL )
{
if ( l1 - > val < l2 - > val )
{
current - > next = l1 ;
l1 = l1 - > next ;
}
else
{
current - > next = l2 ;
l2 = l2 - > next ;
}
current = current - > next ;
}

if ( l1 != NULL )
current - > next = l1 ;
else
current - > next = l2 ;

return dummy . next ;


}

ListNode * createNode ( int value )


{
return new ListNode ( value ) ;
}

void printList ( ListNode * head )


{
while ( head != NULL )
{
cout << head - > val << " " ;
head = head - > next ;
}
cout << endl ;
}

int main ()
{
int n1 , n2 ;

cout << " Enter size of first linked list : " ;


cin >> n1 ;
ListNode * l1 = NULL ;
ListNode * tail1 = NULL ;
for ( int i = 0; i < n1 ; i ++)
{
int val ;
cin >> val ;
ListNode * newNode = createNode ( val ) ;
if ( l1 == NULL )
{
l1 = newNode ;
tail1 = l1 ;
}
else
{
tail1 - > next = newNode ;
tail1 = tail1 - > next ;
}
}

cout << " Enter size of second linked list : " ;


cin >> n2 ;
ListNode * l2 = NULL ;
ListNode * tail2 = NULL ;
for ( int i = 0; i < n2 ; i ++)
{
int val ;
cin >> val ;
ListNode * newNode = createNode ( val ) ;
if ( l2 == NULL )

21
{
l2 = newNode ;
tail2 = l2 ;
}
else
{
tail2 - > next = newNode ;
tail2 = tail2 - > next ;
}
}

ListNode * mergedHead = mergeLists ( l1 , l2 ) ;

cout << " Merged sorted linked list : " ;


printList ( mergedHead ) ;

return 0;
}

Output 1:
Enter size of first linked list: 4
1 3 5 7
Enter size of second linked list: 3
2 4 6
Merged sorted linked list: 1 2 3 4 5 6 7
Output 2:
Enter size of first linked list: 3
2 6 8
Enter size of second linked list: 4
1 3 5 7
Merged sorted linked list: 1 2 3 5 6 7 8

3.3 Check if Linked List is Palindrome


Algorithm:
1. Initialize a slow pointer (slow) and a fast pointer (fast), both pointing to the head of the linked list.
2. Move slow one step and fast two steps until fast reaches the end of the list. This way, slow will point to the
middle of the linked list.

3. Reverse the second half of the linked list starting from slow.
4. Compare the first half of the linked list with the reversed second half:
• Traverse from the head and compare each node with the corresponding node in the reversed second half.
5. If all nodes match, the linked list is a palindrome. Otherwise, it’s not.

6. Restore the second half of the linked list (optional, to preserve the original list).
7. Return the result.
Time Complexity:

• Best case: O(n) (if the list is already a palindrome and we traverse it once)
• Worst case: O(n) (we traverse the list once to find the middle and once to compare the two halves)
• Average case: O(n) (linear time complexity)
Space Complexity:

• O(1) (constant space, as we are modifying the list in place)


Code:

22
# include < iostream >
using namespace std ;

struct ListNode
{
int val ;
ListNode * next ;
ListNode ( int x ) : val ( x ) , next ( NULL ) {}
};

ListNode * reverseList ( ListNode * head )


{
ListNode * prev = NULL ;
ListNode * curr = head ;
ListNode * next = NULL ;

while ( curr != NULL )


{
next = curr - > next ;
curr - > next = prev ;
prev = curr ;
curr = next ;
}

return prev ;
}

bool isPalindrome ( ListNode * head )


{
if ( head == NULL || head - > next == NULL )
return true ;

ListNode * slow = head , * fast = head ;


while ( fast != NULL && fast - > next != NULL )
{
slow = slow - > next ;
fast = fast - > next - > next ;
}

slow = reverseList ( slow ) ;


ListNode * firstHalf = head ;
ListNode * secondHalf = slow ;

while ( secondHalf != NULL )


{
if ( firstHalf - > val != secondHalf - > val )
return false ;
firstHalf = firstHalf - > next ;
secondHalf = secondHalf - > next ;
}

return true ;
}

ListNode * createNode ( int value )


{
return new ListNode ( value ) ;
}

void printList ( ListNode * head )


{
while ( head != NULL )
{
cout << head - > val << " " ;
head = head - > next ;
}
cout << endl ;
}

int main ()
{
int n ;
cout << " Enter size of linked list : " ;

23
cin >> n ;

ListNode * head = NULL ;


ListNode * tail = NULL ;

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


{
int val ;
cin >> val ;
ListNode * newNode = createNode ( val ) ;
if ( head == NULL )
{
head = newNode ;
tail = head ;
}
else
{
tail - > next = newNode ;
tail = tail - > next ;
}
}

if ( isPalindrome ( head ) )
cout << " The linked list is a palindrome . " << endl ;
else
cout << " The linked list is not a palindrome . " << endl ;

return 0;
}

Output 1:
Enter size of linked list: 5
1 2 3 2 1
The linked list is a palindrome.
Output 2:
Enter size of linked list: 4
1 2 3 4
The linked list is not a palindrome.

24
Date: 5-12-24

4 Trees
4.1 Inorder Traversal
Algorithm (Recursive):
1. Start at the root node of the tree.
2. Traverse the left subtree in an inorder manner (recursively).
3. Visit the current node (process its value).

4. Traverse the right subtree in an inorder manner (recursively).


Time Complexity:
• Best case: O(n) (all nodes must be visited)

• Worst case: O(n)


• Average case: O(n)
Space Complexity:
• Recursive solution: O(h), where h is the height of the tree (due to the function call stack).

• Iterative solution: O(h), as a stack is used to simulate recursion.


Code:
# include < iostream >
# include < vector >
using namespace std ;

struct Node
{
int val ;
Node * left ;
Node * right ;
Node ( int x ) : val ( x ) , left ( NULL ) , right ( NULL ) {}
};

void inorder ( Node * root )


{
if ( root == NULL )
return ;

inorder ( root - > left ) ;


cout << root - > val << " " ;
inorder ( root - > right ) ;
}

Node * createTree ( vector < int > & nodes , int & index , int n )
{
if ( index >= n || nodes [ index ] == -1)
return NULL ;

Node * root = new Node ( nodes [ index ]) ;


index ++;
root - > left = createTree ( nodes , index , n ) ;
index ++;
root - > right = createTree ( nodes , index , n ) ;

return root ;
}

int main ()
{
int n ;

25
cout << " Enter the number of nodes in the binary tree : " ;
cin >> n ;

vector < int > nodes ( n ) ;


cout << " Enter the nodes of the binary tree ( -1 for NULL ) : " ;
for ( int i = 0; i < n ; i ++)
{
cin >> nodes [ i ];
}

int index = 0;
Node * root = createTree ( nodes , index , n ) ;

cout << " Inorder Traversal of the binary tree : " ;


inorder ( root ) ;
cout << endl ;

return 0;
}

Output 1:
Enter the number of nodes in the binary tree: 7
Enter the nodes of the binary tree (-1 for NULL): 1 2 3 -1 -1 4 5
Inorder Traversal of the binary tree: 2 1 4 3 5
Output 2:
Enter the number of nodes in the binary tree: 5
Enter the nodes of the binary tree (-1 for NULL): 10 5 15 -1 -1
Inorder Traversal of the binary tree: 5 10 15

4.2 Tree to Linked List


Algorithm (Preorder Traversal):
1. Start at the root node of the binary tree.
2. Traverse the left subtree and flatten it recursively.
3. Traverse the right subtree and flatten it recursively.
4. If the left subtree exists:
(a) Save the right subtree in a temporary variable.
(b) Attach the left subtree as the new right subtree.
(c) Move to the end of this new right subtree.
(d) Attach the saved right subtree to the end of the new right subtree.
5. Repeat this process until all nodes are flattened.
Time Complexity:
• O(n): Each node is visited once during traversal.
Space Complexity:
• O(h): Recursive function calls require stack space proportional to the height of the tree.
Code:
# include < iostream >
using namespace std ;

struct Node
{
int val ;
Node * left ;
Node * right ;

26
Node ( int x ) : val ( x ) , left ( NULL ) , right ( NULL ) {}
};

void flatten ( Node * root )


{
if ( root == NULL )
return ;

flatten ( root - > left ) ;


flatten ( root - > right ) ;

if ( root - > left != NULL )


{
Node * temp = root - > right ;
root - > right = root - > left ;
root - > left = NULL ;

Node * current = root - > right ;


while ( current - > right != NULL )
current = current - > right ;

current - > right = temp ;


}
}

void p ri nt Li n ke dL is t ( Node * root )


{
while ( root != NULL )
{
cout << root - > val << " " ;
root = root - > right ;
}
cout << endl ;
}

Node * createTree ( int nodes [] , int & index , int n )


{
if ( index >= n || nodes [ index ] == -1)
return NULL ;

Node * root = new Node ( nodes [ index ]) ;


index ++;
root - > left = createTree ( nodes , index , n ) ;
index ++;
root - > right = createTree ( nodes , index , n ) ;

return root ;
}

int main ()
{
int n ;
cout << " Enter the number of nodes in the binary tree : " ;
cin >> n ;

int nodes [ n ];
cout << " Enter the nodes of the binary tree ( -1 for NULL ) : " ;
for ( int i = 0; i < n ; i ++)
cin >> nodes [ i ];

int index = 0;
Node * root = createTree ( nodes , index , n ) ;

flatten ( root ) ;

cout << " Linked list repre sentatio n : " ;


p ri nt Li n ke dL is t ( root ) ;

return 0;
}

Output 1:
Enter the number of nodes in the binary tree: 7

27
Enter the nodes of the binary tree (-1 for NULL): 1 2 5 3 4 -1 6
Linked list representation: 1 2 3 4 5 6
Output 2:
Enter the number of nodes in the binary tree: 5
Enter the nodes of the binary tree (-1 for NULL): 10 5 15 -1 -1
Linked list representation: 10 5 15

4.3 Print Bottom View of Binary Tree


Algorithm:
1. Use a queue for level-order traversal, and a map to store the bottom view.
2. Start at the root node with horizontal distance 0.
3. Traverse each node in the tree:
(a) Update the map with the node’s value at its horizontal distance.
(b) Push the left child into the queue with horizontal distance −1 from the current node.
(c) Push the right child into the queue with horizontal distance +1 from the current node.
4. After traversal, extract and print values from the map in ascending order of horizontal distances.
Time Complexity:
• O(n): Each node is visited once, and map operations are logarithmic.
Space Complexity:
• O(n): Space required for the queue and map storage.
Code:
# include < iostream >
# include <map >
# include < queue >
using namespace std ;

struct Node
{
int val ;
Node * left ;
Node * right ;
Node ( int x ) : val ( x ) , left ( NULL ) , right ( NULL ) {}
};

void p ri nt Bo t to mV ie w ( Node * root )


{
if ( root == NULL )
{
return ;
}

map < int , int > bottomView ;


queue < pair < Node * , int > > q ;

q . push ({ root , 0}) ;

while (! q . empty () )
{
auto front = q . front () ;
q . pop () ;

Node * current = front . first ;


int hd = front . second ;

bottomView [ hd ] = current - > val ;

if ( current - > left != NULL )

28
q . push ({ current - > left , hd - 1}) ;
if ( current - > right != NULL )
q . push ({ current - > right , hd + 1}) ;
}

for ( const auto & pair : bottomView )


cout << pair . second << " " ;
cout << endl ;
}

Node * createTree ( int nodes [] , int & index , int n )


{
if ( index >= n || nodes [ index ] == -1)
return NULL ;

Node * root = new Node ( nodes [ index ]) ;


index ++;
root - > left = createTree ( nodes , index , n ) ;
index ++;
root - > right = createTree ( nodes , index , n ) ;

return root ;
}

int main ()
{
int n ;
cout << " Enter the number of nodes in the binary tree : " ;
cin >> n ;

int nodes [ n ];
cout << " Enter the nodes of the binary tree ( -1 for NULL ) : " ;
for ( int i = 0; i < n ; i ++)
cin >> nodes [ i ];

int index = 0;
Node * root = createTree ( nodes , index , n ) ;

cout << " Bottom view of the binary tree : " ;


p ri nt Bo t to mV ie w ( root ) ;

return 0;
}

Output 1:
Enter the number of nodes in the binary tree: 7
Enter the nodes of the binary tree (-1 for NULL): 20 8 22 5 3 -1 25
Bottom view of the binary tree: 5 3 22 25
Output 2:
Enter the number of nodes in the binary tree: 10
Enter the nodes of the binary tree (-1 for NULL): 1 2 3 4 5 6 7 -1 -1 8 9
Bottom view of the binary tree: 4 8 6 3 7

5 Operation On heaps
5.1 Heap Sort
Algorithm:
1. Build a max heap from the input array.
2. Swap the first element (largest) with the last element of the heap.
3. Reduce the heap size by 1 and heapify the root element to maintain the max heap property.
4. Repeat steps 2 and 3 until the heap size becomes 1.
Time Complexity:

29
• O(n log n): Building the heap takes O(n), and heapifying each element during the sorting process takes O(log n).
Space Complexity:

• O(1): In-place sorting requires no additional space.


Code:
# include < iostream >
# include < vector >
using namespace std ;

void heapify ( vector < int > & arr , int n , int i )
{
int largest = i ;
int left = 2 * i + 1;
int right = 2 * i + 2;

if ( left < n && arr [ left ] > arr [ largest ])


largest = left ;

if ( right < n && arr [ right ] > arr [ largest ])


largest = right ;

if ( largest != i )
{
swap ( arr [ i ] , arr [ largest ]) ;
heapify ( arr , n , largest ) ;
}
}

void heapSort ( vector < int > & arr )


{
int n = arr . size () ;

for ( int i = n / 2 - 1; i >= 0; i - -)


heapify ( arr , n , i ) ;

for ( int i = n - 1; i > 0; i - -)


{
swap ( arr [0] , arr [ i ]) ;
heapify ( arr , i , 0) ;
}
}

int main ()
{
int n ;
cout << " Enter the number of elements : " ;
cin >> n ;

vector < int > arr ( n ) ;


cout << " Enter the elements : " ;
for ( int i = 0; i < n ; i ++)
cin >> arr [ i ];

heapSort ( arr ) ;

cout << " Sorted array : " ;


for ( int i = 0; i < n ; i ++)
cout << arr [ i ] << " " ;
cout << endl ;

return 0;
}

Output 1:
Enter the number of elements: 5
Enter the elements: 4 10 3 5 1
Sorted array: 1 3 4 5 10
Output 2:

30
Enter the number of elements: 7
Enter the elements: 12 11 13 5 6 7 15
Sorted array: 5 6 7 11 12 13 15

5.2 Relative Sorting


Algorithm:
1. Count the frequency of each element in the first array (arr1) using a hash map.
2. Traverse the second array (arr2) to find and append elements in the relative order:
(a) For each element in arr2, add it to the result as many times as it appears in arr1.
(b) Remove the processed elements from the hash map.
3. Append the remaining elements of arr1 (not in arr2) in sorted order to the result.
Time Complexity:
• O(n+m+r log r): O(n) for counting elements in arr1, O(m) for traversing arr2, and O(r log r) for sorting remaining
elements, where n, m, r are the sizes of arr1, arr2, and remaining elements in arr1, respectively.
Space Complexity:
• O(n + m): Space for the hash map and result vector.
Code:
# include < iostream >
# include < vector >
# include < unordered_map >
# include < algorithm >
using namespace std ;

vector < int > relativeSort ( vector < int > & arr1 , vector < int > & arr2 )
{
unordered_map < int , int > freq ;
vector < int > result ;

for ( int num : arr1 )


freq [ num ]++;

for ( int num : arr2 )


{
while ( freq [ num ] > 0)
{
result . push_back ( num ) ;
freq [ num ] - -;
}
freq . erase ( num ) ;
}

vector < int > remaining ;


for ( const auto & entry : freq )
for ( int i = 0; i < entry . second ; i ++)
remaining . push_back ( entry . first ) ;
sort ( remaining . begin () , remaining . end () ) ;

result . insert ( result . end () , remaining . begin () , remaining . end () ) ;

return result ;
}

int main ()
{
int n , m ;
cout << " Enter the size of the first array : " ;
cin >> n ;
vector < int > arr1 ( n ) ;
cout << " Enter the elements of the first array : " ;
for ( int i = 0; i < n ; i ++)
cin >> arr1 [ i ];

31
cout << " Enter the size of the second array : " ;
cin >> m ;
vector < int > arr2 ( m ) ;
cout << " Enter the elements of the second array : " ;
for ( int i = 0; i < m ; i ++)
cin >> arr2 [ i ];

vector < int > result = relativeSort ( arr1 , arr2 ) ;

cout << " Resulting array after relative sorting : " ;


for ( int num : result )
cout << num << " " ;
cout << endl ;

return 0;
}

Output 1:
Enter the size of the first array: 7
Enter the elements of the first array: 2 1 2 5 7 1 9
Enter the size of the second array: 4
Enter the elements of the second array: 2 1 8 3
Resulting array after relative sorting: 2 2 1 1 5 7 9
Output 2:

Enter the size of the first array: 6


Enter the elements of the first array: 8 6 4 3 7 2
Enter the size of the second array: 3
Enter the elements of the second array: 7 4 3
Resulting array after relative sorting: 7 4 3 2 6 8

5.3 Implementing a priority queue


Algorithm:
1. Define a class PriorityQueue to manage the priority queue.

2. Use a vector to store elements internally.


3. Provide the following methods:
• push(int x): Add an element to the queue and maintain the max-heap property.
• pop(): Remove the highest-priority element (the root of the heap) and restore the heap property.
• top(): Return the highest-priority element without removing it.
4. Implement a helper function heapify(int i) to restore the max-heap property by comparing the root with its
children and swapping as necessary.
5. Ensure that the queue behaves as a max-heap, where the largest element has the highest priority.

Time Complexity:
• Push Operation: O(log n): Inserting an element involves heapify-up, which takes O(log n).
• Pop Operation: O(log n): Removing the root element involves heapify-down, which takes O(log n).

• Top Operation: O(1): Accessing the top element is O(1).


Space Complexity:
• O(n): Space is required to store n elements in the vector.
Code:

32
# include < iostream >
# include < vector >
using namespace std ;

class PriorityQueue
{
private :
vector < int > heap ;

void heapifyDown ( int i )


{
int largest = i ;
int left = 2 * i + 1;
int right = 2 * i + 2;

if ( left < heap . size () && heap [ left ] > heap [ largest ])
largest = left ;
if ( right < heap . size () && heap [ right ] > heap [ largest ])
largest = right ;

if ( largest != i )
{
swap ( heap [ i ] , heap [ largest ]) ;
heapifyDown ( largest ) ;
}
}

void heapifyUp ( int i )


{
int parent = ( i - 1) / 2;
if ( i > 0 && heap [ i ] > heap [ parent ])
{
swap ( heap [ i ] , heap [ parent ]) ;
heapifyUp ( parent ) ;
}
}

public :
void push ( int x )
{
heap . push_back ( x ) ;
heapifyUp ( heap . size () - 1) ;
}

void pop ()
{
if ( heap . empty () )
{
cout << " Priority queue is empty ! " << endl ;
return ;
}
heap [0] = heap . back () ;
heap . pop_back () ;
heapifyDown (0) ;
}

int top ()
{
if ( heap . empty () )
{
cout << " Priority queue is empty ! " << endl ;
return -1;
}
return heap [0];
}

bool empty ()
{
return heap . empty () ;
}
};

int main ()

33
{
PriorityQueue pq ;

int n , choice , value ;


cout << " Enter the number of operations : " ;
cin >> n ;

cout << " Operations :\ n1 . Push \ n2 . Pop \ n3 . Top \ n " ;


for ( int i = 0; i < n ; i ++)
{
cout << " Enter operation : " ;
cin >> choice ;

switch ( choice )
{
case 1:
cout << " Enter value to push : " ;
cin >> value ;
pq . push ( value ) ;
break ;
case 2:
pq . pop () ;
break ;
case 3:
cout << " Top element : " << pq . top () << endl ;
break ;
default :
cout << " Invalid operation ! " << endl ;
}
}

return 0;
}

Output 1:
Enter the number of operations: 6
Operations:
1. Push
2. Pop
3. Top
Enter operation: 1
Enter value to push: 10
Enter operation: 1
Enter value to push: 5
Enter operation: 1
Enter value to push: 20
Enter operation: 3
Top element: 20
Enter operation: 2
Enter operation: 3
Top element: 10
Output 2:
Enter the number of operations: 5
Operations:
1. Push
2. Pop
3. Top
Enter operation: 1
Enter value to push: 7
Enter operation: 1
Enter value to push: 15
Enter operation: 3
Top element: 15
Enter operation: 2

34
Enter operation: 3
Top element: 7

6 Implementation of Hash Table and Applications


6.1 Implementing a Hash Table
Algorithm:
1. Create a class HashTable to manage the hash table.
2. Use an array of vectors (buckets) to handle collisions using chaining.

3. Provide the following methods:


• insert(int key, int value): Compute the hash index for the key and append the key-value pair to the
corresponding bucket.
• search(int key): Compute the hash index and search the bucket for the key. Return the value if found,
otherwise indicate it is not present.
• remove(int key): Compute the hash index and delete the key-value pair from the bucket if it exists.
4. Use a simple hash function: hash = key mod size.
Time Complexity:
• Best Case: O(1): No collisions.

• Worst Case: O(n): All keys hash to the same index.


• Average Case: O(1): For a good hash function and moderate load factor.
Space Complexity:

• O(n): Space is required to store n key-value pairs in the hash table.


Code:
# include < iostream >
# include < vector >
# include < list >
using namespace std ;

class HashTable
{
private :
vector < list < pair < int , int > > > table ;
int size ;

int hashFunction ( int key )


{
return key % size ;
}

public :
HashTable ( int s )
{
size = s ;
table . resize ( size ) ;
}

void insert ( int key , int value )


{
int index = hashFunction ( key ) ;
for ( auto & pair : table [ index ])
{
if ( pair . first == key )
{
pair . second = value ;
return ;

35
}
}
table [ index ]. emplace_back ( key , value ) ;
}

int search ( int key )


{
int index = hashFunction ( key ) ;
for ( auto & pair : table [ index ])
if ( pair . first == key )
return pair . second ;
return -1;
}

void remove ( int key )


{
int index = hashFunction ( key ) ;
for ( auto it = table [ index ]. begin () ; it != table [ index ]. end () ; ++ it )
{
if ( it - > first == key )
{
table [ index ]. erase ( it ) ;
return ;
}
}
cout << " Key not found ! " << endl ;
}

void display ()
{
for ( int i = 0; i < size ; i ++)
{
cout << " Bucket " << i << " : " ;
for ( auto & pair : table [ i ])
cout << " ( " << pair . first << " , " << pair . second << " ) " ;
cout << endl ;
}
}
};

int main ()
{
int size , choice , key , value ;
cout << " Enter the size of the hash table : " ;
cin >> size ;

HashTable ht ( size ) ;

cout << " Operations :\ n1 . Insert \ n2 . Search \ n3 . Remove \ n4 . Display \ n " ;


do
{
cout << " Enter your choice (0 to exit ) : " ;
cin >> choice ;

switch ( choice )
{
case 1:
cout << " Enter key and value to insert : " ;
cin >> key >> value ;
ht . insert ( key , value ) ;
break ;
case 2:
cout << " Enter key to search : " ;
cin >> key ;
value = ht . search ( key ) ;
if ( value != -1)
cout << " Value : " << value << endl ;
else
cout << " Key not found ! " << endl ;
break ;
case 3:
cout << " Enter key to remove : " ;
cin >> key ;
ht . remove ( key ) ;

36
break ;
case 4:
ht . display () ;
break ;
case 0:
cout << " Exiting . " << endl ;
break ;
default :
cout << " Invalid choice ! " << endl ;
}
} while ( choice != 0) ;

return 0;
}

Output 1:
Enter the size of the hash table: 5
Operations:
1. Insert
2. Search
3. Remove
4. Display
Enter your choice (0 to exit): 1
Enter key and value to insert: 10 100
Enter your choice (0 to exit): 1
Enter key and value to insert: 15 200
Enter your choice (0 to exit): 2
Enter key to search: 10
Value: 100
Enter your choice (0 to exit): 4
Bucket 0:
Bucket 1:
Bucket 2:
Bucket 3:
Bucket 4: (10, 100) (15, 200)
Enter your choice (0 to exit): 0
Exiting.

Output 2:
Enter the size of the hash table: 7
Operations:
1. Insert
2. Search
3. Remove
4. Display
Enter your choice (0 to exit): 1
Enter key and value to insert: 5 50
Enter your choice (0 to exit): 1
Enter key and value to insert: 12 120
Enter your choice (0 to exit): 4
Bucket 0:
Bucket 1:
Bucket 2:
Bucket 3:
Bucket 4:
Bucket 5: (5, 50)
Bucket 6: (12, 120)
Enter your choice (0 to exit): 3
Enter key to remove: 5
Enter your choice (0 to exit): 4
Bucket 0:

37
Bucket 1:
Bucket 2:
Bucket 3:
Bucket 4:
Bucket 5:
Bucket 6: (12, 120)
Enter your choice (0 to exit): 0
Exiting.

6.2 Sorting elements of an array by frequency


Algorithm:
1. Create a frequency map to count the occurrences of each element in the array.
2. Store elements in a vector of pairs, where each pair contains an element and its frequency.
3. Sort the vector of pairs based on the following criteria:
• Higher frequency comes first.
• If frequencies are the same, sort by the element value in ascending order.
4. Reconstruct the sorted array using the sorted vector of pairs.
5. Output the sorted array.
Time Complexity:
• O(n log n):
– Building the frequency map takes O(n).
– Sorting the vector of pairs takes O(n log n), where n is the number of distinct elements.
• Overall: O(n log n).
Space Complexity:
• O(n): Space for the frequency map and the sorted vector.
Code:
# include < iostream >
# include < vector >
# include < unordered_map >
# include < algorithm >
using namespace std ;

bool comparator ( const pair < int , int > &a , const pair < int , int > & b )
{
if ( a . second == b . second )
return a . first < b . first ;
return a . second > b . second ;
}

void s or tB yF r eq ue nc y ( vector < int > & arr )


{
unordered_map < int , int > freqMap ;

for ( int num : arr )


freqMap [ num ]++;

vector < pair < int , int > > freqVec ( freqMap . begin () , freqMap . end () ) ;
sort ( freqVec . begin () , freqVec . end () , comparator ) ;

vector < int > sortedArr ;


for ( const auto & pair : freqVec )
for ( int i = 0; i < pair . second ; i ++)
sortedArr . push_back ( pair . first ) ;

arr = sortedArr ;

38
}

int main ()
{
int n ;
cout << " Enter the number of elements : " ;
cin >> n ;

vector < int > arr ( n ) ;


cout << " Enter the elements : " ;
for ( int i = 0; i < n ; i ++)
cin >> arr [ i ];

s or tB yF r eq ue nc y ( arr ) ;

cout << " Sorted array by frequency : " ;


for ( int num : arr )
cout << num << " " ;
cout << endl ;

return 0;
}

Output 1:
Enter the number of elements: 8
Enter the elements: 4 5 6 5 4 3 3 3
Sorted array by frequency: 3 3 3 4 4 5 5 6
Output 2:
Enter the number of elements: 7
Enter the elements: 1 2 3 2 1 1 4
Sorted array by frequency: 1 1 1 2 2 3 4

6.3 Longest Consecutive Subsequence


Algorithm:
1. Use a hash set to store all elements of the array for O(1) lookup.
2. Iterate through the array, treating each element as a potential starting point of a sequence:
• If the current element minus one (x − 1) is not in the set, it marks the start of a new sequence.
3. For each starting point, count the length of the consecutive subsequence by checking subsequent elements (x + 1, x +
2, . . .) in the set.
4. Track the maximum sequence length encountered during the traversal.
5. Return the maximum length.
Time Complexity:
• O(n):
– Each element is added to the set once and processed at most twice (once as a starting point and during sequence
extension).
Space Complexity:
• O(n): Space for the hash set.
Code:
# include < iostream >
# include < unordered_set >
# include < vector >
using namespace std ;

int l o n g e s t C o n s e c u t i v e S u b s e q u e n c e ( vector < int > & arr )

39
{
unordered_set < int > elements ( arr . begin () , arr . end () ) ;
int longest = 0;

for ( int num : arr )


{
if ( elements . find ( num - 1) == elements . end () )
{
int currentNum = num ;
int currentStreak = 1;
while ( elements . find ( currentNum + 1) != elements . end () )
{
currentNum ++;
currentStreak ++;
}
longest = max ( longest , currentStreak ) ;
}
}
return longest ;
}

int main ()
{
int n ;
cout << " Enter the number of elements : " ;
cin >> n ;

vector < int > arr ( n ) ;


cout << " Enter the elements : " ;
for ( int i = 0; i < n ; i ++)
cin >> arr [ i ];

int result = l o n g e s t C o n s e c u t i v e S u b s e q u e n c e ( arr ) ;


cout << " Length of the longest consecutive subsequence : " << result << endl ;

return 0;
}

Output 1:
Enter the number of elements: 6
Enter the elements: 100 4 200 1 3 2
Length of the longest consecutive subsequence: 4
Output 2:
Enter the number of elements: 7
Enter the elements: 2 6 1 9 4 3 5
Length of the longest consecutive subsequence: 6

6.4 Check if two arrays are equal or not


Algorithm:

1. Check if the lengths of the two arrays are different. If they are, the arrays are not equal.
2. Use a hash map to count the frequency of each element in the first array.
3. Traverse the second array and decrement the frequency of each element in the hash map:
• If an element is not found or its count goes below zero, the arrays are not equal.

4. After processing the second array, check if all values in the hash map are zero. If they are, the arrays are equal.
5. Return the result.
Time Complexity:

• O(n):
– Building the frequency map takes O(n).

40
– Traversing the second array and checking the map also takes O(n).
Space Complexity:
• O(n): Space for the hash map.
Code:
# include < iostream >
# include < vector >
# include < unordered_map >
using namespace std ;

bool areArray sEqual ( vector < int > & arr1 , vector < int > & arr2 )
{
if ( arr1 . size () != arr2 . size () )
return false ;

unordered_map < int , int > freqMap ;


for ( int num : arr1 )
freqMap [ num ]++;

for ( int num : arr2 )


{
if ( freqMap . find ( num ) == freqMap . end () || freqMap [ num ] == 0)
return false ;
freqMap [ num ] - -;
}
return true ;
}

int main ()
{
int n1 , n2 ;
cout << " Enter the size of the first array : " ;
cin >> n1 ;
vector < int > arr1 ( n1 ) ;
cout << " Enter elements of the first array : " ;
for ( int i = 0; i < n1 ; i ++)
cin >> arr1 [ i ];

cout << " Enter the size of the second array : " ;
cin >> n2 ;
vector < int > arr2 ( n2 ) ;
cout << " Enter elements of the second array : " ;
for ( int i = 0; i < n2 ; i ++)
cin >> arr2 [ i ];

if ( areArray sEqual ( arr1 , arr2 ) )


cout << " The arrays are equal . " << endl ;
else
cout << " The arrays are not equal . " << endl ;

return 0;
}

Output 1:
Enter the size of the first array: 5
Enter elements of the first array: 1 2 3 4 5
Enter the size of the second array: 5
Enter elements of the second array: 5 4 3 2 1
The arrays are equal.
Output 2:
Enter the size of the first array: 4
Enter elements of the first array: 1 2 3 4
Enter the size of the second array: 4
Enter elements of the second array: 1 2 2 4
The arrays are not equal.

41
Date: 12-12-2024

7 Sorting
7.1 Insertion Sort
Algorithm:
1. Iterate through the array from the second element to the last element.
2. For each element, store its value in a temporary variable.
3. Compare the temporary value with elements in the sorted portion of the array (elements to its left):

• Shift larger elements one position to the right.


4. Insert the temporary value into its correct position in the sorted portion.
5. Repeat until the entire array is sorted.

Time Complexity:
• Best Case: O(n): When the array is already sorted, only n − 1 comparisons are needed.
• Worst Case: O(n2 ): When the array is sorted in reverse order, the inner loop runs for every element.
• Average Case: O(n2 ): On average, elements are halfway out of place.

Space Complexity:
• O(1): Sorting is done in-place, requiring no extra memory.
Code:
# include < iostream >
# include < vector >
using namespace std ;

void insertionSort ( vector < int >& arr )


{
int n = arr . size () ;
for ( int i = 1; i < n ; i ++)
{
int key = arr [ i ];
int j = i - 1;

while ( j >= 0 && arr [ j ] > key )


{
arr [ j + 1] = arr [ j ];
j - -;
}
arr [ j + 1] = key ;
}
}

int main ()
{
int n ;
cout << " Enter the number of elements : " ;
cin >> n ;

vector < int > arr ( n ) ;


cout << " Enter the elements : " ;
for ( int i = 0; i < n ; i ++)
cin >> arr [ i ];

insertionSort ( arr ) ;

cout << " Sorted array : " ;


for ( int num : arr )
cout << num << " " ;

42
cout << endl ;
return 0;
}

Output 1:
Enter the number of elements: 5
Enter the elements: 12 11 13 5 6
Sorted array: 5 6 11 12 13
Output 2:
Enter the number of elements: 4
Enter the elements: 20 10 40 30
Sorted array: 10 20 30 40

7.2 Quick Sort


Algorithm:
1. Partition the Array (Hoare’s Partitioning):
• Select a pivot element (typically the first element).
• Use two pointers:
– One pointer starts from the left and moves right until an element greater than or equal to the pivot is
found.
– The other pointer starts from the right and moves left until an element less than or equal to the pivot is
found.
• Swap the elements at the two pointers and continue until the pointers cross.
• Return the index where the pointers crossed.
2. Recursive Quick Sort:
• Apply the partitioning scheme to divide the array into two parts.
• Recursively sort the left and right parts.
Time Complexity:
• Best Case: O(n log n): When the pivot divides the array into two nearly equal parts.
• Worst Case: O(n2 ): When the pivot always picks the largest or smallest element.
• Average Case: O(n log n): On average, the partitioning is balanced.
Space Complexity:
• O(log n): Recursive stack space.
Code:
# include < iostream >
# include < vector >
using namespace std ;

int hoarePa rtition ( vector < int >& arr , int low , int high ) {
int pivot = arr [ low ];
int i = low - 1;
int j = high + 1;

while ( true ) {
do {
i ++;
} while ( arr [ i ] < pivot ) ;

do {
j - -;

43
} while ( arr [ j ] > pivot ) ;

if ( i >= j ) {
return j ;
}
swap ( arr [ i ] , arr [ j ]) ;
}
}

void quickSort ( vector < int >& arr , int low , int high ) {
if ( low < high ) {
int p = hoar ePartit ion ( arr , low , high ) ;
quickSort ( arr , low , p ) ;
quickSort ( arr , p + 1 , high ) ;
}
}

int main () {
int n ;
cout << " Enter the number of elements : " ;
cin >> n ;

vector < int > arr ( n ) ;


cout << " Enter the elements : " ;
for ( int i = 0; i < n ; i ++)
cin >> arr [ i ];

quickSort ( arr , 0 , n - 1) ;

cout << " Sorted array : " ;


for ( int num : arr )
cout << num << " " ;
cout << endl ;

return 0;
}

Output 1:

Enter the number of elements: 6


Enter the elements: 10 7 8 9 1 5
Sorted array: 1 5 7 8 9 10
Output 2:

Enter the number of elements: 5


Enter the elements: 50 20 40 30 10
Sorted array: 10 20 30 40 50

7.3 Merge Sort


Algorithm:
1. Divide: Recursively split the array into two halves until each subarray has only one element.
2. Conquer: Merge the two sorted halves by comparing elements and placing them in sorted order.

3. Combine: Continue merging until the entire array is sorted.


Time Complexity:
• Best Case: O(n log n): Recursion splits the array into halves (log n) and merges them (O(n)).
• Worst Case: O(n log n): Same as the best case due to consistent splitting and merging.

• Average Case: O(n log n): Same as above.


Space Complexity:
• O(n): Additional space for temporary arrays during merging.

44
Code:
# include < iostream >
# include < vector >
using namespace std ;

void merge ( vector < int >& arr , int left , int mid , int right ) {
int n1 = mid - left + 1;
int n2 = right - mid ;

vector < int > leftArr ( n1 ) , rightArr ( n2 ) ;

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


leftArr [ i ] = arr [ left + i ];
for ( int i = 0; i < n2 ; i ++)
rightArr [ i ] = arr [ mid + 1 + i ];

int i = 0 , j = 0 , k = left ;
while ( i < n1 && j < n2 )
{
if ( leftArr [ i ] <= rightArr [ j ])
arr [ k ++] = leftArr [ i ++];
else
arr [ k ++] = rightArr [ j ++];
}

while ( i < n1 )
arr [ k ++] = leftArr [ i ++];

while ( j < n2 )
arr [ k ++] = rightArr [ j ++];
}

void mergeSort ( vector < int >& arr , int left , int right ) {
if ( left < right ) {
int mid = left + ( right - left ) / 2;

mergeSort ( arr , left , mid ) ;


mergeSort ( arr , mid + 1 , right ) ;
merge ( arr , left , mid , right ) ;
}
}

int main () {
int n ;
cout << " Enter the number of elements : " ;
cin >> n ;

vector < int > arr ( n ) ;


cout << " Enter the elements : " ;
for ( int i = 0; i < n ; i ++)
cin >> arr [ i ];

mergeSort ( arr , 0 , n - 1) ;

cout << " Sorted array : " ;


for ( int num : arr )
cout << num << " " ;

cout << endl ;

return 0;
}

Output 1:
Enter the number of elements: 5
Enter the elements: 12 11 13 5 6
Sorted array: 5 6 11 12 13
Output 2:
Enter the number of elements: 6
Enter the elements: 38 27 43 3 9 82

45
Sorted array: 3 9 27 38 43 82

8 Recursion
8.1 Fibonacci Sequence using recursion
Algorithm:
1. Define a recursive function fibonacci(n):
• Base cases:
– If n = 0, return 0.
– If n = 1, return 1.
• Recursive case: Return the sum of fibonacci(n - 1) and fibonacci(n - 2).
2. In the main function:
• Take the number of terms (n) as input.
• Print the Fibonacci sequence by calling the fibonacci function iteratively for indices 0 to n − 1.
Time Complexity:
• O(2n ): Each call splits into two further calls, resulting in exponential growth of computations.
Space Complexity:
• O(n): Due to recursive function call stack.
Code:
# include < iostream >
using namespace std ;

int fibonacci ( int n )


{
if ( n == 0)
return 0;
if ( n == 1)
return 1;
return fibonacci ( n - 1) + fibonacci ( n - 2) ;
}

int main ()
{
int n ;
cout << " Enter the number of terms : " ;
cin >> n ;

if ( n <= 0)
{
cout << " Number of terms must be positive . " << endl ;
return 0;
}

cout << " Fibonacci Sequence : " ;


for ( int i = 0; i < n ; i ++)
cout << fibonacci ( i ) << " " ;
cout << endl ;

return 0;
}

Output 1:
Enter the number of terms: 5
Fibonacci Sequence: 0 1 1 2 3
Output 2:
Enter the number of terms: 8
Fibonacci Sequence: 0 1 1 2 3 5 8 13

46
8.2 Fibonacci Sequence using Memo Table
Algorithm:

1. Create a memo table (array) to store Fibonacci numbers, initialized with -1.
2. Define a recursive function fibonacci(n):
• Base cases:
– If n = 0, return 0.
– If n = 1, return 1.
• If the Fibonacci number for n is already computed (i.e., memo[n] != -1), return memo[n].
• Otherwise, compute fibonacci(n - 1) + fibonacci(n - 2), store it in memo[n], and return the result.
3. In the main function:
• Take the number of terms (n) as input.
• Print the Fibonacci sequence by calling the fibonacci function iteratively for indices 0 to n − 1.

Time Complexity:
• O(n): Each Fibonacci number is computed only once and stored in the memo table.
Space Complexity:

• O(n): For the memo table and the recursion stack.


Code:
# include < iostream >
# include < vector >
using namespace std ;

int fibonacci ( int n , vector < int > & memo )


{
if ( n == 0)
return 0;
if ( n == 1)
return 1;

if ( memo [ n ] != -1)
return memo [ n ];

memo [ n ] = fibonacci ( n - 1 , memo ) + fibonacci ( n - 2 , memo ) ;


return memo [ n ];
}

int main ()
{
int n ;
cout << " Enter the number of terms : " ;
cin >> n ;

if ( n <= 0)
{
cout << " Number of terms must be positive . " << endl ;
return 0;
}

vector < int > memo (n , -1) ;

cout << " Fibonacci Sequence : " ;


for ( int i = 0; i < n ; i ++)
cout << fibonacci (i , memo ) << " " ;
cout << endl ;

return 0;
}

Output 1:

47
Enter the number of terms: 5
Fibonacci Sequence: 0 1 1 2 3

Output 2:
Enter the number of terms: 8
Fibonacci Sequence: 0 1 1 2 3 5 8 13

48
Date: 19-12-2024

8.3 Tower of Hanoi


Algorithm:
1. Define a recursive function towerOfHanoi(n, source, destination, auxiliary):
• Base Case: If n = 1, move the disk from the source rod to the destination rod.
• Recursive Case:
– Move n − 1 disks from the source rod to the auxiliary rod, using the destination rod as a helper.
– Move the nth disk from the source rod to the destination rod.
– Move n − 1 disks from the auxiliary rod to the destination rod, using the source rod as a helper.

2. In the main function:


• Take the number of disks (n) as input.
• Call the recursive function to display the steps.
Time Complexity:

• O(2n − 1): Each recursive call splits into two smaller recursive calls, doubling the work with each additional disk.
Space Complexity:
• O(n): Due to the recursive call stack.
Code:
# include < iostream >
using namespace std ;

void towerOfHanoi ( int n , char source , char destination , char auxiliary )


{
if ( n == 1)
{
cout << " Move disk 1 from " << source << " to " << destination << endl ;
return ;
}
towerOfHanoi ( n - 1 , source , auxiliary , destination ) ;
cout << " Move disk " << n << " from " << source << " to " << destination << endl ;
towerOfHanoi ( n - 1 , auxiliary , destination , source ) ;
}

int main ()
{
int n ;
cout << " Enter the number of disks : " ;
cin >> n ;

if ( n <= 0)
{
cout << " Number of disks must be positive . " << endl ;
return 0;
}

cout << " The sequence of moves to solve Tower of Hanoi is :\ n " ;
towerOfHanoi (n , ’A ’ , ’C ’ , ’B ’) ;
return 0;
}

Output 1:
Enter the number of disks: 2
The sequence of moves to solve Tower of Hanoi is:
Move disk 1 from A to B
Move disk 2 from A to C
Move disk 1 from B to C

49
Output 2:
Enter the number of disks: 3
The sequence of moves to solve Tower of Hanoi is:
Move disk 1 from A to C
Move disk 2 from A to B
Move disk 1 from C to B
Move disk 3 from A to C
Move disk 1 from B to A
Move disk 2 from B to C
Move disk 1 from A to C

8.4 Sorting Array Elements using Recursion


Algorithm:
1. Define a recursive function sumArray(arr, n):
• Base Case: If the size of the array (n) is 0, return 0.
• Recursive Case: Return the sum of the last element and the sum of the first n − 1 elements:

sumArray(arr, n) = arr[n-1] + sumArray(arr, n-1).

2. In the main function:


• Take the size of the array (n) and its elements as input.
• Call the recursive function sumArray(arr, n).
• Display the sum.
Time Complexity:
• O(n): The function is called recursively n times, where n is the size of the array.
Space Complexity:
• O(n): Due to the recursive call stack.
Code:
# include < iostream >
# include < vector >
using namespace std ;

int sumArray ( const vector < int >& arr , int n ) {


if ( n == 0)
return 0;
return arr [ n - 1] + sumArray ( arr , n - 1) ;
}

int main () {
int n ;
cout << " Enter the number of elements : " ;
cin >> n ;

if ( n <= 0)
{
cout << " Number of elements must be positive . " << endl ;
return 0;
}

vector < int > arr ( n ) ;


cout << " Enter the elements : " ;
for ( int i = 0; i < n ; i ++)
cin >> arr [ i ];

int sum = sumArray ( arr , n ) ;


cout << " Sum of array elements : " << sum << endl ;
return 0;
}

50
Output 1:
Enter the number of elements: 5
Enter the elements: 1 2 3 4 5
Sum of array elements: 15
Output 2:
Enter the number of elements: 4
Enter the elements: 10 20 30 40
Sum of array elements: 100

9 Graphs
9.1 Depth First Traversal
Algorithm:
1. Use a recursive function to perform Depth First Search (DFS).
2. Define a function DFS(node, visited, graph):
• Base Case: If the node has already been visited, return.
• Recursive Case:
(a) Mark the current node as visited.
(b) Print the current node or store it in a result list.
(c) Recursively call DFS for all unvisited neighbors of the current node.
3. For disconnected graphs:
• Iterate through all nodes and perform DFS for each unvisited node.
Time Complexity:
• O(V + E): V is the number of vertices, and E is the number of edges. Each vertex and edge is visited once.
Space Complexity:
• O(V ): Due to the recursion stack and visited array.
Code:
# include < iostream >
# include < vector >
# include < list >
using namespace std ;

void DFS ( int node , vector < bool > & visited , const vector < list < int > > & graph )
{
visited [ node ] = true ;
cout << node << " " ;

for ( int neighbor : graph [ node ])


if (! visited [ neighbor ])
DFS ( neighbor , visited , graph ) ;
}

int main ()
{
int V , E ;
cout << " Enter the number of vertices : " ;
cin >> V ;
cout << " Enter the number of edges : " ;
cin >> E ;

vector < list < int > > graph ( V ) ;


cout << " Enter the edges ( u v ) : " << endl ;
for ( int i = 0; i < E ; i ++)

51
{
int u , v ;
cin >> u >> v ;
graph [ u ]. push_back ( v ) ;
graph [ v ]. push_back ( u ) ;
}

vector < bool > visited (V , false ) ;

cout << " Depth First Traversal starting from node 0: " ;
for ( int i = 0; i < V ; i ++)
if (! visited [ i ])
DFS (i , visited , graph ) ;
cout << endl ;

return 0;
}

Output 1:
Enter the number of vertices: 5
Enter the number of edges: 4
Enter the edges (u v):
0 1
0 2
1 3
1 4
Depth First Traversal starting from node 0: 0 1 3 4 2
Output 2:
Enter the number of vertices: 6
Enter the number of edges: 5
Enter the edges (u v):
0 1
0 2
1 3
3 4
4 5
Depth First Traversal starting from node 0: 0 1 3 4 5 2

52
Date: 26-12-2024

9.2 Shortest source to destination path


Algorithm:
1. Represent the graph using an adjacency matrix.
2. Maintain a distance array initialized to infinity (∞) for all nodes except the source (set to 0).
3. Use a visited array to keep track of processed nodes.
4. Select the node with the smallest tentative distance that hasn’t been visited.
5. Update the distances to its neighbors if a shorter path is found.
6. Mark the node as visited and repeat until all nodes are processed.
7. Output the distance array.
Time Complexity:
• O(V 2 ): Due to the adjacency matrix and nested loops for processing V nodes.
Space Complexity:
• O(V 2 ): For the adjacency matrix and arrays.
Code:
# include < iostream >
# include < limits >
using namespace std ;

const int INF = numeric_limits < int >:: max () ;

int f in dM in D is ta nc e ( int distance [] , bool visited [] , int V )


{
int minDist = INF , minIndex = -1;
for ( int i = 0; i < V ; i ++)
if (! visited [ i ] && distance [ i ] < minDist )
{
minDist = distance [ i ];
minIndex = i ;
}
return minIndex ;
}

void dijkstra ( int graph [100][100] , int V , int source )


{
int distance [ V ];
bool visited [ V ] = { false };

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


distance [ i ] = INF ;
distance [ source ] = 0;

for ( int count = 0; count < V - 1; count ++)


{
int u = fi nd Mi n Di st an c e ( distance , visited , V ) ;
visited [ u ] = true ;

for ( int v = 0; v < V ; v ++)


if (! visited [ v ] && graph [ u ][ v ] && distance [ u ] != INF && distance [ u ] + graph [ u ][ v ] <
distance [ v ])
distance [ v ] = distance [ u ] + graph [ u ][ v ];
}

cout << " Shortest distances from source " << source << " : " << endl ;
for ( int i = 0; i < V ; i ++)
if ( distance [ i ] == INF )
cout << " Vertex " << i << " : INF " << endl ;
else

53
cout << " Vertex " << i << " : " << distance [ i ] << endl ;
}

int main ()
{
int V , E ;
cout << " Enter the number of vertices : " ;
cin >> V ;
cout << " Enter the number of edges : " ;
cin >> E ;

int graph [100][100] = {0};


cout << " Enter the edges (u , v , weight ) : " << endl ;
for ( int i = 0; i < E ; i ++)
{
int u , v , weight ;
cin >> u >> v >> weight ;
graph [ u ][ v ] = weight ;
graph [ v ][ u ] = weight ; // For u n d i r e c t e d graph
}

int source ;
cout << " Enter the source vertex : " ;
cin >> source ;

dijkstra ( graph , V , source ) ;

return 0;
}

Output 1:
Enter the number of vertices: 5
Enter the number of edges: 6
Enter the edges (u, v, weight):
0 1 2
0 2 4
1 2 1
1 3 7
2 4 3
3 4 1
Enter the source vertex: 0
Shortest distances from source 0:
Vertex 0: 0
Vertex 1: 2
Vertex 2: 3
Vertex 3: 9
Vertex 4: 6
Output 2:
Enter the number of vertices: 4
Enter the number of edges: 4
Enter the edges (u, v, weight):
0 1 1
1 2 2
0 2 4
2 3 1
Enter the source vertex: 0
Shortest distances from source 0:
Vertex 0: 0
Vertex 1: 1
Vertex 2: 3
Vertex 3: 4

54
9.3 Cycle Detection: Undirected Graph
Algorithm:
1. Represent the graph using an adjacency matrix graph.
2. Use a visited array to track visited nodes.
3. Perform DFS:
• If a visited node is encountered that is not the parent, a cycle is detected.
4. Repeat the process for all components of the graph.
Time Complexity:
• O(V 2 ): Adjacency matrix requires checking all nodes for edges.
Space Complexity:
• O(V ): Due to the visited and recStack arrays.
Code:
# include < iostream >
# include < cstring >
using namespace std ;

const int MAX = 100;

bool dfsUndirected ( int node , int parent , int graph [ MAX ][ MAX ] , bool visited [] , int n )
{
visited [ node ] = true ;

for ( int neighbor = 0; neighbor < n ; neighbor ++)


if ( graph [ node ][ neighbor ])
{
if (! visited [ neighbor ])
{
if ( dfsUndirected ( neighbor , node , graph , visited , n ) )
return true ;
}
else if ( neighbor != parent )
return true ;
}
return false ;
}

bool h a s C y c l e U n d i r e c t e d ( int n , int graph [ MAX ][ MAX ])


{
bool visited [ MAX ];
memset ( visited , false , sizeof ( visited ) ) ;

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


if (! visited [ i ])
if ( dfsUndirected (i , -1 , graph , visited , n ) )
return true ;
return false ;
}

int main ()
{
int n , m ;
cout << " Enter the number of nodes and edges : " ;
cin >> n >> m ;

int graph [ MAX ][ MAX ] = {0};


cout << " Enter the edges ( u v ) : " << endl ;
for ( int i = 0; i < m ; i ++)
{
int u , v ;
cin >> u >> v ;
graph [ u ][ v ] = graph [ v ][ u ] = 1;
}

55
if ( h a s C y c l e U n d i r e c t e d (n , graph ) )
cout << " Cycle detected in the undirected graph . " << endl ;
else
cout << " No cycle detected in the undirected graph . " << endl ;

return 0;
}

Output 1:
Enter the number of nodes and edges: 4 3
Enter the edges (u v):
0 1
1 2
2 0
Cycle detected in the undirected graph.
Output 2:
Enter the number of nodes and edges: 4 3
Enter the edges (u v):
0 1
1 2
2 3
No cycle detected in the undirected graph.

9.4 Cycle Detection: Directed Graph


Algorithm:
1. Represent the graph using an adjacency matrix graph.
2. Use two arrays:
• visited to track visited nodes.
• recStack to track nodes in the current recursion stack.
3. Perform DFS:
• If a node in the recursion stack is encountered, a cycle is detected.
Time Complexity:
• O(V 2 ): Adjacency matrix requires checking all nodes for edges.
Space Complexity:
• O(V ): Due to the visited and recStack arrays.
Code:
# include < iostream >
# include < cstring >
using namespace std ;

const int MAX = 100;

bool dfsDirected ( int node , int graph [ MAX ][ MAX ] , bool visited [] , bool recStack [] , int n )
{
visited [ node ] = true ;
recStack [ node ] = true ;

for ( int neighbor = 0; neighbor < n ; neighbor ++)


if ( graph [ node ][ neighbor ])
{
if (! visited [ neighbor ])
{
if ( dfsDirected ( neighbor , graph , visited , recStack , n ) )

56
{
return true ;
}
}
else if ( recStack [ neighbor ])
return true ;
}

recStack [ node ] = false ;


return false ;
}

bool h a s C y c l e D i r e c t e d ( int n , int graph [ MAX ][ MAX ])


{
bool visited [ MAX ];
bool recStack [ MAX ];
memset ( visited , false , sizeof ( visited ) ) ;
memset ( recStack , false , sizeof ( recStack ) ) ;

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


if (! visited [ i ])
if ( dfsDirected (i , graph , visited , recStack , n ) )
return true ;
return false ;
}

int main ()
{
int n , m ;
cout << " Enter the number of nodes and edges : " ;
cin >> n >> m ;

int graph [ MAX ][ MAX ] = {0};


cout << " Enter the edges ( u v ) : " << endl ;
for ( int i = 0; i < m ; i ++)
{
int u , v ;
cin >> u >> v ;
graph [ u ][ v ] = 1;
}

if ( h a s C y c l e D i r e c t e d (n , graph ) )
cout << " Cycle detected in the directed graph . " << endl ;
else
cout << " No cycle detected in the directed graph . " << endl ;

return 0;
}

Output 1:

Enter the number of nodes and edges: 4 4


Enter the edges (u v):
0 1
1 2
2 3
3 1
Cycle detected in the directed graph.
Output 2:
Enter the number of nodes and edges: 4 4
Enter the edges (u v):
0 1
1 2
2 3
3 4
No cycle detected in the directed graph.

57
10 Dynamic Programming
10.1 Binomial Co efficient
Algorithm:
1. Define a DP table dp[n + 1][k + 1]:

dp[i][j] stores C(i, j), the number of ways to choose j items from i items.

2. Initialize the base cases:


• C(i, 0) = 1 (choosing 0 items from i items).
• C(i, i) = 1 (choosing all i items from i items).
3. Use the recurrence relation:
C(i, j) = C(i − 1, j − 1) + C(i − 1, j)
• C(i − 1, j − 1): Include the current item.
• C(i − 1, j): Exclude the current item.
4. Fill the DP table iteratively for all i from 0 to n and j from 0 to min(i, k).
5. Return dp[n][k].
Time Complexity:
• O(n · k): Each cell in the DP table is computed once.
Space Complexity:
• O(n · k): To store the DP table.
Code:
# include < iostream >
# include < cstring >
using namespace std ;

const int MAX = 100;

int b i n o m i a l C o e f f i c i e n t ( int n , int k )


{
int dp [ MAX ][ MAX ];
memset ( dp , 0 , sizeof ( dp ) ) ;

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


{
dp [ i ][0] = 1;
if ( i <= k )
{
dp [ i ][ i ] = 1;
}
}

for ( int i = 1; i <= n ; i ++)


for ( int j = 1; j <= min (i , k ) ; j ++)
dp [ i ][ j ] = dp [ i - 1][ j - 1] + dp [ i - 1][ j ];

return dp [ n ][ k ];
}

int main ()
{
int n , k ;
cout << " Enter the values of n and k : " ;
cin >> n >> k ;

if ( k > n )
{

58
cout << " Invalid input : k cannot be greater than n . " << endl ;
return 0;
}

cout << " Binomial Coefficient C ( " << n << " , " << k << " ) = " << b i n o m i a l C o e f f i c i e n t (n , k ) << endl ;

return 0;
}

Output 1:
Enter the values of n and k: 5 2
Binomial Coefficient C(5, 2) = 10
Output 2:
Enter the values of n and k: 6 3
Binomial Coefficient C(6, 3) = 20

10.2 Longest Common Substring


Algorithm:
1. Define a DP table dp[m+1][n+1]:

dp[i][j] stores the length of the longest common substring ending at indices i − 1 and j − 1.

2. Initialize all values of dp to 0.


3. Traverse both strings A and B:
• If A[i − 1] == B[j − 1]:
dp[i][j] = dp[i-1][j-1] + 1
Update the maximum length and track the substring.
• Else:
dp[i][j] = 0
4. The value of maxLen gives the length of the longest common substring.
Time Complexity:
• O(m · n): m is the length of string A and n is the length of string B.
Space Complexity:
• O(m · n): For the DP table.
Code:
# include < iostream >
# include < cstring >
using namespace std ;

const int MAX = 1000;

int l o n g e s t C o m m o n S u b s t r i n g ( char A [] , char B [] , int m , int n )


{
int dp [ MAX ][ MAX ];
memset ( dp , 0 , sizeof ( dp ) ) ;
int maxLen = 0;

for ( int i = 1; i <= m ; i ++)


for ( int j = 1; j <= n ; j ++)
if ( A [ i - 1] == B [ j - 1])
{
dp [ i ][ j ] = dp [ i - 1][ j - 1] + 1;
maxLen = max ( maxLen , dp [ i ][ j ]) ;
}
else

59
dp [ i ][ j ] = 0;

return maxLen ;
}

int main ()
{
char A [ MAX ] , B [ MAX ];
cout << " Enter the first string : " ;
cin >> A ;
cout << " Enter the second string : " ;
cin >> B ;

int m = strlen ( A ) , n = strlen ( B ) ;


cout << " Length of Longest Common Substring : " << l o n g e s t C o m m o n S u b s t r i n g (A , B , m , n ) << endl ;

return 0;
}

Output 1:
Enter the first string: abcde
Enter the second string: abfce
Length of Longest Common Substring: 2
Output 2:
Enter the first string: abcxyz
Enter the second string: xyzabc
Length of Longest Common Substring: 3

10.3 Travelling Salesman Problem


Algorithm:
1. Represent the graph using an adjacency matrix graph[i][j], where graph[i][j] is the distance from city i to city j.
2. Use a DP table dp[mask][i], where mask represents the set of visited cities and i is the current city.
3. Initialize the base case: dp[1 ¡¡ i][i] = 0 for all cities i.
4. Use the recurrence relation:

dp[mask][i] = min dp[mask][i], dp[prevMask][j] + graph[j][i]

where prevMask = mask ˆ(1 ¡¡ i) and j is the previous city.


5. Compute the minimum cost of returning to the starting city after visiting all cities.
Time Complexity:
• O(n · 2n ): We compute the cost for each subset of cities and for each city.
Space Complexity:
• O(n · 2n ): The DP table requires space proportional to the number of subsets and cities.
Code:
# include < iostream >
# include < limits >
# include < cstring >
using namespace std ;

const int INF = numeric_limits < int >:: max () ;


const int MAX = 20;

int tsp ( int graph [ MAX ][ MAX ] , int n )


{
int dp [1 << MAX ][ MAX ];
memset ( dp , INF , sizeof ( dp ) ) ;

60
for ( int i = 0; i < n ; i ++)
dp [1 << i ][ i ] = 0;

for ( int mask = 1; mask < (1 << n ) ; mask ++)


for ( int i = 0; i < n ; i ++)
if ( mask & (1 << i ) )
for ( int j = 0; j < n ; j ++)
if (( mask & (1 << j ) ) && i != j )
dp [ mask ][ i ] = min ( dp [ mask ][ i ] , dp [ mask ^ (1 << i ) ][ j ] + graph [ j ][ i ]) ;

int result = INF ;


for ( int i = 1; i < n ; i ++)
result = min ( result , dp [(1 << n ) - 1][ i ] + graph [ i ][0]) ;

return result ;
}

int main ()
{
int n ;
cout << " Enter the number of cities : " ;
cin >> n ;

int graph [ MAX ][ MAX ];


cout << " Enter the distance matrix : " << endl ;
for ( int i = 0; i < n ; i ++)
for ( int j = 0; j < n ; j ++)
cin >> graph [ i ][ j ];

int result = tsp ( graph , n ) ;


cout << " The minimum cost of the Travelling Salesman Problem is : " << result << endl ;

return 0;
}

Output 1:
Enter the number of cities: 4
Enter the distance matrix:
0 10 15 20
10 0 35 25
15 35 0 30
20 25 30 0
The minimum cost of the Travelling Salesman Problem is: 80

Output 2:
Enter the number of cities: 3
Enter the distance matrix:
0 1 2
1 0 3
2 3 0
The minimum cost of the Travelling Salesman Problem is: 6

61

You might also like