我正在try 通过大约1.5n次运算来确定Rust中整数向量中的最大和第二大元素.(这是我正在读的一本书中的练习).

我的方法是迭代数组,直到n/2,以 Select 一对整数,然后比较这些整数以确定哪个更大.较大的数字被放置在新的数组L中,而较小的数字被添加到另一数组S.

逻辑是最大的数字应该在数组L中,而第二大数字应该在数组L或数组S中.这将使用不同的比较.

第一步应采用n/2运算,而其他步骤理想情况下应采用少于n个运算,约为1.5n个运算.

我已经用Rust编写了一个程序,该程序有时会输出正确的答案,但大多数情况下,它会输出错误的答案.

我try 对其进行调试,但发现了一些问题:

  • 当迭代到100时,当向量的大小为奇数时,程序无法访问向量中的最后一个元素.如果迭代到101,我会得到一个越界错误.

  • 代码无法确定最大数字和第二大数字是否具有相同的索引.(它们不应该是同一个指数,这更多的是因为我缺乏如何实现这一点的 idea ).

以下是我开发的程序:

pub fn max_pairwise_product_faster(a: &Vec<i64>) -> i64 {

    let n = (a.len()) / 2;
    let mut larger_numbers: Vec<i64> = Vec::new();
    let mut smaller_numbers: Vec<i64> = Vec::new();
    println!("n: {}", n);

    for i in 0..n {
        if a[2 * i] > a[2 * i + 1] {
            larger_numbers.push(a[2 * i]);
            smaller_numbers.push(a[2 * i + 1]);
        } else {
            larger_numbers.push(a[2 * i * 1]);
            smaller_numbers.push(a[2 * i]);
        }
    }

    println!(
        "larger_numbers: {:?}, smaller_numbers: {:?}",
        larger_numbers, smaller_numbers
    );

    let largest = larger_numbers.iter().max().unwrap();

    // Determine the second-largest number
    let mut second_largest = smaller_numbers.iter().max().unwrap();
    //Check if there is any number higher than this in the larger_number vector
    for number in &larger_numbers {
        if number != largest && number > second_largest {
            second_largest = number;
        }
    }

    println!(
        "largest_number: {}, second_largest_number: {}",
        largest, second_largest
    );

    largest * second_largest
}

以下是当前实现的样例输入和输出:

input array: [3, 5, 8, 20, 8, 7, 11, 20, 5, 16]
n: 5
larger_numbers: [3, 8, 8, 11, 5], smaller_numbers: [3, 8, 7, 11, 5]
largest_number: 11, second_largest_number: 11
Wrong answer -> naive: 400, fast: 121

在本例中,正确答案应该是400,但代码甚至没有正确获得最大数字.

我不想要这方面的确切代码,但演练我可以如何实现这一点.

推荐答案

算法rithm

我预计@Greybeard发表了关键的 comments in their answer:

无论如何,如果你将每个元素与目前的第二大元素进行比较,我预计与最大值的比较将变得越来越不可能.

原因是,如果输入是随机的,那么:

  • 上半年的最大值,可能是下半年最大的值之一.
  • 上半年的第二大价值也是如此.

在极端情况下,假设最大的两个值是您开始的前两个值:

  • 与最大的第一个和第二大的第二个进行比较,如果值低于第一个,则always将导致两次比较.
  • 与第二个最大的第一个比较,如果值大于第二个,则与第二个最大的第二个比较将导致单次比较.

当然,你不会马上知道是最大的还是第二大的.平均而言,您在查看50%的元素后就会知道它们,因此您将在前半部分对每个元素进行2次比较,在后半部分对每个元素进行1次比较,总共进行1.5N次比较.

Rusting it up

我们应该在表格上签个名:

fn max_pairwise_product(values: &[i64]) -> Option<i64>;

首先,如果values是空的,就不会有任何类型的产品.

其次,注意使用&[i64]作为参数,而不是&Vec<i64>:它更通用.你可以从Vec<T>,VecDeque<T>,[T; N]&[T]分...

Imperative

让我们首先进行必要的编码(Playground link):

fn max_pairwise_product(values: &[i64]) -> Option<i64> {
    //  `[T]::get` returns an `Option<&T>`.
    //  `Option<&T>::copied` returns an `Option<T>`, if `T` is `Copy.
    //  `?` short-circuits the operation when things don't work out.
    let first = values.get(0).copied()?;
    let second = values.get(1).copied()?;

    //  Can be expressed as `(first.max(second), first.min(second))` too.
    let (mut first, mut second) = if first > second {
        (first, second)
    } else {
        (second, first)
    };

    //  And from then, we scan the rest of the values using sub-slicing to
    //  express that we want to skip the first 2 values.
    //  The `&` in front of `v` is pattern-matching to make `v` a value,
    //  instead of a reference.
    for &v in &values[2..] {
        if v <= second {
            continue;
        }

        if v <= first {
            second = v;
            continue;
        }

        second = first;
        first = v;
    }

    Some(first * second)
}

Functional

然而,Ruust确实有更多的技巧,特别是模式匹配(更多)和用Iterator个方法替换循环可以产生相当不错的代码.

实际上,相同的算法可以表示为(Playground link):

fn max_pairwise_product(values: &[i64]) -> Option<i64> {
    //  The all-powerful `let .. else` form of pattern-matching, the "my way
    //  or the highway" style of matching for quickly getting out of here.
    let [first, second, tail @ ..] = values else { return None };

    let (first, second) = (first.max(second), first.min(second));

    let (first, second) = tail
        .iter()
        .copied()
        .fold((*first, *second), |(first, second), v| {
            if v <= second {
                (first, second)
            } else if v <= first {
                (first, v)
            } else {
                (v, first)
            }
        });

    Some(first * second)
}

这是使用切片上的模式匹配以可视的方式提取元素(尽管tail @ ..部分目前只在晚上),以及强大的fold算法.

Rust相关问答推荐

什么是谓词的简短和简洁类型

为什么我们不能通过指针算法将Rust原始指针指向任意地址?'

把Vector3变成Vector4的绝妙方法

什么是Rust惯用的方式来使特征向量具有单个向量项的别名?

如何模拟/创建ReqData以测试Actix Web请求处理程序?

为什么';t std::cell::ref使用引用而不是非空?

根据填充系数以相对大小在给定空间中布局项目

什么时候使用FuturesOrdered?

从管道读取后重置标准输入

借来的价值生命周期 不够长,不确定为什么它仍然是借来的

Rust 中的静态引用

仅在使用 &mut 或线程时borrow 的数据在闭包之外转义?

为什么我的trait 对象类型不匹配?

为什么带有生命周期指定的方法不能被调用两次?

全面的 Rust Ch.16.2 - 使用捕获和 const 表达式的 struct 模式匹配

为什么要这样编译?

`移动||异步移动{...}`,如何知道哪个移动正在移动哪个?

为什么 for_each 在释放模式(cargo run -r)下比 for 循环快得多?

使用 `clap` 在 Rust CLI 工具中设置布尔标志

试图理解 Rust 中的可变闭包