DS Unit-I
DS Unit-I
Unit – I:
Structures
2. Abstract Data Types & their implementation, Overview of time and space
3. Recursion
7. Shell Sort
8. Quick Sort
9. Merge Sort
Structures
Data Structure
Linear Data Structure: A data structure in which data elements are arranged
sequentially or linearly, where each element is attached to its previous and
next adjacent elements, is called a linear data structure. Examples are array,
stack, queue, Linked List etc.
Non-linear Data Structure: Data structures where data elements are not
placed sequentially or linearly are called non-linear data structures. Examples
are trees and graphs.
1. Array
2. Stack
The data structure follows the rule of LIFO (Last In-First Out) where
the data last added element is removed first.
Push operation is used for adding an element of data on a stack
and the pop operation is used for deleting the data from the stack.
This can be explained by the example of books stacked together. In
order to access the last book, all the books placed on top of the last
book have to be safely removed.
3. Queue
This structure is almost similar to the stack as the data is stored sequentially.
The difference is that the queue data structure follows FIFO which is the
rule of First In-First Out where the first added element is to exit the
queue first. Front and rear are the two terms to be used in a queue.
The data structure might be explained with the example of people queuing
up to ride a bus. The first person in the line will get the chance to exit
the queue while the last person will be the last to exit.
4. Linked List
Linked lists are the types where the data is stored in the form of
nodes which consist of an element of data and a pointer.
The use of the pointer is that it points or directs to the node which
is next to the element in the sequence. The data stored in a linked
list might be of any form, strings, numbers, or characters.
5. Hash Tables
These types can be implemented as linear or non-linear data
structures. The data structures consist of key-value pairs.
1. Trees
2. Graph
Its examples are: array, stack, queue, While its examples are: trees and
6.
linked list, etc. graphs.
Data structure is a way of storing and organizing data efficiently such that the
required operations on them can be performed be efficient with respect to
time as well as memory.
In Static data structure the size of the structure is fixed. The content of
the data structure can be modified but without changing the memory
space allocated to it.
Example of Static Data Structures: Array
Fixed size: Arrays have a fixed size that is determined at the time of
creation. This means that if the size of the array needs to be increased, a
new array must be created and the data must be copied from the old
array to the new array, which can be time-consuming and memory-
intensive.
Memory allocation issues: Allocating a large array can be problematic,
particularly in systems with limited memory. If the size of the array is too
large, the system may run out of memory, which can cause the program to
crash.
Insertion and deletion issues: Inserting or deleting an element from an
array can be inefficient and time-consuming because all the elements
after the insertion or deletion point must be shifted to accommodate the
change.
Wasted space: If an array is not fully populated, there can be wasted
space in the memory allocated for the array. This can be a concern if
memory is limited.
LC2:
Abstract Data Types & their implementation, Overview of time
and space complexity analysis for linear data structures
Abstract Data type (ADT) is a type (or class) for objects whose behavior is
defined by a set of values and a set of operations. The definition of ADT
only mentions what operations are to be performed but not how these
operations will be implemented.
1. Representation of Data.
2. Set of Operations on the Data.
arr
Abstract, that is without knowing internal details we can use them. So a user
only needs to know what a data type can do, but not how it will be
implemented. Think of ADT as a black box which hides the inner structure and
design of the data type.
Now we’ll define three ADTs namely List ADT, Stack ADT, Queue ADT.
1. List ADT
The data is generally stored in key sequence in a list which has a head
structure consisting of count, pointers and address of compare
function needed to compare the data in the list.
The data node contains the pointer to a data structure and a self-
referential pointer which points to the next node in the list.
The List ADT Functions is given below:
get() – Return an element from the list at any given position.
insert() – Insert an element at any position of the list.
remove() – Remove the first occurrence of any element from a non-empty
list.
removeAt() – Remove the element at a specified location from a non-
empty list.
replace() – Replace an element at any position by another element.
size() – Return the number of elements in the list.
isEmpty() – Return true if the list is empty, otherwise return false.
isFull() – Return true if the list is full, otherwise return false.
2. Stack ADT
View of stack
View of Queue
The queue abstract data type (ADT) follows the basic design of the stack
abstract data type.
Each node contains a void pointer to the data and the link pointer to the
next element in the queue. The program’s responsibility is to allocate
memory for storing the data.
enqueue() – Insert an element at the end of the queue.
dequeue() – Remove and return the first element of the queue, if the
queue is not empty.
peek() – Return the element of the queue without removing it, if the
queue is not empty.
size() – Return the number of elements in the queue.
isEmpty() – Return true if the queue is empty, otherwise return false.
isFull() – Return true if the queue is full, otherwise return false.
Accessing by
O(n) O(n) O(1)
Index
Insertion at
O(1) O(1) O(1)
Beginning
Insertion at
O(n) O(1) O(1)
End
Insertion at
Given O(n) O(n) O(1)
Position
Deletion at
O(1) O(1) O(1)
Beginning
Deletion at
O(n) O(1) O(1)
End
Deletion at
Given O(n) O(n) O(1)
Position
LC3: Recursion
What is Recursion?
The process in which a function calls itself directly or indirectly is called
recursion and the corresponding function is called a recursive function.
#include <stdio.h>
// Recursion function
void fun(int n)
{
if (n > 0) {
printf("%d ", n);
output: 3 2 1
Head Recursion: If a recursive function calling itself and that recursive call
is the first statement in the function then it’s known as Head
Recursion. There’s no statement, no operation before the call. The function
doesn’t have to process or perform any operation at the time of calling and
all operations are done at returning time.
example:
void fun(int n)
{
if (n > 0) {
Tree Recursion
If a recursive function calling itself for more than one time then it’s known
as Tree Recursion.
void fun(int n)
{
if (n > 0) {
printf("%d ", n);
// Calling once
fun(n - 1);
// Calling twice
fun(n - 1);
}
}
tracing
Binary recursion:
binary recursion a function makes two recursive calls to itself when invoked, it uses binary
recursion. Fibonacci series is an example of binary recursion
Linear Search
For example: Consider the array arr[] = {10, 50, 30, 70, 80, 20, 90,
40} and key = 30
Step 1: Start from the first element (index 0) and compare key with each
element (arr[i]).
Comparing key with first element arr[0]. SInce not equal, the iterator
moves to the next element as a potential match.
Comparing key with next element arr[1]. SInce not equal, the iterator
moves to the next element as a potential match.
Step 2: Now when comparing arr[2] with key, the value matches. So the
Linear Search Algorithm will yield a successful message and return the index
of the element when key is found (here 2).
Program
C Programs to implement the Searching Techniques – Linear & Binary Search
Linear Search
#include<stdio.h>
int main()
scanf("%d", &n);
scanf("%d", &arr[i]);
scanf("%d",&key);
found=search(arr,n,key);
if(found==-1)
else
if (array[i] == x)
return i+1;
return -1;
}
Output
element found at 1
Binary Search
binary search is defined as a searching algorithm used in a sorted array by repeatedly dividing
the search interval in half. The idea of binary search is to use the information that the array is
sorted and reduce the time complexity to O(log N).
In this algorithm,
Divide the search space into two halves by finding the middle index “mid”.
Compare the middle element of the search space with the key.
If the key is found at middle element, the process is terminated.
If the key is not found at middle element, choose which half will be used as the next
search space.
If the key is smaller than the middle element, then the left side is used for
next search.
If the key is larger than the middle element, then the right side is used for
next search.
This process is continued until the key is found or the total search space is exhausted.
Consider an array arr[] = {2, 5, 8, 12, 16, 23, 38, 56, 72, 91}, and the target = 23.
First Step: Calculate the mid and compare the mid element with the key. If the key is less
than mid element, move to left and if it is greater than the mid then move search space to
the right.
Key (i.e., 23) is greater than current mid element (i.e., 16). The search space moves to
the right.
Key is less than the current mid 56. The search space moves to the left.
Second Step: If the key matches the value of the mid element, the element
is found and stop search.
Time Complexity of Linear Search Algorithm:
Best Case Time Complexity: O(1)
Best case is when the list or array’s first element matches the target
element. The time complexity is O(1) because there is just one comparison
made. So the best case complexity is O(1).
Average Case Time Complexity: O(n)
The average case time complexity of the linear search algorithm
is O(n), where n is the number of elements in the array being searched.
Worst Case Time Complexity: O(n)
The worst-case will be when the target element is absent from the list or
array or is located at the end of the list. O(n) time complexity results from
the necessity to traverse the complete list and the n comparisons that are
required.
The space complexity of the binary search algorithm is O(1), which indicates
that the amount of memory required by the algorithm remains constant
regardless of the size of the input array. This efficiency in both time and space
usage makes binary search a preferred choice for searching in large sorted
arrays, as it significantly reduces the time taken to find an element compared
to linear search algorithms.
Suppose we have a sorted array of elements {12, 14, 16, 17, 20,
24, 31, 43, 50, 62} and need to identify the location of element
24 in it using Fibonacci Search.
Step 1
The size of the input array is 10. The smallest Fibonacci number
greater than 10 is 13.
We initialize offset = -1
Step 2
The fourth element in the array is 20, which is not a match and is
less than the key element.
Step 3
In the second iteration, update the offset value and the Fibonacci
numbers.
Since the key is greater, the offset value will become the index of
the element, i.e. 4. Fibonacci numbers are updated as F m = Fm-
1 = 8.
Fm-1 = 5, Fm-2 = 3.
Step 4
Fm-1 = 2, Fm-2 = 1.
Bubble Sort
In Bubble Sort algorithm,
traverse from left and compare adjacent elements and the higher one is placed at right
side.
In this way, the largest element is moved to the rightmost end at first.
This process is then continued to find the second largest and place it and so on until the
data is sorted.
Second Pass:
Place the second largest element at correct position
Third Pass:
Place the remaining two elements at their correct positions.
Start
Initialise a variable named array of required size, i, j, n.
The outer loop will run from i = 0 to i < n – 1, where n is the number of
elements in the list.
The inner loop will run from j = 0 to j < n – i – 1. It is because, after each
iteration of the outer loop, one element at the end (or at the start if the
order is decreasing order) will be in its right place so we can leave it as it
is.
This process will be repeated till the conditions of the loop are satisfied.
LC 6. Selection Sort, Insertion Sort
Selection Sort
The algorithm repeatedly selects the smallest (or largest) element from the unsorted portion
of the list and swaps it with the first element of the unsorted part. This process is repeated
for the remaining unsorted portion until the entire list is sorted.
1. Start
2. Initialise a variable named min_ind, which will store 0 at the beginning.
3. Now, run a loop to find the minimum element in the array.
4. If you find any smaller elements than min_ind, then swap both values.
5. Now, increment min_ind++ to point to the next element in the array.
6. Keep repeating until you reach the size of the array or list.
7. End
First pass
For the first position in the sorted array, the whole array is traversed from index 0 to 4
sequentially. The first position where 64 is stored presently, after traversing whole array
it is clear that 11 is the lowest value.
Thus, replace 64 with 11. After one iteration 11, which happens to be the least value in
the array, tends to appear in the first position of the sorted list.
Second Pass:
For the second position, where 25 is present, again traverse the rest of the array in a
sequential manner.
After traversing, we found that 12 is the second lowest value in the array and it should
appear at the second place in the array, thus swap these values.
Third Pass:
Now, for third place, where 25 is present again traverse the rest of the array and find the
third least value present in the array.
While traversing, 22 came out to be the third least value and it should appear at the third
place in the array, thus swap 22 with element present at third position.
Fourth pass:
Similarly, for fourth position traverse the rest of the array and find the
fourth least element in the array
As 25 is the 4th lowest value hence, it will place at the fourth position.
Fifth Pass:
At last the largest value present in the array automatically get placed at
the last position in the array
The resulted array is the sorted array.
Algorithm
set min_ind as i
set min_ind as j
end for
end selectionSort
Insertion Sort
To sort an array of size N in ascending order iterate over the array and compare the current
element (key) to its predecessor, if the key element is smaller than its predecessor, compare it
to the elements before. Move the greater elements one position up to make space for the
swapped element.
Working of Insertion Sort algorithm
Initially, the first two elements of the array are compared in insertion sort.
12 11 13 5 6
Here, 12 is greater than 11 hence they are not in the ascending order and 12 is not at its
correct position. Thus, swap 11 and 12.
So, for now 11 is stored in a sorted sub-array.
11 12 13 5 6
Second Pass:
Now, move to the next two elements and compare them
11 12 13 5 6
Here, 13 is greater than 12, thus both elements seems to be in ascending order, hence,
no swapping will occur. 12 also stored in a sorted sub-array along with 11
Third Pass:
Now, two elements are present in the sorted sub-array which are 11 and 12
Moving forward to the next two elements which are 13 and 5
11 12 13 5 6
Both 5 and 13 are not present at their correct place so swap them
11 12 5 13 6
After swapping, elements 12 and 5 are not sorted, thus swap again
11 5 12 13 6
5 11 12 13 6
Fourth Pass:
Now, the elements which are present in the sorted sub-array are 5, 11 and 12
Moving to the next two elements 13 and 6
5 11 12 13 6
Clearly, they are not sorted, thus perform swap between both
5 11 12 6 13
5 11 6 12 13
5 6 11 12 13
The Selection Sort program in C to sort the array in ascending order can be
implemented in a few easy steps as mentioned below:-
arr[j+1] = arr[j]
Increment j = j + 1
QuickSort
QuickSort is a sorting algorithm based on the Divide and
Conquer algorithm that picks an element as a pivot and partitions
the given array around the picked pivot by placing the pivot in its
correct position in the sorted array.
QuickSort pseudocode
Analysis
The worst case complexity of Quick-Sort algorithm is O(n2). However,
using this technique, in average cases generally we get the output
in O (n log n) time.
Algorithm:
Step 1: Start
return
mid= (left+right)/2
Step 4: Stop
Now, as we already know that merge sort first divides the whole
array iteratively into equal halves, unless the atomic values are
achieved.
Here, we see that an array of 7 items is divided into two arrays of
size 4 and 3 respectively.
Now, again find that is left index is less than the right index for
both arrays, if found yes, then again calculate mid points for both
the arrays.
Now, further divide these two arrays into further halves, until the
atomic units of the array is reached and further division is not
possible.
After dividing the array into smallest units, start merging the
elements again based on comparison of size of elements
Firstly, compare the element for each list and then combine
them into another list in a sorted manner.
After the final merging, the list looks like this:
Example2:
Merge sort complexity
Now, let's see the time complexity of merge sort in best case, average case, and in
worst case. We will also see the space complexity of the merge sort.
1. Time Complexity