🦀 Mastering Insertion Sort with Rust

A Step-by-Step Guide to Understanding Basic Sorting Algorithms

Posted by Tech Blogger on November 13, 2024

💡 The Complete Guide to Insertion Sort

📚 Table of Contents

  1. What is Insertion Sort?
  2. How It Works
  3. Step-by-Step Example
  4. Rust Implementation
  5. Time Complexity Analysis
  6. Practical Problem Solving

What is Insertion Sort?

Insertion Sort closely mimics how most people sort playing cards in their hands. Just as you would pick up a new card and insert it into its proper position among the cards you’re already holding, Insertion Sort works by building the final sorted array one item at a time.

How It Works

  1. The first element starts as “sorted”
  2. Pick the next element
  3. Compare with all elements in the sorted portion
  4. Place the element at its correct position
  5. Repeat until the entire array is sorted

Step-by-Step Example

Let’s sort the array [5, 2, 9, 1, 5, 6]:

1
2
3
4
5
6
Initial: [5│2, 9, 1, 5, 6]  (│ marks the boundary between sorted and unsorted portions)
Step 1: [2, 5│9, 1, 5, 6]  (Insert 2 before 5)
Step 2: [2, 5, 9│1, 5, 6]  (9 is already in correct position)
Step 3: [1, 2, 5, 9│5, 6]  (Insert 1 at the beginning)
Step 4: [1, 2, 5, 5, 9│6]  (Insert 5 in correct position)
Final:  [1, 2, 5, 5, 6, 9]  (Insert 6 in correct position)

Rust Implementation

Here’s a clean implementation of Insertion Sort in Rust:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fn sort(arr: &mut Vec<i32>, n: usize) {
    for i in 1..n {
        let key = arr[i];  // Current element to be inserted
        let mut j = (i - 1) as i32;

        // Move elements greater than key one position ahead
        while j >= 0 && arr[j as usize] > key {
            arr[(j + 1) as usize] = arr[j as usize];
            j -= 1;
        }
        arr[(j + 1) as usize] = key;  // Place key in its correct position
    }
}

// Example usage
fn main() {
    let mut arr = vec![5, 2, 9, 1, 5, 6];
    let n = arr.len();
    sort(&mut arr, n);
    println!("Sorted array: {:?}", arr);
}

Time Complexity Analysis

  • Best Case (O(n)): When the array is already sorted
  • Average and Worst Case (O(n²)): When the array is sorted in reverse order

Advantages ✨

  • Simple implementation and easy to understand
  • Efficient for small data sets
  • Stable sorting algorithm
  • Very efficient for data that is already nearly sorted
  • In-place algorithm with minimal space requirement

Disadvantages ⚠️

  • Inefficient for large data sets
  • O(n²) time complexity makes it slow for large arrays
  • Not suitable for big data operations

Practical Problem Solving

Here’s a solution to the “Joyful Coding Life” contest cutoff score problem using Insertion Sort:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Write};

fn sort(arr: &mut Vec<usize>, n: usize) {
    for i in 1..n {
        let key = arr[i];
        let mut j = (i - 1) as i32;
        
        // Modified comparison for descending order
        while j >= 0 && arr[j as usize] < key {
            arr[(j + 1) as usize] = arr[j as usize];
            j -= 1;
        }
        arr[(j + 1) as usize] = key;
    }
}

fn main() {
    let reader = BufReader::new(stdin().lock());
    let mut writer = BufWriter::new(stdout().lock());
    let mut input = reader.lines();

    if let Some(Ok(line)) = input.next() {
        let nk = line
            .trim()
            .split_whitespace()
            .map(|x| x.parse().unwrap())
            .collect::<Vec<usize>>();
        let n = nk[0];
        let k = nk[1];

        if let Some(Ok(line)) = input.next() {
            let mut scores = line
                .trim()
                .split_whitespace()
                .map(|x| x.parse().unwrap())
                .collect::<Vec<usize>>();
            
            // Sort scores in descending order
            sort(&mut scores, n);
            
            // kth score is the cutoff
            writeln!(writer, "{}", scores[k - 1]).unwrap();
        }
    }
    writer.flush().unwrap();
}

Sample Input

1
2
5 2
100 76 85 93 98

Sample Output

1
98

Real-World Applications

Insertion Sort shines in several practical scenarios:

  • Sorting small files
  • Online sorting (sorting data as it is received)
  • Mixed sorting (when data is partially sorted)
  • When simplicity is preferred over efficiency

When to Use Insertion Sort?

Consider using Insertion Sort when:

  1. The data set is small (less than 50 elements)
  2. The input is nearly sorted
  3. You need a simple, stable sorting algorithm
  4. Memory space is limited
  5. You’re implementing adaptive sorting

Common Interview Questions

  1. Q: Why use Insertion Sort when there are more efficient algorithms? A: Its simplicity and efficiency for small or nearly sorted datasets make it practical in specific scenarios.

  2. Q: What makes Insertion Sort “stable”? A: Equal elements maintain their relative positions after sorting.

Conclusion

While Insertion Sort may not be the fastest sorting algorithm, its simplicity and efficiency in specific scenarios make it a valuable tool in any programmer’s arsenal. When combined with Rust’s safety features, it provides a reliable sorting solution for appropriate use cases.

🔍 Further Reading

💻 Practice Exercises

  1. Modify the implementation to sort strings
  2. Implement a version that sorts in descending order
  3. Add error handling for edge cases
  4. Create a generic version that works with any comparable type