插入排序
主问题重现
我们在下面用形式化语言1 (已加之修改)给出主问题的一个重现:
输入
个数的数组
输出
输入数组的升序排列 ,满足
主问题思考
在《算法导论》中,我们先引入了插入排序,这并不特别简单(不如冒泡)但可以让我们进行较高级的初步学习。
来看下面实现:
实现1
pub fn realize<T: Ord>(arr: &mut [T]) {
for i in 1..arr.len() {
let mut j = i;
while j > 0 && arr[j] < arr[j - 1] {
arr.swap(j, j - 1);
j -= 1;
}
}
}
可以发现: 插入排序的核心逻辑是通过逐步比较和移动元素来维护一个有序的子序列。具体地:
- 有序部分逐步扩张:将数组分为已排序部分(初始仅含第一个元素)和未排序部分,每次从未排序部分取出第一个元素,将其插入到已排序部分的正确位置,直到所有元素有序。
- 原地插入:插入操作通过依次向后移动元素实现,无需额外空间。
在《算法导论》中,其实是不是通过swap
,而是将操作值保存,直到最后再进行赋值,这样可以减少中间的连续交换导致的空间和时间损耗。代价是需要更高的数学水平,同时需要更多的trait
(像接下来的实现)。
实现2
pub fn realize2<T: Ord + Copy>(arr: &mut [T]) {
for i in 1..arr.len() {
// 保存当前元素的值
let key = arr[i];
// 找到合适的插入位置
let mut j = i;
while j > 0 && key < arr[j - 1] {
// 向后移动元素而不是交换
arr[j] = arr[j - 1];
j -= 1;
}
// 将保存的值插入到正确位置
arr[j] = key;
}
}
(注: 需要Copy
)
也可以使用Clone
:
pub fn realize2_clone<T: Ord + Clone>(arr: &mut [T]) {
for i in 1..arr.len() {
// 保存当前元素的值
let key = arr[i].clone();
// 找到合适的插入位置
let mut j = i;
while j > 0 && key < arr[j - 1] {
// 向后移动元素而不是交换
arr[j] = arr[j - 1].clone();
j -= 1;
}
// 将保存的值插入到正确位置
arr[j] = key;
}
}
练习与回答
- 尝试将实现1改为降序排列(满足 )。
实现3
// 对应练习 1
pub fn realize3<T: Ord>(arr: &mut [T]) {
for i in 0..arr.len() - 1 {
let mut j = i;
while j > 0 && arr[j] > arr[j - 1] {
arr.swap(j, j - 1);
j -= 1;
}
}
}
注: 可以发现: 在插入排序中,只需修改比较条件即可改变排序方向
所以我们也有实现4这种可以设置排序规则的:
实现4
// 实现以`compare`来控制排序规则的插入排序
pub fn realize4<T, F>(arr: &mut [T], compare: F)
where
F: Fn(&T, &T) -> bool,
{
for i in 1..arr.len() {
let mut j = i;
while j > 0 && compare(&arr[j], &arr[j - 1]) {
arr.swap(j, j - 1);
j -= 1;
}
}
}
上面这种比较,对于学习过其他语言的读者来说,应该不成问题。这个比较很显然的允许我们操作一些更特殊的类型,比如说结构体:
#[derive(Debug)]
struct Person {
name: String,
age: u32,
}
fn main() {
// 创建可变的结构体数组
let mut people = vec![
Person { name: "Alice".to_string(), age: 30 },
Person { name: "Bob".to_string(), age: 25 },
Person { name: "Charlie".to_string(), age: 35 },
];
// 按年龄升序排序
realize4(&mut people, |a, b| a.age < b.age);
println!("按年龄排序: {:?}", people);
// 按名字字典序排序
realize4(&mut people, |a, b| a.name < b.name);
println!("按名字排序: {:?}", people);
}
我们再来看别的例题,继续深化学习:
- 考虑以下查找问题:
输入: 个数的数组 和一个值
输出: 下标使得,或者当不在出现时,返回特殊值2
尝试给出一个线性查找3的例子。
观察来看,这道题目适合
Option
可以用Option::None
来解决NIL
。
实现5
pub fn realize5<T: PartialEq>(arr: &[T], v: T) -> Option<usize> {
for i in 0..arr.len() {
if v == arr[i] {
return Some(i);
}
}
None
}