R23_DS_Unit_1-1
R23_DS_Unit_1-1
Introduction to Linear Data Structures: Definition and importance of linear data structures,
Abstract data types (ADTs) and their implementation, Overview of time and space complexity
analysis for linear data structures. Searching Techniques: Linear & Binary Search, Sorting
Techniques: Bubble sort, Selection sort, Insertion Sort, Quick Sort, Merge Sort
INTRODUCTION:
• A data structure is a particular way of storing and organizing data in a computer so that it can be
used efficiently. So a data structure is a specialized format for organizing, processing, retrieving
and storing data.
• Some common examples of data structures are arrays, linked lists, queues, stacks, binary trees,
and hash tables.
• It is considered as not only the storing of data elements but also the maintaining of the logical
relationship existing between individual data elements.
• The way of organizing of the data & performing the operations is called as data structure.
Data structure = organized data + operations
Algorithm + Data Structure=Program
Definition of Data Structure:
A data structure is a way of organizing and storing data in a computer so that it can be accessed
and manipulated efficiently. It defines the relationship between the data and the operations that
can be performed on that data. Data structures are essential for solving various computational
problems efficiently and effectively.
Data structures are generally categorized into two classes: primitive and non-primitive data structures
Therefore, linear data structures can organize elements in a sequential manner, one after the
other, just like items in a line. The importance of linear data structures lies in their simplicity
and versatility. The following are some key reasons why linear data structures are significant:
1. Simplicity: They are easier to understand and implement compared to more complex, non-
linear structures. This makes them ideal for beginners and straightforward applications.
2. Efficient access: Linear data structures allow for efficient access to elements based on their
position. Accessing specific elements is often very efficient, especially for the first and last
elements.
3. Fast insertions and deletions: Adding or removing elements at the beginning or end of the
structure can be quite efficient, just like adding a new element to the end of the data structure or
removing the first one of the data structure.
4. Memory locality: Linear data structures typically require contiguous or sequential memory
allocation, which can be more memory-efficient compared to other data structures like trees or
graphs, especially when dealing with large datasets
5. Wide applicability: Linear data structures are the building blocks for numerous applications like
lists, queues, stacks, and binary trees. They play a vital role in algorithms like sorting and
searching, making them as more important.
6. Application in Problem Solving: Many real-world problems can be efficiently solved using
linear data structures. For instance, a stack can be used to implement functions and manage
function calls in programming languages, while a queue can be employed in tasks like task
scheduling or managing resources.
ADT:
An Abstract Data Type (ADT) is a mathematical model for a certain class of data structures that
specifies a set of operations and the semantics (meaning) of those operations, without specifying the
implementation details. In other words, an ADT defines what operations can be performed on the
data, but not how those operations are implemented. For example, a stack is an abstract data type that
specifies a linear data structure with LIFO (last in, first out) behavior.
Dr. M. Purnachandra Rao, Asso.Prof, Dept. of IT, KITS Page 2
The implementation of an Abstract Data Type (ADT) involves translating its abstract interface,
which defines the operations and their semantics, into concrete code that represents the data structure
and implements those operations. The following are the steps required to implement an ADT,
1. Define the Interface: This step defines the interface of the ADT. This includes specifying the
operations that can be performed on the data structure and their signatures (method names,
parameters, and return types).
2. Choose a Data Structure: Select an appropriate data structure to represent the ADT. The choice
of data structure depends on factors such as the efficiency of operations, memory usage, and the
requirements of the application. Common data structures for implementing ADTs include arrays,
linked lists, and hash tables.
3. Implement Operations: Implement each operation defined by the ADT interface using the chosen
data structure.
Efficiency is one of the important characteristics of algorithms. This efficiency is also referred as
complexity of an algorithm. This complexity can be determined as the amount of resources such as
time and memory needed to execute an algorithm. So the efficiency or complexity of an algorithm is
stated in terms of time and space complexity. Hence, the complexity of an algorithm is divided into
two types:
1. Space Complexity
2. Time Complexity
Space Complexity:
Space Complexity can be defined as amount of memory (or) space required by an algorithm to run.
To compute the space complexity we use 2 factors i. Constant ii. Instance characteristics.
The space requirement denoted by S(p) can be given as S(p)=C+Sp
Where C- Constant, it denotes the space taken for input and output. Sp–Amount of space taken
by an instruction, variable and identifiers.
Time complexity:
The time complexity of an algorithm is the amount of computing time required by an algorithm
to run its completion.
There are 2 types of computing time 1.Compile time 2.Runtime
The time complexity generally computed at runtime (or) execution time.
The time complexity of an algorithm is basically the running time of a program as a function of
the input size.
Worst-case running time:
This is the maximum time required to solve the problem of size ‘n’. so this worst-case running time
of an algorithm is an upper bound on the running time for any input. Therefore, the running time of
algorithm will never go beyond this time limit. Normally this is represented by Big Oh (O) notation.
Average-case running time:
The average-case running time of an algorithm is an estimate of the running time for an ‘average’
input. It specifies the expected behavior of the algorithm when the input is randomly drawn from a
given distribution. Sometimes it is difficult to find, because we have to check all possible inputs.
Dr. M. Purnachandra Rao, Asso.Prof, Dept. of IT, KITS Page 3
Normally it is represented by Big Theta (θ) notation.
Big Oh(O):
If f(n) describes the running time of an algorithm, f(n) is O(g(n)) if there
exist a positive constant c and n 0 such that, 0 ≤ f(n) ≤ cg(n) for all n ≥ n 0. It
gives the worst-case complexity of an algorithm.
Example : If f(n)= 3n+2 then prove that f(n)=O(n)
Solution: By the definition of Big Oh,
f(n)= O(g(n)) where f(n) ⩽ c. g(n) for all n ≥ n0
let g(n)=n and c=4, then
consider 0 ⩽ 3n+2 ⩽ 4n
When n=1, 0⩽5⩽4
When n=2, 0⩽8⩽8
When n=3, 0 ⩽ 11⩽ 12
Therefore, 3n+2 ⩽ 4n can satisfied for all n where n0 >= 2, So by the definition of Big Oh, we can say that
f(n)= O(n)
Big Omega(Ω):
This notation is defined as, a function f is said to be Ω(g(n)), if there is a constant c > 0 and a natural
number n0 such that c*g(n) ≤ f(n) for all n ≥ n 0. However, it provides the best case complexity of an
algorithm.
Example : If f(n)= 3n+2 then prove that f(n)= Ω(n)
Solution: By the definition of Big Ω, f(n) = Ω(g(n)
where c*g(n) ≤ f(n) for all n ≥ n0
Let c=3 and g(n)=n, then
Consider 3n+2 ≥ 3n
When n= 1, 5≥ 3
When n = 2, 8 ≥ 6
Therefore 3n + 2 ≥ 3n can be satisfied for n where n ≥ 1,
hence by the definition of Big omega, we can say that f(n)= Ω(n)
Big Theta(θ):
The function f is said to be θ(g(n)), if there are constants c1, c2 > 0 and a natural number n 0 such that
c1* g(n) ≤ f(n) ≤ c2 * g(n) for all n ≥ n 0. It is used for analyzing the average-case complexity of an
algorithm.
Example: If f(n)= 3n+2 then prove that f(n)= θ(n)
Solution: By the definition of Big θ, f(n) = θ(n)
where c1* g(n) ≤ f(n) ≤ c2 * g(n) for all n ≥ n 0
Let c1 = 3, c2 = 4 and g(n) = n, then
Consider 3n ≤ 3n+2 ≤ 4n
When n = 1, 3 ≤ 5 ≤ 4
When n = 2, 6 ≤ 8 ≤ 8
When n = 3, 9≤ 11≤ 12
Therefore 3n ≤ 3n+2 ≤ 4n can be satisfied for all n, where n ≥ 2.
Hence we say that f(n)= θ(n).
The time and space complexities of linear data structures, such as arrays, linked lists, stacks, and
queues, vary depending on the specific operations being performed and the underlying
implementation details. Here's a general overview:
- Time Complexity:
- Accessing an element by index (random access): O(1)
- Searching an element: O(n)
- Insertion/Deletion at the beginning: O(1)
- Insertion/Deletion at location : O(n)
- Insertion/Deletion at the end (if resizing is not required): O(1)
- Space Complexity: O(n)
2. Linked Lists:
- Time Complexity:
- Accessing an element by index (sequential access): O(n)
- Searching an element: O(n)
- Insertion/Deletion at the beginning: O(1)
- Insertion/Deletion at the end (with tail pointer): O(1)
- Insertion/Deletion at a given position: O(n)
- Space Complexity: O(n)
3. Stacks (implemented using arrays or linked lists):
- Time Complexity:
- Push (Insertion) operation: O(1)
- Pop (Deletion) operation: O(1)
- Peek (Accessing the top element) operation: O(1)
- Space Complexity: O(n)
4. Queues (implemented using arrays or linked lists):
- Time Complexity:
- Enqueue (Insertion) operation: O(1)
- Dequeue (Deletion) operation: O(1)
- Peek (Accessing the front element) operation: O(1)
- Space Complexity: O(n)
Searching:
Searching means that to find whether a particular element in the given list of elements.
The searching element is referred as key element.
If the element is found then search is said to be successful otherwise it is unsuccessful.
We have different Searching techniques and most frequently used search technique are linear
search, binary search.
Linear Search:
This is simplest search technique and also called as sequential search.
In this search we can traverse the group of elements sequentially or linearly to search an element.
Hence, in this search we access every element of the group one by one sequentially and check
whether it is required element or not.
If the element found then this linear search becomes successful and it gives the location of the
element found, otherwise it becomes unsuccessful.
This linear search is mostly used to search an element from unordered list i.e the items are not
sorted.
Binary Search:
Binary search is another search technique which works efficiently on the sorted lists.
This technique uses divide and conquer principle to search an element. According to this
principle, the list is divided into two halves and key element is compared with middly element.
If match found, then this binary search becomes successful and gives the location of the element
found. Otherwise it becomes unsuccessful.
However, inorder to search an element by using binary search technique, we must ensure that the
list is sorted.
The Binary search is implemented by using the following C function:
void BinarySearch(int arr[], int key, int low, int high)
{
int mid;
mid = (low + high)/2;
if(low>high)
printf("Element is not found” );
else
{
if(arr[mid]==num){
printf("Element Is At The Index: %d ",mid);
exit(0); }
else if(arr[mid] > num)
BinarySearch(arr, num, first, mid-1);
else
BinarySearch(arr, num, mid+1, last);
}
}
Time Complexity for Binary Search:
The time complexity for binary search technique is generally based on the height of binary tree.
If the number of elements are ‘n’ which is greater than 2 then the time computation is maximum
and it is a log arithmetic function. i.e log(n).So in successful searches the computing time for
binary search is
Best Case: It takes O(1) as time complexity.
Worst Case: It takes O(log n) as time complexity. and
Average Case: It takes O(log n) as time complexity.
However in unsuccessful search the best, average and worst case time complexity is O(logn)
time.
Dr. M. Purnachandra Rao, Asso.Prof, Dept. of IT, KITS Page 6
Sorting:
Sorting is a technique to arrange the list of elements either in a specific order.
The specific order may be an ascending order (increasing order) or descending order
(decreasing order)
We have number of sorting techniques and most frequently used techniques are given below
Bubble sort
Insertion sort
Selection sort
Quick sort
Merge sort
Bubble Sort:
Insertion sort:
This is another sorting technique and in this sorting technique the list can be divided into two
Dr. M. Purnachandra Rao, Asso.Prof, Dept. of IT, KITS Page 7
parts, one is sorted list and other is unsorted list.
Initially the sorted list is empty and unsorted list contains all the elements to be sorted.
Finally the sorted list contains all the elements in sorted order and unsorted list becomes empty.
The mechanism used for this insertion sort is given as follows:
i. For a given list of n elements, this technique requires n – 1 passes to sort the elements.
ii. In each pass the first element of unsorted list is moved to sorted list by inserting it in its
proper place.
iii. In each pass the first element of unsorted list is compared with its predecessor elements of
sorted list and if they are not in order then they are swapped, so that the element is inserted
in its appropriate position of the sorted list.
The Insert sort technique is implemented by using the following C function:
void InsertionSort(int a[], int n)
{
for(i=1; i<n;i++)
for(j = i; j >0; j--)
if(a[j]<a[j-1])
swap(a[j], a[j-1])
}
Time Complexity of Insertion Sort:
Best Case: This case occurs when an array is already in sorted order. So in this case it performs
only O(n) comparisons and even single swap is also not required. As a result, the time
complexity for this scenario is O(n).
Worst Case: This case occurs when an array is in reverse order. For the first iteration of outer
loop, 1 comparison required for inner loop to swap the elements. And for the second iteration, it
required 2 comparisons and so on. Therefore the time complexity is given as
T(n) = 1 + 2 + 3+..................... +(n - 3) + (n – 2)+ (n – 1)
T(n) = n(n – 1)/2. Hence, T(n)= O(n2). This is same as average case also.
Selection Sort:
A selection sort technique also divides the list into two parts, the sorted part on the left
side and the unsorted part on the right side.
Initially, the sorted part is empty, and the unsorted part contains the entire list.
Finally, we get the sorted part contains the entire list in sorted order and unsorted part
becomes empty.
The mechanism used for this selection sort is given as follows:
i) For a given list of n elements, this technique requires n – 1 passes to sort the elements.
ii) In each pass the minimum element from unsorted list is selected and moved to sorted
list in its proper place.
Usually this sorting technique is used when the list is small in size.
This selection sort is implemented by using following C function:
void SelectionSort(int a[], int n)
{
int i, j, min,temp;
for(i = 0; i < n - 1; i++)
{
min = i;
for(j = i + 1; j < n; j++)
if(a[j] < a[min])
min = j;
temp = a[i];
a[i] = a[min];
a[min] = temp;
}
Dr. M. Purnachandra Rao, Asso.Prof, Dept. of IT, KITS Page 8
}
Time Complexity of Selection Sort:
Best Case: This case occurs when an array is already in sorted order. So in this case it performs only
O(n) comparisons and even single swap is also not required. As a result, the time complexity for this
scenario is O(n).
Worst Case: This case occurs when an array is in reverse order. For the first iteration of outer loop,
n-1 comparisons required for inner loop to find the location of minimum element to swap. And for
the second iteration, it required n-2 comparisons and so on. Therefore the time complexity is given
as
T(n) = (n - 1) + (n – 2)+ (n – 3)+ ................. + 3 + 2 +1
T(n) = n(n – 1)/2
T(n)= O(n2)
This is same as average case also.
Quick Sort:
Quick sort is a highly efficient sorting algorithm and is based on partitioning of array of elements into
smaller arrays.
A large array is partitioned into two arrays one of which holds values smaller than the specified value,
say pivot, based on which the partition is made and another array holds values greater than the pivot
value.
Quicksort partitions an array and then calls itself recursively twice to sort the two resulting sub-arrays.
This technique is quite efficient for large-sized set of elements.
Steps for Quick sort:
1. First select an element which is to be called as pivot element.
2. Next, compare all array elements with the selected pivot element and arrange them in such a way
that, an element less than the pivot element are to its left and greater than pivot is to it's right.
3. Perform the same operations on left and right side elements to the pivot element.
4. Finally, we will get the sorted array of elements.
Merge Sort:
This sorting technique uses divide and conquers strategy. According to it, the array is initially divided
into two equal halves and then they are combined in a sorted manner.
We can think of it as a recursive process that continuously splits the array in to half until it cannot be
divided further. This means that if the array becomes empty or has only one element left, i.e it is the
case to stop the recursion.
Then, both halves are sorted by merge operation. This merge operation is the process of taking two
smaller sorted arrays and combines them into a larger sorted list.
Finally, we will get the sorted array of elements.