Chapter 2
Chapter 2
Sorting Algorithms
Sorting algorithms are methods used in computer science to arrange elements of a list or array
into a specific order, typically ascending or descending. This ordering can be numerical,
lexicographical (alphabetical), or based on other criteria. Efficient sorting is crucial as it
enhances the performance of other algorithms that require sorted data, such as search and merge
operations.
Sorting algorithms can be broadly categorized based on their approach and efficiency:
1. Comparison-Based Sorting Algorithms: These algorithms determine the order by
comparing elements. Examples include:
o Bubble Sort: Repeatedly steps through the list, compares adjacent elements, and swaps
them if they are in the wrong order.
o Insertion Sort: Builds the final sorted array one item at a time, with the assumption that
the preceding items are already sorted.
o Selection Sort: Divides the input into a sorted and an unsorted region, and iteratively
selects the smallest (or largest) element from the unsorted region to move to the sorted
region.
o Merge Sort: Divides the list into two halves, recursively sorts them, and then merges
the sorted halves.
o Quick Sort: Selects a 'pivot' element and partitions the other elements into those less
than and greater than the pivot, recursively applying the same strategy to the partitions.
2. Non-Comparison-Based Sorting Algorithms: These algorithms sort data without directly
comparing elements, often achieving better performance for specific types of data. Examples
include:
o Counting Sort: Counts the number of occurrences of each distinct element and uses this
information to place elements in the correct position.
o Radix Sort: Processes each digit of the numbers to sort them, starting from the least
significant digit to the most significant.
o Bucket Sort: Distributes elements into several 'buckets' and then sorts each bucket
individually, often using another sorting algorithm.
Prepared by Medhanye K 1
When selecting a sorting algorithm, factors to consider include:
Time Complexity: How the algorithm's running time increases with the size of the input.
Space Complexity: The amount of additional memory the algorithm requires.
Stability: Whether the algorithm preserves the relative order of equal elements.
Adaptability: How well the algorithm performs on data that is already partially sorted.
refers to how well a sorting algorithm takes advantage of existing order in the input data.
Adaptive Sorting Algorithms perform faster on partially sorted data.
Non-Adaptive Sorting Algorithms do the same amount of work regardless of the
input order, even if the data is nearly sorted.
1. Bubble Sort
Bubble sort is a simple comparison-based sorting algorithm that repeatedly steps through a list,
compares adjacent elements, and swaps them if they are in the wrong order. This process is
repeated until the list is sorted. The algorithm gets its name because smaller elements "bubble"
to.
How Bubble Sort Works:
1. Initialization: Start at the beginning of the list.
2. Compare Adjacent Elements: Compare the current element with the next one.
3. Swap if Necessary: If the current element is greater than the next element, swap them.
4. Continue Through the List: Move to the next pair of elements and repeat the
comparison and swap if needed.
5. Repeat Passes: After completing a pass through the list, repeat the process from the
beginning until a pass is completed without any swaps, indicating that the list is sorted.
Example:
Consider the array: [5, 2, 9, 1, 5]
Pass 1:
o Compare 5 and 2; swap → [2, 5, 9, 1, 5]
o Compare 5 and 9; no swap → [2, 5, 9, 1, 5]
o Compare 9 and 1; swap → [2, 5, 1, 9, 5]
o Compare 9 and 5; swap → [2, 5, 1, 5, 9]
Pass 2:
o Compare 2 and 5; no swap → [2, 5, 1, 5, 9]
o Compare 5 and 1; swap → [2, 1, 5, 5, 9]
Prepared by Medhanye K 2
o Compare 5 and 5; no swap → [2, 1, 5, 5, 9]
o Compare 5 and 9; no swap → [2, 1, 5, 5, 9]
Pass 3:
o Compare 2 and 1; swap → [1, 2, 5, 5, 9]
o Compare 2 and 5; no swap → [1, 2, 5, 5, 9]
o Compare 5 and 5; no swap → [1, 2, 5, 5, 9]
o Compare 5 and 9; no swap → [1, 2, 5, 5, 9]
Pass 4:
o Compare 1 and 2; no swap → [1, 2, 5, 5, 9]
o Compare 2 and 5; no swap → [1, 2, 5, 5, 9]
o Compare 5 and 5; no swap → [1, 2, 5, 5, 9]
o Compare 5 and 9; no swap → [1, 2, 5, 5, 9]
After the fourth pass, no swaps are needed, indicating that the array is sorted.
Characteristics:
Time Complexity:
o Best Case: O(n) – occurs when the array is already sorted.
o Average and Worst Case: O(n²) – happens when the array is in reverse order or
randomly ordered.
Space Complexity: O(1) – requires a constant amount of additional memory.
Stability: Bubble sort is stable; it preserves the relative order of equal elements.
Adaptability: Efficient for small or nearly sorted datasets.
Advantages:
Simple to understand and implement.
Performs well on small datasets.
Requires a minimal number of swaps, which can be beneficial when write operations are
costly.
Disadvantages:
Inefficient for large datasets due to its quadratic time complexity.
Does not take advantage of any existing order in the array.
While bubble sort is educational and easy to grasp, it is generally outperformed by more
advanced algorithms like quicksort or mergesort for larger datasets. However, its simplicity and
Prepared by Medhanye K 3
low memory usage make it suitable for specific situations, particularly when dealing with small
lists or when the cost of swapping elements is a significant factor.
Concept: Repeatedly swaps adjacent elements if they are in the wrong order.
Inefficiencies:
o Takes O(n2) time complexity in worst and average cases.
o Performs many unnecessary swaps.
o Not suitable for large datasets.
Implementation:
void bubbleSort(int arr[], int n) {
for (int i = 0; i < n - 1; i++) {
bool swapped = false;
for (int j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
swap(arr[j], arr[j + 1]);
swapped = true;
}
}
if (!swapped) break; // Optimization: Stop if already sorted
}
}
2. Insertion Sort
Insertion sort is a straightforward sorting algorithm that constructs the final sorted array one
element at a time. It operates by dividing the array into a sorted and an unsorted region. Initially,
the sorted region contains the first element, and the unsorted region comprises the rest. The
algorithm repeatedly picks the first element from the unsorted region and inserts it into its correct
position within the sorted region. This process continues until the entire array is sorted.
How Insertion Sort Works:
1. Start with the first element: Assume the first element is already sorted.
2. Select the next element: Take the first element from the unsorted region.
3. Find the correct position: Compare this element with the elements in the sorted region,
moving from right to left, until you find the appropriate spot.
4. Insert the element: Place the element in its correct position within the sorted region.
Prepared by Medhanye K 4
5. Repeat: Continue this process for all elements in the unsorted region until the entire
array is sorted.
Example: Consider the array: [5, 2, 9, 1, 5]
Initial state: [5 | 2, 9, 1, 5]
Insert 2: [2, 5 | 9, 1, 5]
Insert 9: [2, 5, 9 | 1, 5]
Insert 1: [1, 2, 5, 9 | 5]
Insert 5: [1, 2, 5, 5, 9]
Characteristics:
Time Complexity:
o Best Case: O(n) – occurs when the array is already sorted.
o Average and Worst Case: O(n²) – happens when the array is in reverse order or
randomly ordered.
Space Complexity: O(1) – requires a constant amount of additional memory.
Stability: Insertion sort is stable; it preserves the relative order of equal elements.
Adaptability: Efficient for small or nearly sorted datasets.
Insertion sort is often compared to the method used when sorting playing cards in hand, where
you insert each new card into its proper position among the already sorted cards.
While not suitable for large datasets due to its quadratic time complexity, insertion sort is valued
for its simplicity and efficiency in specific scenarios, particularly with small or nearly sorted
arrays.
Concept: A simple and intuitive sorting algorithm that builds a sorted array one element
at a time by inserting each element into its correct position.
Step-by-step Explanation:
1. Start with the second element and compare it with the previous elements.
2. Shift larger elements to the right.
3. Insert the element in its correct position.
4. Repeat for all elements.
Step 1: for i = 1 to length(A) inclusive do:
/* select value to be inserted */
valueToInsert = A[i]
holePosition = i
Prepared by Medhanye K 5
Step 2: while holePosition > 0 and A[holePosition-1] > valueToInsert do:
/*locate hole position for the element to be inserted */
A[holePosition] = A[holePosition-1]
holePosition = holePosition -1
end while
Step 3: A[holePosition] = valueToInsert /* insert the number at hole position */
end for
Implementation:
void insertionSort(int arr[], int n)
{
for (int i = 1; i < n; i++)
{
int valueToinsert = arr[i];
int k = i ;
while (k >= 0 && arr[ k-1] > valueToinsert)
{
arr[k] = arr[ k-1 ]; /* here
k--;
}
arr[k + 1] = valueToinsert;
}
}
Time Complexity:
o Best case: O(n) (already sorted)
3. Selection Sort
Selection sort is a straightforward comparison-based sorting algorithm that organizes a list by
repeatedly selecting the smallest (or largest, depending on the desired order) element from the
unsorted portion and moving it to the end of the sorted portion. This process continues until the
entire list is sorted.
How Selection Sort Works:
1. Initialization: Start with the entire list as the unsorted portion.
2. Find the Minimum: Scan the unsorted portion to find the smallest element.
3. Swap: Swap this smallest element with the first element of the unsorted portion, effectively
expanding the sorted portion by one and shrinking the unsorted portion.
Prepared by Medhanye K 6
4. Repeat: Continue this process with the remaining unsorted portion until the entire list is
sorted.
Example:
Consider the array: [29, 10, 14, 37, 13]
Initial state: [29, 10, 14, 37, 13]
Pass 1: Find the minimum (10) and swap with the first element. Result: [10, 29, 14, 37, 13]
Pass 2: Find the minimum in the remaining unsorted portion (13) and swap with the first
unsorted element. Result: [10, 13, 14, 37, 29]
Pass 3: Find the minimum (14) and swap with itself (no change). Result: [10, 13, 14, 37, 29]
Pass 4: Find the minimum (29) and swap with 37. Result: [10, 13, 14, 29, 37]
Characteristics:
Time Complexity: Selection sort has a time complexity of O(n²), where n is the number
of elements in the array. This is because, for each element, the algorithm scans the
remaining unsorted elements to find the minimum.
Space Complexity: O(1), as it requires only a constant amount of additional memory.
Stability: Selection sort is not stable; equal elements may not retain their original relative
order after sorting.
Adaptability: The algorithm does not adapt to the initial order of elements; its
performance remains consistent regardless of the input's initial state.
Advantages:
Simple to understand and implement.
Performs well on small datasets.
Requires a minimal number of swaps, which can be beneficial when write operations are
costly.
Disadvantages:
Inefficient for large datasets due to its quadratic time complexity.
Does not take advantage of any existing order in the array.
While selection sort is educational and easy to grasp, it is generally outperformed by more
advanced algorithms like quicksort or mergesort for larger datasets. However, its simplicity and
low memory usage make it suitable for specific situations, particularly when dealing with small
lists or when the cost of swapping elements is a significant factor.
Prepared by Medhanye K 7
Concept: Selects the smallest element and swaps it with the first unsorted element.
Working Mechanism:
1. Find the minimum element in the unsorted part of the array.
2. Swap it with the first unsorted element.
3. Repeat until the array is sorted.
Step 1: Repeat Step 2 For i = 0 to N-1
Step 2: Repeat For J = i + 1 to N - I
Step 3: IF A[J] > A[i]
swap a[j] and a[i]
end of inner loop= step 2
end of outer loop= step 1
Implementation:
void selectionSort(int arr[], int n) {
for (int i = 0; i < n - 1; i++) {
int minIdx = i;
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[minIdx])
minIdx = j;
}
swap(arr[i], arr[minIdx]);
}
}
Comparison with Insertion Sort:
o Selection Sort makes fewer swaps but has a fixed O(n2) time complexity.
o Insertion Sort can be faster for nearly sorted data.
4. Pointer Sort
Pointer Sort is a sorting technique where, instead of rearranging the actual elements in an array,
we sort an array of pointers that point to the elements. The elements remain in their original
locations, but their logical order is represented by the arrangement of pointers.
How It Works:
Prepared by Medhanye K 8
Advantages:
Reduces Memory Overhead: Large data structures are not physically moved; only
pointers are rearranged.
Efficiency: Useful when elements are large objects (e.g., structures or records).
Faster Swaps: Swapping pointers is faster than swapping large data elements.
Limitations:
Prepared by Medhanye K 9
Explanation of Each Part in the Code:
Step Description
2 Create an array of pointers ptr[]. Each ptr[i] stores the address of arr[i].
3 Sort the ptr[] array using dereferenced values. Here, we use bubble sort.
5 Print elements in sorted order using the sorted ptr[] array by dereferencing the pointers.
Sorting pointers, not data: This reduces overhead when data elements are large.
Access by dereferencing: Sorted output is obtained by dereferencing the sorted pointers.
Original array remains intact.
Complexity Analysis:
Searching Algorithms
Definition:
Searching is the process of locating a specific element within a data structure, such as an array,
list, database, or tree. It involves examining the elements within the data structure to find a
particular value, often called the target or key, and determining its position or index if it exists.
Key Points:
Searching is a fundamental operation in computer science, extensively used in various fields like
databases, file systems, and data retrieval systems.
The primary goal is to determine whether a target value is present and to retrieve its location
within the data structure.
Prepared by Medhanye K 10
Searching can be conducted on both sorted and unsorted data structures, depending on the
algorithm employed.
Searching is the process of finding the location of a particular element within a data structure,
such as an array, list, or database. It involves examining elements of the data structure to locate a
specific value and determine its position.
- It is a fundamental operation in computer science, widely used in various applications.
Types of Searching Algorithms:
1. Linear Search (Sequential Search)
1. Linear Search (Sequential Search):
- Checks each element one by one.
- Simple but inefficient for large datasets.
- Time complexity: O(n).
- Works on both sorted and unsorted data.
Implementation:
int linearSearch(int arr[], int n, int key) {
for (int i = 0; i < n; i++) {
if (arr[i] == key)
return i; // Found, return index
}
return -1; // Not found
}
2. Binary Search
Binary Search:
- Requires a sorted array.
- Divides the array into halves repeatedly until the target is found.
- More efficient for large datasets compared to linear search.
- Time complexity: O(log n).
Requirements:
The array must be sorted.
# Pseudo code for Binary Search
BinarySearch(array, target):
low = 0
Prepared by Medhanye K 11
high = length(array) - 1
while low <= high:
mid = (low + high) // 2
if array[mid] == target:
return mid
else if array[mid] < target:
low = mid + 1
else:
high = mid - 1
return -1
Time Complexity:
o Best case: O(1) (element found at the middle)
o Worst/Average case: O(logn) (logarithmic search space reduction)
Other Advanced Searching Algorithms:
- Hashing: Provides direct access to elements using a hash function; very efficient with O(1)
average time complexity.
- Interpolation Search: A more efficient variation of binary search when the elements are
uniformly distributed.
- Exponential Search: Useful for unbounded or infinite lists.
Applications of Searching:
- Looking up data in databases.
- Searching for files on a computer.
- Searching for a word in a document.
- Retrieving records in data structures like arrays, lists, and trees.
- Web search engines for finding relevant web pages.
- Searching in large datasets for data analysis.
Efficient searching is crucial for optimizing performance, especially when dealing with large
volumes of data.
Prepared by Medhanye K 12