pub fn extremum<T: Ord>(
_: Range<usize>,
f: impl FnMut(usize) -> T
) -> (usize, T)
Expand description
三分探索で極値を探す。
離散値の区間 において、以下を満たす および を返す。
Requirements
凸である。すなわち、ある が存在して以下の二つが成り立つ。
- に対して 。
- に対して 。
Implementation notes
連続値の場合における黄金比分割のように、Fibonacci 数列に基づいて区間を分割していくため、関数値を使い回すことができる。
関数 の呼び出し回数を、区間を三等分する素直な実装と比較する。 三等分の実装では 回(係数の に注意)であり、こちらは 回である。 ただし は黄金比 である。 であり、 の底は大きい方がうれしいので、こちらの実装の方が定数倍が軽い。
コード長はやや長くなるものの、単純な例での実測では三等分のものよりわずかに高速であった。
Complexity
, , () で定義される数列 を考える。 区間幅 がある に対して と抑えられるとき、 の呼び出しを高々 回、関数値同士の比較を高々 回行う。 なお、この は で抑えられる。
Suggestions
引数は Range<usize>
を渡すことにしているものの、実際には
RangeBounds<I: {integer}>
を渡せるようにする方がよさそう?
ただし、両端とも Unbounded
であっては困りそう(特に多倍長を視野に入れる場合?)。
多倍長だと Copy
がないから、計算結果自体を使い回せても .clone()
でつらい?
Examples
use nekolib::algo::extremum;
let buf = [1, 3, 4, 6, 5, 2, 0, 1, 3];
// <------ f ------>
// <------ g ------>
let f = |i: usize| buf[i] * buf[i];
assert_eq!(extremum(0..6, f), (3_usize, 36));
let g = |i: usize| -buf[i];
assert_eq!(extremum(3..8, g), (6_usize, 0));
use nekolib::algo::extremum;
let n = 1500;
for k in 0..n {
let mut count = 0;
let f = |i| { count += 1; -(i as i32 - k as i32).abs() };
assert_eq!(extremum(0..n, f), (k, 0));
assert!(count <= 15);
}