在数学的世界里,有许多简单易懂但至今未解的猜想。考拉兹猜想(Collatz Conjecture)就是这样一个著名的问题,它由德国数学家洛塔尔·考拉兹在1937年提出。这个猜想的规则非常简单,但其行为却异常复杂,至今仍未被证明或证伪。在 Exercism 的 “collatz-conjecture” 练习中,我们将实现这个有趣的数学算法,这不仅能帮助我们掌握递归和迭代算法的实现,还能深入学习 Rust 中的数值处理和错误处理机制。
什么是考拉兹猜想?
考拉兹猜想的规则如下:
- 从任意正整数 n 开始
- 如果 n 是偶数,则下一步是 n/2
- 如果 n 是奇数,则下一步是 3n + 1
- 重复这个过程,直到达到 1
猜想认为,无论从哪个正整数开始,最终都会到达 1。
例如,从 12 开始:
12 → 6 → 3 → 10 → 5 → 16 → 8 → 4 → 2 → 1
这个序列包含 9 个步骤。
让我们先看看练习提供的函数签名:
pub fn collatz(n: u64) -> Option<u64> {
unimplemented!(
"return Some(x) where x is the number of steps required to reach 1 starting with {}",
n,
)
}
我们需要实现这个函数,它应该:
- 接收一个正整数 n
- 计算从 n 到达 1 所需的步骤数
- 如果输入是 0,则返回 None(因为 0 不是正整数)
算法实现
1. 迭代实现
pub fn collatz(n: u64) -> Option<u64> {
if n == 0 {
return None;
}
let mut steps = 0;
let mut current = n;
while current != 1 {
if current % 2 == 0 {
current /= 2;
} else {
current = 3 * current + 1;
}
steps += 1;
}
Some(steps)
}
这个实现使用迭代方法,逐步计算每一步直到达到 1。
2. 递归实现
pub fn collatz(n: u64) -> Option<u64> {
if n == 0 {
return None;
}
Some(collatz_steps(n, 0))
}
fn collatz_steps(n: u64, steps: u64) -> u64 {
if n == 1 {
steps
} else if n % 2 == 0 {
collatz_steps(n / 2, steps + 1)
} else {
collatz_steps(3 * n + 1, steps + 1)
}
}
递归实现更加简洁,但可能会遇到栈溢出问题。
3. 优化迭代实现
pub fn collatz(n: u64) -> Option<u64> {
if n == 0 {
return None;
}
let mut steps = 0;
let mut current = n;
while current != 1 {
match current {
even if even % 2 == 0 => current /= 2,
odd => current = odd.checked_mul(3)?.checked_add(1)?,
}
steps += 1;
}
Some(steps)
}
这个版本使用了 checked_mul 和 checked_add 来防止整数溢出。
测试用例分析
通过查看测试用例,我们可以更好地理解需求:
#[test]
fn test_1() {
assert_eq!(Some(0), collatz(1));
}
从 1 开始需要 0 步。
#[test]
fn test_16() {
assert_eq!(Some(4), collatz(16));
}
从 16 开始:16 → 8 → 4 → 2 → 1,需要 4 步。
#[test]
fn test_12() {
assert_eq!(Some(9), collatz(12));
}
从 12 开始需要 9 步。
#[test]
fn test_1000000() {
assert_eq!(Some(152), collatz(1_000_000));
}
较大的数字需要更多步骤。
#[test]
fn test_0() {
assert_eq!(None, collatz(0));
}
输入 0 应该返回 None。
完整实现
考虑所有边界情况的完整实现:
pub fn collatz(n: u64) -> Option<u64> {
if n == 0 {
return None;
}
let mut steps = 0;
let mut current = n;
while current != 1 {
if current % 2 == 0 {
current /= 2;
} else {
// 检查是否会溢出
current = current.checked_mul(3)?.checked_add(1)?;
}
steps += 1;
}
Some(steps)
}
使用记忆化优化
为了提高性能,我们可以使用记忆化技术:
use std::collections::HashMap;
pub fn collatz(n: u64) -> Option<u64> {
if n == 0 {
return None;
}
let mut memo = HashMap::new();
Some(collatz_with_memo(n, &mut memo))
}
fn collatz_with_memo(n: u64, memo: &mut HashMap<u64, u64>) -> u64 {
if n == 1 {
return 0;
}
if let Some(&steps) = memo.get(&n) {
return steps;
}
let steps = if n % 2 == 0 {
1 + collatz_with_memo(n / 2, memo)
} else {
1 + collatz_with_memo(3 * n + 1, memo)
};
memo.insert(n, steps);
steps
}
错误处理和边界情况
考虑更多边界情况的实现:
pub fn collatz(n: u64) -> Option<u64> {
// 处理无效输入
if n == 0 {
return None;
}
let mut steps = 0;
let mut current = n;
// 使用 checked arithmetic 防止溢出
while current != 1 {
if current % 2 == 0 {
current /= 2;
} else {
// 检查 3 * current + 1 是否会溢出
current = match current.checked_mul(3) {
Some(val) => match val.checked_add(1) {
Some(result) => result,
None => return None, // 溢出
},
None => return None, // 溢出
};
}
steps += 1;
}
Some(steps)
}
性能优化版本
考虑性能的优化实现:
pub fn collatz(n: u64) -> Option<u64> {
if n == 0 {
return None;
}
let mut steps = 0;
let mut current = n;
while current != 1 {
// 使用位运算优化偶数检查
if current & 1 == 0 {
current >>= 1; // 右移一位相当于除以2
} else {
// 检查溢出
current = current.checked_mul(3)?.checked_add(1)?;
}
steps += 1;
}
Some(steps)
}
扩展功能
基于基础实现,我们可以添加更多功能:
pub struct CollatzCalculator {
memo: std::collections::HashMap<u64, u64>,
}
impl CollatzCalculator {
pub fn new() -> Self {
CollatzCalculator {
memo: std::collections::HashMap::new(),
}
}
pub fn steps(&mut self, n: u64) -> Option<u64> {
if n == 0 {
return None;
}
if let Some(&steps) = self.memo.get(&n) {
return Some(steps);
}
let steps = self.calculate_steps(n)?;
self.memo.insert(n, steps);
Some(steps)
}
fn calculate_steps(&mut self, n: u64) -> Option<u64> {
if n == 1 {
return Some(0);
}
let next = if n % 2 == 0 {
n / 2
} else {
n.checked_mul(3)?.checked_add(1)?
};
Some(1 + self.steps(next)?)
}
// 获取完整的序列
pub fn sequence(&self, n: u64) -> Option<Vec<u64>> {
if n == 0 {
return None;
}
let mut sequence = vec![n];
let mut current = n;
while current != 1 {
current = if current % 2 == 0 {
current / 2
} else {
current.checked_mul(3)?.checked_add(1)?
};
sequence.push(current);
}
Some(sequence)
}
// 查找指定范围内的最大步数
pub fn max_steps_in_range(&mut self, start: u64, end: u64) -> Option<(u64, u64)> {
if start == 0 || start > end {
return None;
}
let mut max_steps = 0;
let mut number_with_max_steps = start;
for i in start..=end {
if let Some(steps) = self.steps(i) {
if steps > max_steps {
max_steps = steps;
number_with_max_steps = i;
}
}
}
Some((number_with_max_steps, max_steps))
}
}
实际应用场景
考拉兹猜想在实际开发中有以下应用:
- 算法教学:作为递归和迭代算法的经典示例
- 性能测试:用于测试不同算法实现的性能
- 数学研究:在数论和动力系统研究中作为模型
- 编程竞赛:在编程竞赛中作为数学问题出现
- 随机数生成:某些伪随机数生成器基于类似原理
算法复杂度分析
-
时间复杂度:未知
- 考拉兹猜想本身尚未被证明,因此最坏情况下的时间复杂度未知
- 对于已知的输入,时间复杂度取决于到达 1 所需的步骤数
-
空间复杂度:
- 迭代版本:O(1)
- 递归版本:O(k),其中 k 是步骤数
- 记忆化版本:O(n),其中 n 是计算过的不同数字数量
与其他实现方式的比较
// 使用宏的实现
macro_rules! collatz_step {
($n:expr) => {
if $n % 2 == 0 {
$n / 2
} else {
3 * $n + 1
}
};
}
pub fn collatz(n: u64) -> Option<u64> {
if n == 0 {
return None;
}
let mut steps = 0;
let mut current = n;
while current != 1 {
current = collatz_step!(current);
steps += 1;
}
Some(steps)
}
// 函数式风格实现
pub fn collatz_functional(n: u64) -> Option<u64> {
if n == 0 {
return None;
}
let mut steps = 0;
let mut current = n;
std::iter::repeat(())
.take_while(|_| {
if current != 1 {
current = if current % 2 == 0 {
current / 2
} else {
current * 3 + 1
};
steps += 1;
true
} else {
false
}
})
.for_each(|_| {});
Some(steps)
}
总结
通过 collatz-conjecture 练习,我们学到了:
- 数学算法实现:掌握了考拉兹猜想的实现方法
- 迭代与递归:理解了两种不同算法设计思路的优缺点
- 错误处理:熟练使用 Option 类型处理无效输入
- 边界处理:学会了处理各种边界情况
- 性能优化:了解了记忆化和位运算等优化技巧
- 数值安全:理解了如何防止整数溢出
这些技能在实际开发中非常有用,特别是在实现数学算法、处理数值计算和进行算法优化时。考拉兹猜想虽然看起来简单,但它涉及到了算法设计、数值处理和错误处理等许多核心概念,是学习 Rust 算法实现的良好起点。
通过这个练习,我们也看到了 Rust 在数值处理和错误处理方面的强大能力,以及如何用安全且高效的方式实现数学算法。这种结合了安全性和性能的语言特性正是 Rust 的魅力所在。
Rust实现考拉兹猜想算法

被折叠的 条评论
为什么被折叠?



