Hello! Welcome to our next lesson.
In our previous session, we explored the Boyer-Moore Voting Algorithm, a very clever, optimal solution for a specific array problem. It showcased how a non-obvious, single-pass approach can achieve O(n) time and O(1) space complexity.
Today, we're shifting gears from specialized algorithms back to the fundamentals of sorting. This lesson covers three foundational sorting algorithms: Bubble Sort, Selection Sort, and Insertion Sort. While they are not the most efficient for large datasets, they are essential for building a solid understanding of sorting mechanics. They are frequently asked about in interviews to test core knowledge, and understanding their limitations will provide the perfect motivation for the more advanced algorithms we'll cover later.
Your goal is to implement these three algorithms in Java and analyze their performance, including their time and space complexity in best, average, and worst-case scenarios.
A High-Level Glimpse
Before we dive into each one, let's look at a visual summary of their core strategies. Each algorithm approaches the problem of sorting differently.
We will now explore each of these in detail. For the core explanations, we'll rely on an excellent video that walks through the intuition, implementation logic, and complexity of all three.
Sorting - Part 1 | Selection Sort, Bubble Sort, Insertion Sort | Strivers A2Z DSA Course
The video 'Sorting - Part 1' by the 'take U forward' channel provides a comprehensive walkthrough of Selection, Bubble, and Insertion sort. It's a great starting point for understanding the intuition behind each.
We will watch this video in three parts, one for each algorithm. You can keep it open as we proceed through the lesson.
1. Selection Sort: Select the Minimum
The core idea of Selection Sort is straightforward:
- Find the smallest element in the entire unsorted array.
- Swap it with the element at the first position.
- Now, the first position is sorted. Move to the second position.
- Find the smallest element in the rest of the unsorted array (from the second position onwards).
- Swap it with the element at the second position.
- Repeat this process until the entire array is sorted.
Essentially, in each pass i, you are "selecting" the correct element that should be at index i and putting it there.
Explanation and Dry Run
Sorting - Part 1 | Selection Sort, Bubble Sort, Insertion Sort | Strivers A2Z DSA Course
Let's start with Selection Sort. Watch this segment to understand its mechanism of finding the minimum and swapping.
Watch the section on Selection Sort from 02:45 to 17:28. Pay close attention to how the outer loop tracks the current position to be filled and the inner loop finds the minimum in the remaining unsorted portion.
Java Implementation
Here is a clean Java implementation of Selection Sort. Notice how it directly maps to the logic described in the video.
public class Sorting {
public static void selectionSort(int[] arr) {
int n = arr.length;
// One by one move the boundary of the unsorted subarray
for (int i = 0; i < n - 1; i++) {
// Find the minimum element in the unsorted array
int minIndex = i;
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
// Swap the found minimum element with the first element
// of the unsorted part
int temp = arr[minIndex];
arr[minIndex] = arr[i];
arr[i] = temp;
}
}
}
Analysis
- Time Complexity: O(n²) for all cases (Best, Average, Worst).
- The outer loop runs times.
- The inner loop runs approximately , then , , ..., 1 times. This series sums up to , which is O(n²).
- Even if the array is already sorted (best case), the algorithm must still scan the entire unsorted portion to confirm that the current element is indeed the minimum. It has no way to exit early.
- Space Complexity: O(1). It's an in-place algorithm; we only need one temporary variable for the swap.
- Stability: Selection Sort is not stable. If you have two equal elements, their relative order might change. For example, in
[5a, 5b, 2], the first pass will swap2with5a, resulting in[2, 5b, 5a]. The original order of5aand5bhas been reversed. - Swaps: It performs at most swaps, which is the minimum among these three algorithms. This can be an advantage if write operations are significantly more expensive than read operations.
2. Bubble Sort: Swap Adjacent Elements
Bubble Sort works by repeatedly stepping through the list, comparing each pair of adjacent items, and swapping them if they are in the wrong order. After the first pass, the largest element will have "bubbled" up to its correct position at the end of the array.
Explanation and Dry Run
A crucial feature of Bubble Sort is the ability to optimize it. If we complete a full pass through the array without making a single swap, we know the array is already sorted and we can stop early.
Sorting - Part 1 | Selection Sort, Bubble Sort, Insertion Sort | Strivers A2Z DSA Course
Now for Bubble Sort. This part of the video explains the adjacent swapping mechanism and, importantly, the optimization that gives it a better best-case performance.
Watch the segment on Bubble Sort from 17:28 to 31:28. Focus on the didSwap flag logic, which is key to understanding its O(n) best-case complexity.
Java Implementation
This implementation includes the "swapped" flag optimization.
public class Sorting {
public static void bubbleSort(int[] arr) {
int n = arr.length;
boolean swapped;
for (int i = 0; i < n - 1; i++) {
swapped = false;
// The last i elements are already in place
for (int j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
// Swap arr[j] and arr[j+1]
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
swapped = true;
}
}
// If no two elements were swapped by inner loop, then break
if (!swapped) {
break;
}
}
}
}
Analysis
- Time Complexity:
- Worst and Average Case: O(n²). This occurs when the array is in reverse order, requiring the maximum number of passes and swaps.
- Best Case: O(n). If the array is already sorted, the algorithm will make one pass, find that no swaps are needed, and exit.
- Space Complexity: O(1). It is also an in-place algorithm.
- Stability: Bubble Sort is stable. Since it only swaps adjacent elements if the first is strictly greater than the second, the relative order of equal elements is never changed.
Test your understanding!
Consider a reverse-sorted array of n distinct elements, e.g., [5, 4, 3, 2, 1]. Which algorithm performs more swaps: Selection Sort or Bubble Sort?
Show answer
Bubble Sort performs significantly more swaps.
- Selection Sort will perform at most
n-1swaps. For[5, 4, 3, 2, 1], it will swap 1 with 5, then 2 with 4, etc. It does roughlyn/2swaps. - Bubble Sort in the worst case (a reverse-sorted array) will perform a swap for almost every comparison. The number of swaps will be on the order of O(n²).
This highlights a key difference: Selection Sort minimizes swaps, while Bubble Sort does not.
3. Insertion Sort: Insert into Sorted Position
Insertion Sort builds the final sorted array one item at a time. It's conceptually similar to how many people sort a hand of playing cards.
- Assume the first element is a sorted subarray of size 1.
- Take the second element and insert it into the correct position within the sorted subarray (either before or after the first element).
- Now you have a sorted subarray of size 2.
- Take the third element and shift elements in the sorted subarray to the right until you find the correct position for it.
- Repeat until all elements have been inserted.
Explanation and Dry Run
Insertion sort is particularly efficient for datasets that are already substantially sorted.
Sorting - Part 1 | Selection Sort, Bubble Sort, Insertion Sort | Strivers A2Z DSA Course
Finally, let's look at Insertion Sort. This section explains how it builds a sorted portion of the array by inserting elements from the unsorted part.
Watch from 31:28 to 42:48. Note the mechanism of taking an element (key) and shifting larger elements to the right to make space for it. This is different from the direct swapping seen in the other two algorithms.
Java Implementation
The inner while loop is responsible for finding the correct insertion spot by shifting elements.
public class Sorting {
public static void insertionSort(int[] arr) {
int n = arr.length;
for (int i = 1; i < n; ++i) {
int key = arr[i];
int j = i - 1;
/* Move elements of arr[0..i-1], that are
greater than key, to one position ahead
of their current position */
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j = j - 1;
}
arr[j + 1] = key;
}
}
}
Analysis
- Time Complexity:
- Worst and Average Case: O(n²). If the array is reverse-sorted, each element needs to be shifted all the way to the beginning of the sorted subarray.
- Best Case: O(n). If the array is already sorted, the inner
whileloop conditionarr[j] > keywill be false immediately for every element. The algorithm just makes one pass to confirm this.
- Space Complexity: O(1). It is an in-place algorithm.
- Stability: Insertion Sort is stable.
- Adaptiveness: This is its key strength. It is an adaptive algorithm, meaning its performance improves when the input array is "partially sorted". Because of this, and its excellent performance on small
n, it is often used as a component in more sophisticated hybrid sorting algorithms like Timsort (used in Python and Java) and Introsort.
4. Summary and Comparison
You've now seen the mechanics of all three basic sorting algorithms. While they all share a similar O(n²) average-case complexity, their characteristics make them suitable for different (mostly academic or niche) scenarios.
For a final consolidated view, I recommend reading this short article that compares them directly and summarizes their properties.
Comparison among Bubble Sort, Selection Sort and Insertion Sort
The GeeksforGeeks article 'Comparison among Bubble Sort, Selection Sort and Insertion Sort' provides an excellent summary table and a list of advantages/disadvantages for each algorithm.
Read the introductory comparison, the 'Advantages and disadvantages' section, and review the final 'Time and Space Complexity' table. This will help you crystallize the key differences between the three algorithms.
Here is a summary table based on what we've learned:
| Property | Bubble Sort | Selection Sort | Insertion Sort |
|---|---|---|---|
| Time (Best) | O(n) (optimized) | O(n²) | O(n) |
| Time (Avg/Worst) | O(n²) | O(n²) | O(n²) |
| Space Complexity | O(1) | O(1) | O(1) |
| Stability | Stable | Not Stable | Stable |
| Swaps | O(n²) (worst) | O(n) | O(n²) (worst) |
| Adaptiveness | Yes (optimized) | No | Yes (highly) |
| Key Use Case | Educational | Educational, low number of swaps | Small n, nearly sorted data, part of hybrid sorts |
Conclusion
In this lesson, you implemented and analyzed three fundamental sorting algorithms. You are now equipped to discuss their mechanisms, performance trade-offs, and relative strengths and weaknesses—a common expectation in foundational DSA interviews.
Key Takeaways:
- Selection Sort minimizes swaps but has an O(n²) time complexity in all cases.
- Bubble Sort is simple but generally inefficient, though an optimized version can detect a sorted array in O(n) time.
- Insertion Sort is the most practical of the three, as it's adaptive and highly efficient for small or nearly sorted arrays.
- All three are in-place (O(1) space) but are too slow for general-purpose sorting on large datasets due to their O(n²) average-case complexity.
Preview of the next lesson:
The inefficiency of these O(n²) algorithms naturally leads to the question: "Can we do better?" The answer is a resounding yes! In our next lesson, we will begin exploring a new class of algorithms with O(n log n) time complexity. We will start with Merge Sort, a powerful and elegant algorithm based on the "divide and conquer" paradigm.