Как реализовать двоичный поиск в Perl?
Я хотел бы реализовать алгоритм бинарного поиска в Perl. Мой "массив" сортируется в порядке убывания (не фактический массив, а функция, которая получает индекс и возвращает значения). проблема в том, что могут быть участки одинаковых значений. Если мое искомое значение находится в таком растяжении, я хочу вернуть первый индекс, который его содержит.
вот что я написал:
# get_val should be a *decreasing* function for idexes $i in min..max,
# formally: for any $i,$j s.t. $max>=$i>$j>=$min :
# $get_val_subref($i, $extra) <= $get_val_subref($j, $extra)
# min and max are the inclusive boundaries for the search
# get_val sub should get an index in min..max and an extra data reference, and return
# the value for the given index
# returns the smallest index $i in min..max for which $get_val_subref($j, $extra)
# returns $searched_val, or undef if no such index exists
sub binary_search {
my ( $min, $max, $searched_val, $get_val_subref, $get_val_sub_extra_data )
= @_;
my ( $mid, $val );
while ( $min <= $max ) {
$mid = $min + int( ( $max - $min ) / 2 );
$val = $get_val_subref->( $mid, $get_val_sub_extra_data );
if ( $val > $searched_val ) {
$min = $mid + 1;
}
elsif ( $val < $searched_val ) {
$max = $mid - 1;
}
else { ## SEE MY QUESTION BELOW ##
# surely $val == $searched_val, but is it the first one?
if ( $mid > $min
and $get_val_subref->( $mid - 1, $get_val_sub_extra_data )
== $searched_val )
{
# $val == $searched_val and prev($val) == $searched_val
# we have to continue
$max = $mid - 1;
}
else {
# $val == $searched_val and prev($val) != $searched_val
# wer'e done
return $mid;
}
}
}
# $val was not found. return undef
return undef;
}
и это простой пример для использования:
sub get_val_sub {
my ( $pos, $a ) = @_;
my $val = $a->[$pos];
return $val;
}
my @arr = (80, 40, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
say "RET:", binary_search( 0, $#arr, 0, &get_val_sub, @arr );
В проблема в том, что я не уверен, что мой последний (отмечен ## SEE MY QUESTION BELOW ##
) "милашки". Есть ли лучший способ сделать это?
3 ответов
хотя я изначально согласился с ответом Axeman ... это, в некотором роде, похоже на мой первый (действительно плохой) ответ в использовании линейной логики (по крайней мере, немного). В частности, нет причин звонить $get_val_subref
С $mid - 1
. Это ненужный шаг линейного поиска.
вот что я предлагаю. В дополнение к избежанию линейного поиска, он имеет преимущество быть чрезвычайно простым:
sub binary_search {
...
my ( $mid, $val, $solution );
while ( $min <= $max ) {
...
else {
$solution = $mid; # Store a possible solution.
$max = $mid - 1; # But continue with the binary search
# until $min and $max converge on each other.
}
}
return $solution;
}
хотя я сначала согласился с ответом FM, случай, который вы показываете (со всеми нулями), не является хорошим случаем для линейного обратного поиска. И хотя мне не понравилось, что вы просто продолжили бинарный поиск, " первый x" тут имеют вычислимое значение и будут еще имеют сублинейную производительность, в то время как линейный поиск назад имеет-конечно-a линейный один.
поэтому мне нравится ваша идея, но она более компактна, как это:
else {
return $mid unless
( $mid > $min
and $get_val_subref->( $mid - 1, $get_val_sub_extra_data )
== $searched_val
);
$max = $mid - 1;
}
линейный поиск назад и an легче вычисление, но поскольку функции значений становятся более сложными, чем меньше вычислений, тем лучше.