Алгоритм quicksort "быстрая сортировка". Роберт Седживик, Дональд Э. Кнут. Rust
//
// @author ESV Corp. (C) 17.09.2026
//
// "проба пера" на Rust
// алгоритм: "быстрая сортировка", quicksort
// Р. Седжвик "Алгоритмы",
// Д. Кнут "Искусство программирования", т.3 "Сортировка и поиск",
// глава 5.2.2, раздел "Обменная сортировка"
//
// сначала происходит разделение массива на подмассивы, размер которых
// подходит для быстрой сортировки методом вставок, далее каждый подмассив
// сортируется методом вcтавки
// сортировка всего массива производится "по месту", т.е. без создания дополнительных
// массивов
//
// реализация стека в отдельном модуле (файл stack.rs или stack/mod.rs)
mod stack;
// используем структуру QsStack
use stack::QsStack;
const N: usize = 20; // размер списка (массива) для сортировки
const M: usize = 5; // размер подмассива для сортировки методом вставок
fn main() {
println!("Robert Sedgewick, Donald E. Knuth algorithms: quicksort");
// исходный список элементов
let mut list: [i32; N] = [5, 14, 2, 7, 1, 13, -5, 10, 4, -3, 7, 0, 12, 6, 15, 9, 11, 8, 3, 12];
println!("source: {:?}", list);
quicksort(0, N-1, &mut list);
println!("sorted: {:?}", list);
// исходный список элементов произвольной длины
// let mut list: Vec<i32> = vec![5, 14, 2, 7, -1, -2, -5, 10, 4, -3, 7, 0, 12, 6, 15, 7, 11, 8, 3, 12, 1, 22, 33, 0x2F, -10];
let mut list: Vec<i32> = vec![5, 14, 2, 7, -1, -2, -5, 10, 4, -3, 7, 0, 12, 6, 15, 7, 11, 8, 3, 12, 1, 22, 33, 0x2F, -10, 2, 1, -10, 3, 50];
println!("\n\n{}\n\n", "*".repeat(30));
println!("vec source: {:?}", list);
quicksort(0, list.len()-1, &mut list);
println!("vec sorted: {:?}", list);
// исходный список элементов произвольной длины
let mut list: Vec<i32> = vec![5, 14, 2, 7, -1, -2, -5, 10, 4, -3, 7, 0, 1, 9, 12, 6, 15, 7, 11, 8, 3, 12, 1, 22, 33, 0x2F, -7, 2, 1, -10, 3, 50];
println!("\n\n{}\n\n", "*".repeat(30));
println!("vec stack source: {:?}", list);
quicksort_stack(0, list.len()-1, &mut list);
println!("vec stack sorted: {:?}", list);
}
/// сортировка массива методом quicksort ("быстрая сортировка")
///
/// # Parameters
/// `l` - левый индекс
/// `r` - правый индекс
/// `list` - массив (срез) для сортировки
fn quicksort(l: usize, r: usize, list: &mut [i32]) {
if l >= r { return; }
#[cfg(debug_assertions)]
{
let qs_slice = &list[l..=r];
println!("\n+++\nquicksort slice ({l}-{r}): {:?}", qs_slice);
}
// разделяем на подмассивы
let s = split(l, r, list);
#[cfg(debug_assertions)]
{
let center_elem = list[s];
let left_slice = &list[l..s];
let right_slice = &list[s+1..=r];
println!("---");
println!("splited:");
println!("index = {s}, list[s] = {center_elem}");
println!("left = {:?}", left_slice);
println!("right = {:?}", right_slice);
println!("{:?} {} {:?}", left_slice, center_elem, right_slice);
println!("===");
}
let left_size = s - l;
let right_size = r - s;
// сортировка левой части
if left_size > 1 {
let right_index = s - 1;
let slice = &mut list[l..s];
if left_size > M {
#[cfg(debug_assertions)]
println!("quicksort left side ({}-{}):\n{:?}", l, right_index, slice);
quicksort(l, right_index, list);
} else {
#[cfg(debug_assertions)]
println!("sort_by_inserts left size ({}-{}): {:?}", l, right_index, slice);
sort_by_inserts(slice);
}
}
// сортировка правой части
if right_size > 1 {
let left_index = s + 1;
let slice = &mut list[left_index..=r];
if right_size > M {
#[cfg(debug_assertions)]
println!("quicksort right side ({}-{}):\n{:?}", left_index, r, slice);
quicksort(left_index, r, list);
} else {
#[cfg(debug_assertions)]
println!("sort_by_inserts right size ({}-{}): {:?}", left_index, r, slice);
sort_by_inserts(slice);
}
}
}
/// сортировка массива методом quicksort ("быстрая сортировка")
/// вместо рекурсивного вызова используем стек
///
/// # Parameters
/// `l` - левый индекс
/// `r` - правый индекс
/// `list` - массив (срез) для сортировки
fn quicksort_stack(l: usize, r: usize, list: &mut [i32]) {
if l >= r { return; }
#[cfg(debug_assertions)]
{
let qs_slice = &list[l..=r];
println!("quicksort with stack ({l}-{r}): {:?}", qs_slice);
}
let mut stack = QsStack::new((l, r));
while stack.qs_exists() {
let (l, r) = stack.qs_pop().unwrap();
// делим список
let s = split(l, r, list);
#[cfg(debug_assertions)]
{
let center_elem = list[s];
let left_slice = &list[l..s];
let right_slice = &list[s+1..=r];
println!("---");
println!("splited:");
println!("index = {s}, list[s] = {center_elem}");
println!("left = {:?}", left_slice);
println!("right = {:?}", right_slice);
println!("{:?} {} {:?}", left_slice, center_elem, right_slice);
println!("===");
}
let left_size = s - l;
let right_size = r - s;
if left_size > 1 {
if left_size > M {
stack.qs_push((l, s - 1));
} else {
stack.ins_push((l, s - 1));
}
}
if right_size > 1 {
if right_size > M {
stack.qs_push((s + 1, r));
} else {
stack.ins_push((s + 1, r));
}
}
}
// сортировка кооротких подмассивов методом вставки
while stack.ins_exists() {
let (l, r) = stack.ins_pop().unwrap();
let slice = &mut list[l..=r];
sort_by_inserts(slice);
}
}
/// "разделение" массива на подмассивы:
/// в результате возвращается индекс (index) в массиве, такой, что
/// элементы list[l..index] <= list[index] <= list[index+1..=r]
///
/// # Parameters
///
/// * `l` — левая (меньшая) граница исходного массива
/// * `r` — правая (большая) граница исходного массива
/// * `list` — изменяемый массив, который разделяется
///
/// # Returns
///
/// Индекс элемент в массиве, который делит массив на левую и правую части
///
fn split(l: usize, r: usize, list: &mut [i32]) -> usize {
#[cfg(debug_assertions)]
{
let n: usize = list.len();
debug_assert!(n > 1, "split: nothing to split");
debug_assert!(r > l, "split: right must be greater than left");
}
let mut i = l;
let mut j = r + 1;
let k = list[l];
while i < j {
// гарантированный сдвиг индексов
// !!! важно: этот код выполняется и после обмена соседних элементов [i] и [j]
// при этом более важен j -= 1 , т.к. после этого он будет указывать
// на элемент, который подлежит обмену с "центральным" элементом после
// выхода из цикла while i < j
i += 1;
j -= 1;
while i < r && k > list[i] { i += 1; }
while j > l && k < list[j] { j -= 1; }
// обмен элементов из левой и правой частей
if i < j {
// обмен элементов
if list[i] != list[j] {
(list[i], list[j]) = (list[j], list[i]);
}
}
}
// обмен левого элемента и "центрального", который должен находится между разделёнными массивами
if l != j {
// установим "центральный" элемент между массивами "меньше" и "больше"
(list[l], list[j]) = (list[j], list[l]);
}
j
}
// ===
// сортировка методом вставок
// пример из ппердыдущей реализации
fn sort_by_inserts(list: &mut [i32]) {
let n: usize = list.len();
debug_assert!(n > 1, "sort_by_inserts: nothing to sort");
#[cfg(debug_assertions)]
println!("sort_by_inserts source: {:?}", list);
let mut j: usize = 1;
while j < n {
let t: i32 = list[j];
let mut i = j;
while i > 0 {
let check = list[i-1];
if check < t { break; }
list[i] = check;
i -= 1;
}
// в оригинальном агоритме Кнута значение может быть
// записано в ту же самую позицию
// видимо потому, что лишняя проверка индексов дополнительно занимает
// память программы, и ещё и для выполнения требует время
// но использую своё дополнение - с проверкой индексов, чтобы не перезаписывать
// одно и то же значение
if i < j { list[i] = t; }
j += 1;
}
#[cfg(debug_assertions)]
println!("sort_by_inserts sorted: {:?}", list);
}
// ===
// модуль тестов
//
#[cfg(test)]
mod split_tests {
use super::*;
#[test]
fn split_tests() {
let mut list = [5, 30, 20, 1, 2];
let j = split(0, list.len() - 1, &mut list);
assert_eq!(j, 2);
assert_eq!(list[j], 5);
assert_eq!(list[0..j], [1, 2]);
assert_eq!(list[j+1..list.len()], [20, 30]);
let mut list = [5, 30, 20, 1, 2, 50];
let j = split(0, list.len() - 1, &mut list);
assert_eq!(j, 2);
assert_eq!(list[j], 5);
assert_eq!(list[0..j], [1, 2]);
assert_eq!(list[j+1..list.len()], [20, 30, 50]);
let mut list = [5, 30, 50, 20, 3, 2, 1];
let j = split(0, list.len() - 1, &mut list);
assert_eq!(j, 3);
assert_eq!(list[j], 5);
assert_eq!(list[0..j], [3, 1, 2]);
assert_eq!(list[j+1..list.len()], [20, 50, 30]);
let mut list = [100, 30, 50, 20, 3, 2, -1, 0, 10];
let j = split(0, list.len() - 1, &mut list);
assert_eq!(j, 8);
assert_eq!(list[j], 100);
assert_eq!(list[0..j], [10, 30, 50, 20, 3, 2, -1, 0]);
assert_eq!(list[j+1..list.len()], []);
let mut list = [-100, 30, 50, 20, 3, 2, -1, 0, 10];
let j = split(0, list.len() - 1, &mut list);
assert_eq!(j, 0);
assert_eq!(list[j], -100);
assert_eq!(list[0..j], []);
assert_eq!(list[j+1..list.len()], [30, 50, 20, 3, 2, -1, 0, 10]);
}
}
#[cfg(test)]
mod quicksort_tests {
use super::*;
#[test]
fn quicksort_small_tests() {
let mut list = [1];
quicksort(0, list.len()-1, &mut list);
assert_eq!(list, [1]);
let mut list = [1, 2];
quicksort(0, list.len()-1, &mut list);
assert_eq!(list, [1, 2]);
let mut list = [2, 1];
quicksort(0, list.len()-1, &mut list);
assert_eq!(list, [1, 2]);
}
#[test]
fn quicksort_tests() {
let mut list = [-100, 1, 2, 3, 4];
quicksort(0, list.len()-1, &mut list);
assert_eq!(list, [-100, 1, 2, 3, 4]);
let mut list = [-100, 1, 2, 3, 4];
quicksort(0, list.len()-1, &mut list);
assert_eq!(list, [-100, 1, 2, 3, 4]);
let mut list = [5, 30, 20, 1, 2];
quicksort(0, list.len()-1, &mut list);
assert_eq!(list, [1, 2, 5, 20, 30]);
let mut list = [5, 30, 20, 1, 2, 6];
quicksort(0, list.len()-1, &mut list);
assert_eq!(list, [1, 2, 5, 6, 20, 30]);
let mut list = [0, 4, 3, 2, 1, -1, -100];
quicksort(0, list.len()-1, &mut list);
assert_eq!(list, [-100, -1, 0, 1, 2, 3, 4]);
let mut list = [0, 1, 3, 2, 4, -1, 100];
quicksort(0, list.len()-1, &mut list);
assert_eq!(list, [-1, 0, 1, 2, 3, 4, 100]);
let mut list = [100, 1, 2, 3, 4, 0, -1];
quicksort(0, list.len()-1, &mut list);
assert_eq!(list, [-1, 0, 1, 2, 3, 4, 100]);
let mut list = [0, 1, 2, 100, 3, -1, 4];
quicksort(0, list.len()-1, &mut list);
assert_eq!(list, [-1, 0, 1, 2, 3, 4, 100]);
let mut list = [5, 1, 2, 100, -1, 4, 0];
quicksort(0, list.len()-1, &mut list);
assert_eq!(list, [-1, 0, 1, 2, 4, 5, 100]);
}
#[test]
fn quicksort_test_several_eq() {
let mut list = [5, 1, 2, 100, 2, 2, 0];
quicksort(0, list.len()-1, &mut list);
assert_eq!(list, [0, 1, 2, 2, 2, 5, 100]);
let mut list = [5, 1, 2, 100, 2, 2, 0, -100];
quicksort(0, list.len()-1, &mut list);
assert_eq!(list, [-100, 0, 1, 2, 2, 2, 5, 100]);
}
#[test]
fn quicksort_test_all_eq() {
let mut list = [1, 1, 1, 1, 1, 1, 1];
quicksort(0, list.len()-1, &mut list);
assert_eq!(list, [1, 1, 1, 1, 1, 1, 1]);
let mut list = [3, 3, 3, 3, 3, 3, 3, 3];
quicksort(0, list.len()-1, &mut list);
assert_eq!(list, [3, 3, 3, 3, 3, 3, 3, 3]);
}
#[test]
fn quicksort_test_sorted() {
let mut list = [1, 2, 3, 4, 5, 6];
quicksort(0, list.len()-1, &mut list);
assert_eq!(list, [1, 2, 3, 4, 5, 6]);
let mut list = [6, 5, 4, 3, 2, 1];
quicksort(0, list.len()-1, &mut list);
assert_eq!(list, [1, 2, 3, 4, 5, 6]);
let mut list = [1, 2, 3, 4, 5, 6, 7];
quicksort(0, list.len()-1, &mut list);
assert_eq!(list, [1, 2, 3, 4, 5, 6, 7]);
let mut list = [7, 6, 5, 4, 3, 2, 1];
quicksort(0, list.len()-1, &mut list);
assert_eq!(list, [1, 2, 3, 4, 5, 6, 7]);
}
#[test]
fn quicksort_stack_small_tests() {
let mut list = [1];
quicksort_stack(0, list.len()-1, &mut list);
assert_eq!(list, [1]);
let mut list = [1, 2];
quicksort_stack(0, list.len()-1, &mut list);
assert_eq!(list, [1, 2]);
let mut list = [2, 1];
quicksort_stack(0, list.len()-1, &mut list);
assert_eq!(list, [1, 2]);
}
#[test]
fn quicksort_stack_tests() {
let mut list = [-100, 1, 2, 3, 4];
quicksort_stack(0, list.len()-1, &mut list);
assert_eq!(list, [-100, 1, 2, 3, 4]);
let mut list = [5, 30, 20, 1, 2];
quicksort(0, list.len()-1, &mut list);
assert_eq!(list, [1, 2, 5, 20, 30]);
let mut list = [-100, 1, 2, 3, 4, 100];
quicksort_stack(0, list.len()-1, &mut list);
assert_eq!(list, [-100, 1, 2, 3, 4, 100]);
let mut list = [0, 4, 3, 2, 1, -1, -100];
quicksort_stack(0, list.len()-1, &mut list);
assert_eq!(list, [-100, -1, 0, 1, 2, 3, 4]);
let mut list = [0, 1, 3, 2, 4, -1, 100];
quicksort_stack(0, list.len()-1, &mut list);
assert_eq!(list, [-1, 0, 1, 2, 3, 4, 100]);
let mut list = [100, 1, 2, 3, 4, 0, -1];
quicksort_stack(0, list.len()-1, &mut list);
assert_eq!(list, [-1, 0, 1, 2, 3, 4, 100]);
let mut list = [0, 1, 2, 100, 3, -1, 4];
quicksort_stack(0, list.len()-1, &mut list);
assert_eq!(list, [-1, 0, 1, 2, 3, 4, 100]);
let mut list = [5, 1, 2, 100, -1, 4, 0];
quicksort_stack(0, list.len()-1, &mut list);
assert_eq!(list, [-1, 0, 1, 2, 4, 5, 100]);
}
#[test]
fn quicksort_stack_test_several_eq() {
let mut list = [5, 1, 2, 100, 2, 2, 0];
quicksort_stack(0, list.len()-1, &mut list);
assert_eq!(list, [0, 1, 2, 2, 2, 5, 100]);
}
#[test]
fn quicksort_stack_test_all_eq() {
let mut list = [1, 1, 1, 1, 1, 1, 1];
quicksort_stack(0, list.len()-1, &mut list);
assert_eq!(list, [1, 1, 1, 1, 1, 1, 1]);
let mut list = [3, 3, 3, 3, 3, 3];
quicksort_stack(0, list.len()-1, &mut list);
assert_eq!(list, [3, 3, 3, 3, 3, 3]);
}
#[test]
fn quicksort_stack_test_sorted() {
let mut list = [1, 2, 3, 4, 5, 6];
quicksort_stack(0, list.len()-1, &mut list);
assert_eq!(list, [1, 2, 3, 4, 5, 6]);
let mut list = [6, 5, 4, 3, 2, 1];
quicksort_stack(0, list.len()-1, &mut list);
assert_eq!(list, [1, 2, 3, 4, 5, 6]);
let mut list = [1, 2, 3, 4, 5, 6, 7];
quicksort_stack(0, list.len()-1, &mut list);
assert_eq!(list, [1, 2, 3, 4, 5, 6, 7]);
let mut list = [7, 6, 5, 4, 3, 2, 1];
quicksort_stack(0, list.len()-1, &mut list);
assert_eq!(list, [1, 2, 3, 4, 5, 6, 7]);
}
}
Для пробы реализовал стек в отдельной структуре и в отдельном модуле:
//
// @author ESV Corp. (C) 17.09.2026
//
// модуль реализации стека
//
pub struct QsStack {
qs_size: usize,
qs_stack: Vec<(usize, usize)>,
ins_size: usize,
ins_stack: Vec<(usize, usize)>,
}
impl QsStack {
pub fn new(init: (usize, usize)) -> Self {
Self {
qs_size: 1,
ins_size: 0,
qs_stack: vec![init],
ins_stack: Vec::new(),
}
}
pub fn qs_push(&mut self, el: (usize, usize)) {
self.qs_stack.push(el);
self.qs_size += 1;
}
pub fn qs_pop(&mut self) -> Option<(usize, usize)> {
if self.qs_exists() {
self.qs_size -= 1;
self.qs_stack.pop()
} else {
None
}
}
pub fn qs_exists(&self) -> bool { self.qs_size > 0 }
// блоки для сортировки методом вставок
pub fn ins_push(&mut self, el: (usize, usize)) {
self.ins_stack.push(el);
self.ins_size += 1;
}
pub fn ins_pop(&mut self) -> Option<(usize, usize)> {
if self.ins_exists() {
self.ins_size -= 1;
self.ins_stack.pop()
} else {
None
}
}
pub fn ins_exists(&self) -> bool { self.ins_size > 0 }
}