Sort Array
- Given an array of i32
- Sort it in ascending order
The point:
- Quicksort
- Place each number in its sorted position one at a time
- Move all number smaller to the left (resp. greater, right). Partitioning
- Number on the left (right) may not be ordered
- The number is called pivot. Must be in correct position
- Recursively sort left and right part
- Partition
- Pivot selection : rightmost number
- 2 ptrs : low and i set on left. If
i<pivotswap i and low then move low to the right
Complexity :
| Time | Space |
|---|---|
| O(n log(n)) | O(log(n)) |
- O(n log(n)) in time because it divides the array in 2 (more or less). So the tree is in log2(n). For each level, perform partition in O(n) => O(n log(n))
- Worst case O(n²)
- O(log(n)) in space because of the depth of the recursive call stack (can grow up to log2(n)).
- Worst case O(n)
V1
- First implementation
About Rust :
- YES : tested on the Rust Playground
fn partition( nums : &mut [i32], left : usize, right : usize) -> usize {
let pivot = nums[right];
let mut low = left;
// Move all numbers less than pivot to the left (=> numbers greater than pivot are on the right)
for i in left..right{
if nums[i] < pivot{
nums.swap(low, i);
low +=1;
}
}
// After partition low is where the pivot should be => swap the pivot number with the number at low pointer
nums.swap(low, right);
low
}
fn quicksort(nums : &mut [i32], left : usize, right : usize) {
// Base case : if subarray has 0 or 1 element it is sorted
if left>=right{
return;
}
// Partition the array and retrieve the pivot index
let pivot_index = partition(nums, left, right);
// Call quicksort on left and right parts
quicksort(nums, left, pivot_index.saturating_sub(1)); // prevent underflow
quicksort(nums, pivot_index+1, right);
}
fn sort_array(nums : &mut [i32]) -> Vec<i32> {
quicksort(nums, 0, nums.len()-1);
nums.to_vec()
}
fn main() { // no main() if this code runs in a Jupyter cell
let mut vals = vec![6, 8, 4, 2, 7, 3, 1, 5];
println!("{:?}", sort_array(&mut vals)); // [1, 2, 3, 4, 5, 6, 7, 8]
} // end of local scope OR end of main()
Optimized
- Try to avoid to select extreme pivot : one larger or smaller than most other element
- Random pivot
- First implementation
About Rust :
- YES : tested on the Rust Playground
// # Cargo.toml
// [dependencies]
// rand = "0.9.1"
// If in a Jupyter cell
// :dep rand = "0.9.1"
// No reference to rand needed in Rust playground
use rand::Rng;
fn partition( nums : &mut [i32], left : usize, right : usize)->usize{
let pivot = nums[right];
let mut low = left;
// Move all numbers less than pivot to the left (=> numbers greater than pivot are on the right)
for i in left..right{
if nums[i] < pivot{
nums.swap(low, i);
low +=1;
}
}
// After partition low is where the pivot should be => swap the pivot number with the number at low pointer
nums.swap(low, right);
low
}
fn quicksort_optimized(nums : &mut [i32], left : usize, right : usize){
// Base case : if subarray has 0 or 1 element it is sorted
if left>=right{
return;
}
// Choose a pivot at a random index
let mut rng = rand::rng();
let random_index = rng.random_range(left..=right);
// Swap the random pivot with rightmost element to position pivot at rightmost index
nums.swap(random_index, right);
// Partition the array and retrieve the pivot index
let pivot_index = partition(nums, left, right);
// Call quicksort on left and right parts
quicksort_optimized(nums, left, pivot_index.saturating_sub(1)); // prevent underflow
quicksort_optimized(nums, pivot_index+1, right);
}
fn sort_array(nums : &mut [i32]) -> Vec<i32>{
quicksort_optimized(nums, 0, nums.len()-1);
nums.to_vec()
}
fn main() { // no main() if this code runs in a Jupyter cell
let mut vals = vec![6, 8, 4, 2, 7, 3, 1, 5];
println!("{:?}", sort_array(&mut vals)); // [1, 2, 3, 4, 5, 6, 7, 8]
} // end of local scope OR end of main()
Optimized V2
sort_array()andquicksort_optimized()work in place on the array and do not return a new vector
About Rust :
- YES : tested on the Rust Playground
// # Cargo.toml
// [dependencies]
// rand = "0.9.1"
// If in a Jupyter cell
// :dep rand = "0.9.1"
// No reference to rand needed in Rust playground
use rand::Rng;
fn partition(nums: &mut [i32], left: usize, right: usize) -> usize {
let pivot = nums[right]; // The pivot is the value at the 'right' index
let mut low = left; // 'low' starts at 'left'
// Move all numbers less than pivot to the left
for i in left..right {
if nums[i] < pivot {
nums.swap(low, i);
low += 1;
}
}
// After partition low is where the pivot should be => swap the pivot number with the number at low pointer
nums.swap(low, right);
low
}
fn quicksort_optimized(nums: &mut [i32], left: usize, right: usize) {
// Base case : if subarray has 0 or 1 element it is sorted
if left >= right {
return;
}
// Choose a pivot at a random index
let mut rng = rand::rng();
let random_index = rng.random_range(left..=right);
// Swap the random pivot with rightmost element to position pivot on rightmost index
nums.swap(random_index, right);
// Partition the array and retrieve the pivot index
let pivot_index = partition(nums, left, right);
// Call quicksort on left and right parts
// To prevent underflow both options are possible
// if pivot_index > 0 {
// quicksort_optimized(nums, left, pivot_index - 1);
// }
quicksort_optimized(nums, left, pivot_index.saturating_sub(1));
quicksort_optimized(nums, pivot_index+1, right);
}
fn sort_array(nums: &mut [i32]) {
if nums.is_empty() {
return;
}
quicksort_optimized(nums, 0, nums.len() - 1);
}
fn main() { // no main() if this code runs in a Jupyter cell
let mut vals = vec![6, 8, 4, 2, 7, 3, 1, 5];
sort_array(&mut vals);
println!("{:?}", vals); // [1, 2, 3, 4, 5, 6, 7, 8]
} // end of local scope OR end of main()
Optimized V3
- Remove
leftandrightparameters fromquicksort_optimized()andpartition() - Random generator
rngcreated once insort_array()and passed as a parameter
About Rust :
- YES : tested on the Rust Playground
- Preferred solution?
Note about impl in quicksort() signature
- The compiler requires
impl Rngis a trait NOT a datatypeimpl Rngmeans the function accept anything that implementsRng, without worrying about the exact type
// # Cargo.toml
// [dependencies]
// rand = "0.9.1"
// If in a Jupyter cell
// :dep rand = "0.9.1"
// No reference to rand needed in Rust playground
use rand::Rng;
fn partition(nums: &mut [i32]) -> usize {
let pivot = nums[nums.len() - 1];
let mut low = 0;
// Move all numbers less than pivot to the left (=> numbers greater than pivot are on the right)
for i in 0..nums.len() - 1 {
if nums[i] < pivot {
nums.swap(low, i);
low += 1;
}
}
// After partition low is where the pivot should be => swap the pivot number with the number at low pointer
nums.swap(low, nums.len() - 1);
low
}
fn quicksort(nums: &mut [i32], rng: &mut impl Rng) {
// Base case : if subarray has 0 or 1 element it is sorted
if nums.len() <= 1 {
return;
}
// Choose a pivot at a random index
let random_index = rng.random_range(0..nums.len());
// Swap the random pivot with rightmost element to position pivot on rightmost index
nums.swap(random_index, nums.len() - 1);
let mid = partition(nums);
let (left, right) = nums.split_at_mut(mid);
// Call quicksort on left and right parts
quicksort(left, rng);
if right.len() > 1 {
quicksort(&mut right[1..], rng);
}
}
fn sort_array(nums: &mut [i32]) {
if nums.is_empty() {
return;
}
let mut rng = rand::rng();
quicksort(nums, &mut rng);
}
fn main() { // no main() if this code runs in a Jupyter cell
let mut vals = vec![6, 8, 4, 2, 7, 3, 1, 5];
sort_array(&mut vals);
println!("{:?}", vals); // [1, 2, 3, 4, 5, 6, 7, 8]
} // end of local scope OR end of main()
Counting sort V1
- The array only have u32
- All values are less or equal to 1_000
The point:
- We know the largest possible number and is not that large
- Non comparaison approach
- Count number of occurrences
- Create an n size array
counts - Increment the value at each index based on how many occurrences of the val we have
- Build sorted array
result- Iterate
countsand addiaccording tocounts[i]
- Iterate
Complexity :
| Time | Space |
|---|---|
| O(n+k) | O(n+k) |
- O(n+k) in time because if
kis the max val ofnums, it take O(n) to count occurrences of each element and O(k) to build the sorted array - O(n+k) in space because
resid O(n) and thecountsis up to O(k)
About Rust :
- First implementation
- YES : tested on the Rust Playground
fn sort_array_counting_sort(nums: &[i32])-> Vec<i32> {
if nums.is_empty() {
return vec![];
}
// Count occurrences of each element in nums
let max_value = *nums.iter().max().unwrap(); // Get the max value (unwrap because max() returns an Option<&T>)
let mut counts = vec![0; (max_value as usize) + 1]; // Create a vector of zeros
for num in nums{
counts[*num as usize]+=1;
}
// For each value `i`, repeat `i` exactly `counts[i]` times and add all to the `res` vector
let mut res:Vec<i32> = Vec::new();
for (i, count) in counts.iter().enumerate(){
res.extend(&vec![i as i32; *count as usize]);
}
res
}
fn main() { // no main() if this code runs in a Jupyter cell
let vals = vec![6, 8, 4, 2, 7, 3, 1, 5];
println!("{:?}", sort_array_counting_sort(&vals)); // [1, 2, 3, 4, 5, 6, 7, 8]
} // end of local scope OR end of main()
Counting sort V2
- Replace
for num in nums{withfor &num in nums {to avoid*numin the loop - Create
reswithVec::with_capacity() - Avoid to use
vec![]in the loop- it allocates a new vector each time
- Use
std::iter::repeat(...).take(...)instead
for (i, count) in...replaced withfor (i, &count) in...
About Rust :
- YES : tested on the Rust Playground
fn sort_array_counting_sort(nums: &[i32]) -> Vec<i32> {
if nums.is_empty() {
return Vec::new();
}
// Count occurrences of each element in nums
let max_value = *nums.iter().max().unwrap(); // Get the max value (unwrap because it returns an Option<&T>)
let mut counts = vec![0; (max_value as usize) + 1]; // Create a vector of zeros
for &num in nums {
counts[num as usize] += 1; // Increment count for each number
}
// Pre-allocate result vector with capacity equal to input length
let mut res = Vec::with_capacity(nums.len());
// For each value `i`, repeat `i` exactly `counts[i]` times and add all to the `res` vector
for (i, &count) in counts.iter().enumerate() {
// res.extend(std::iter::repeat(i as i32).take(count as usize));
res.extend(std::iter::repeat_n(i as i32, count as usize));
}
res
}
fn main() { // no main() if this code runs in a Jupyter cell
let vals = vec![6, 8, 4, 2, 7, 3, 1, 5];
println!("{:?}", sort_array_counting_sort(&vals)); // [1, 2, 3, 4, 5, 6, 7, 8]
} // end of local scope OR end of main()
Counting sort V3
- Avoid
.unwrap()
About Rust :
let Some(&max_value) = nums.iter().max() else {.... Noif. This is direct destructuring.
let PATTERN = EXPR else {
... // code if pattern matching fails
};
- YES : tested on the Rust Playground
Note about Some(&max_value)
iter(): produces &i32 references.max(): returns a reference to the maximum element: Option<&i32>.- At the end we get an
Option<&i32>, NOT anOption<i32>.
fn sort_array_counting_sort(nums: &[i32]) -> Vec<i32> {
// If the array is empty, return an empty vector
let Some(&max_value) = nums.iter().max() else {
return Vec::new();
};
// Count occurrences of each element in nums
let mut counts = vec![0; (max_value as usize) + 1]; // Create a vector of zeros
for &num in nums {
counts[num as usize] += 1; // Increment count for each number
}
// Pre-allocate result vector with capacity equal to input length
let mut res = Vec::with_capacity(nums.len());
// For each value `i`, repeat `i` exactly `counts[i]` times and add all to the `res` vector
for (i, &count) in counts.iter().enumerate() {
res.extend(std::iter::repeat_n(i as i32, count as usize));
}
res
}
fn main() { // no main() if this code runs in a Jupyter cell
let vals = vec![6, 8, 4, 2, 7, 3, 1, 5];
println!("{:?}", sort_array_counting_sort(&vals)); // [1, 2, 3, 4, 5, 6, 7, 8]
} // end of local scope OR end of main()
Counting sort V4
About Rust :
- No
.repeat()but.push(). Should be faster. - YES : tested on the Rust Playground
- Preferred solution?
fn sort_array_counting_sort(nums: &[i32]) -> Vec<i32> {
// If the array is empty, return an empty vector
let Some(&max_value) = nums.iter().max() else {
return Vec::new();
};
// Count occurrences of each element in nums
let mut counts = vec![0; (max_value as usize) + 1]; // Create a vector of zeros
for &num in nums {
counts[num as usize] += 1; // Increment count for each number
}
// Pre-allocate result vector with capacity equal to input length
let mut res = Vec::with_capacity(nums.len());
// For each value `i`, repeat `i` exactly `counts[i]` times and add all to the `res` vector
for (i, &count) in counts.iter().enumerate() {
for _ in 0..count {
res.push(i as i32); // Push `i` `count` times into `res`
}
}
res
}
fn main() { // no main() if this code runs in a Jupyter cell
let vals = vec![6, 8, 4, 2, 7, 3, 1, 5];
println!("{:?}", sort_array_counting_sort(&vals)); // [1, 2, 3, 4, 5, 6, 7, 8]
} // end of local scope OR end of main()