Лучший алгоритм для нахождения минимальной абсолютной разницы между двумя числами в массиве - программирование

Лучший алгоритм для нахождения минимальной абсолютной разницы между двумя числами в массиве

Существует массив, который может содержать, например, до 1000 элементы. Диапазон чисел, которые он может порождать, скажем 1 to 10^10. Теперь я должен найти minimal absolute difference между двумя числами в массиве. Я подумал о двух алгоритмах:

Для первого я определил функцию binarysearch, которая находит положение вставленного числа в отсортированном массиве. Теперь я запускаю отсортированный массив только с первым числом данного массива и начинаю итерирование по данному массиву со второго элемента вперед. Для каждого номера я нахожу его позицию в отсортированном массиве. Если число в этой позиции - это число, то разница равна 0, она является самой низкой возможной, поэтому я выхожу из цикла. Иначе я вставляю число в отсортированный массив в этой точке, затем проверяю разницу между этим числом и предыдущим и следующим числом в этом массиве. Затем я сохраняю минимум этого результата и предыдущего результата и продолжаю таким образом.

Во-вторых: я сортирую массив, используя quicksort. (Диапазон слишком велик, поэтому я думаю, что сортировка с помощью radix не будет такой эффективной). Затем я перебираю его, вырываясь с ответом 0, если два последовательных числа равны, иначе сохраняя минимум разницы между этим числом и предыдущим номером и предыдущим результатом.

Какой из них будет более эффективным?

Есть ли лучшее альго?

У Stackoverflow есть несколько сообщений в этом отношении, но они мало помогли. Здесь мой код в Perl:

sub position {
    my @list   = @{$_[0]};
    my $target = $_[1];

    my ($low,$high) = (0, (scalar @list)-1);

    while ($low <= $high) {
        $mid = int(($high + $low)/2);

        if ( $list[$mid] == $target ) {

            return $mid;
        }
        elsif ( $target < $list[$mid] ) {

            $high = $mid - 1; 
        }
        else {

            $low = $mid + 1;
        }
    }
    $low;
}
sub max { $_[0] > $_[1] ? $_[0] : $_[1]; }
sub min { $_[0] > $_[1] ? $_[1] : $_[0]; }

$ans        = 10_000_000_000;
@numbers    = (234, 56, 1, 34...123); #given array
($max,$min) = @num[0, 0];
@sorted     = ($numbers[0]);

for ( @num[1 .. $#num] ) {
    $pos = position(\@sorted, $_);

    if ( $sorted[$pos] == $_ ) { 

        $ans = 0;
        last;
    }
    splice @sorted, $pos, 0, $_;

    if ( $#sorted == $pos ) { 

        $ans = min($_-$sorted[-2], $ans);
    }
    elsif ( 0 == $pos ) {

        $ans = min($sorted[1]-$_, $ans);
    }
    else { 

        $ans = min(min(abs($sorted[$pos-1]-$_), abs($sorted[$pos+1]-$_)), $ans);
    }
    $max = max($_, $max);
    $min = min($_, $min);
}
print "$ans\n";
4b9b3361

Ответ 1

У вас есть до 5 тыс. элементов.

Обратите внимание, что песчаный мост имеет 32KB L1-Cache, предполагая 4 байта целое число - это означает, что он может содержать целые 8192.

Я бы старался избегать как можно большего количества дополнительных данных (кроме счетчиков и т.д.) и делать все на своем месте с использованием того же массива. Это сделает число cache-misses очень маленьким и, скорее всего, вытеснит любой алгоритм.

Таким образом, на месте quicksort и, чем итерация по элементам в массиве, вероятно, будет лучше, чем любое другое решение, как для обеспечения эффективности кэширования, при этом сохраняя приличную асимптотическую сложность O(nlogn).

Примечание. Хотя это решение, вероятно, будет более эффективным (по крайней мере теоретически), масштаб по-прежнему очень мал - и если вы не собираетесь делать это обложение много раз - это просто не стоит вашего времени, оптимизируя его.


Общий совет: когда речь идет о мелкомасштабных проблемах (и до 5000 элементов соответствует этим критериям), нотации большого О не хватает. Производительность кеша обычно является доминирующим фактором в этих проблемах.

Ответ 2

Это ближайшая проблема пары в одномерном виде. Обратите внимание, что решение этой проблемы, по крайней мере, столь же сложно, как решение проблемы уникальности , поскольку, если есть какие-то повторяющиеся элементы, тогда ответ равен 0.

Проблема уникальности элемента требует O(n lg n) времени для решения, поэтому эта проблема также должна быть, по крайней мере, такой сложной. Поскольку предложенное вами сортировочное итерационное решение O(n lg n), нет лучшего алгоритма асимптотического наихудшего случая.

Однако, как отмечается в статье wiki, существуют алгоритмы, которые имеют худшее время работы, но линейное ожидаемое время работы. Один из таких методов описан в в этой статье, это кажется довольно сложным!

Ответ 3

Второй будет быстрее по той простой причине, что с первым решением, которое вы используете, которое вы написали в Perl-пространстве, в то время как со вторым решением у вас есть возможность использовать встроенный Perl sort, который является C-функцией и очень быстрым. С таким небольшим вкладом для первого будет почти невозможно выиграть, хотя у него есть потенциал сделать меньше работы.

Ответ 4

Второй алгоритм, вероятно, лучше. В первом алгоритме вы используете сортировку вставки, которая менее эффективна, чем некоторые другие алгоритмы сортировки.

Ответ 5

Поскольку мы говорим о Perl, нам не стоит задаваться вопросом о самом эффективном алгоритме сортировки - реализация чего-либо в Perl должна быть медленнее, чем использование встроенных модулей. Просто для удовольствия я запустил этот маленький script (предназначенный для ясности):

time perl -e'
    @array = map {rand} 1..100000;
    $lastdiff=10**11;
    for(sort {$a <=> $b} @array){
        unless(defined $last){
            $last=$_;
            next
        }
        $difference = abs($last - $_);
        $last = $_;
        $lastdiff = $lastdiff < $difference ? $lastdiff : $difference;
        last if $lastdiff == 0;
    }
    print $lastdiff, "\n"
'

Я создал массив с 100 000 случайных чисел. Этот script заканчивается (на моем медленном ноутбуке) через 0,42 секунды. Учитывая, что для запуска и инициализации массива используется ~ 0,12 секунды, основной алгоритм использует около 0,3 секунды. Предполагая, что O (n) вы должны закончить в < 0.02 сек... о, подождите, что не много... (с 5000 элем.)

Если вам это нужно быстрее, напишите свой алгоритм с помощью Inline:: C.

Ответ 6

Простой рандомизированный ситовый алгоритм для проблемы с ближайшей парой описывает рандомизированный алгоритм O (n) для ближайшей парной проблемы, а также ссылки другой документ, который дает детерминированный алгоритм O (n log log n) для одномерной проблемы ближайшей пары, если у вас есть доступ к функции floor.

Ответ 7

  • копировать числа массива в черно-красное дерево
  • это позволит вам проверить, есть ли число в определенном интервале для log (n)
  • set d = inf
  • цикл над массивом с переменной i
  • если в интервале (i-d, я + d) есть число, то установите d равным | i-thatNumber |

вам понадобится ~ n * ln, чтобы найти d