16

1000たとえば、最大要素を含むことができる配列があります。それが生み出すことができる数の範囲は言う1 to 10^10です。minimal absolute difference次に、配列内の2つの数値の間を見つける必要があります。私は2つのアルゴリズムを考えました:

最初のものについてはbinarysearch、ソートされた配列内の挿入される数値の位置を見つける関数を定義しました。次に、指定された配列の最初の番号のみでソートされた配列を開始し、2番目の要素以降で指定された配列の反復を開始します。番号ごとに、並べ替えられた配列でその位置を見つけます。その位置の数値がこの数値である場合、差は0であり、可能な限り最小の数値であるため、ループを終了します。それ以外の場合は、その時点で並べ替えられた配列に番号を挿入し、その番号とその配列の前後の番号の違いを確認します。次に、この結果と前の結果の最小値を保存し、この方法で続行します。

2番目:クイックソートを使用して配列をソートします。(範囲が広すぎるので、基数ソートはそれほど効率的ではないと思います)。次に、それを繰り返し、2つの連続する数値が等しい場合は、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";
4

7 に答える 7

16

最大5kの要素があります。

SandyBridgeプロセッサには32KBのL1-Cacheがあり、4バイトの整数を想定していることに注意してください。これは、8192個の整数を含めることができることを意味します。

追加のデータ(カウンターなどを除く)をできるだけ作成しないようにし、同じ配列を使用してすべてを適切に実行します。これにより、キャッシュミスの数が非常に少なくなり、おそらくどのアルゴリズムよりも優れたパフォーマンスを発揮します。

したがって、インプレースクイックソートと配列内の要素を反復処理するよりも、キャッシュ効率が高く、無症候性の複雑さを維持するために、他のソリューションよりも優れている可能性がありO(nlogn)ます。

注-このソリューションはおそらく(少なくとも理論的には)より効率的ですが、規模はまだ非常に小さいです-そして、この操作を何度も行う予定がない限り、それを過度に最適化する価値はありません。


一般的なヒント:小規模な問題(および最大5000要素がこの基準に適合する)について話す場合、通常、big-O表記では不十分です。キャッシュのパフォーマンスは通常、これらの問題の主要な要因です。

于 2012-09-02T06:55:13.363 に答える
11

これは、1次元で最も近いペアの問題です。この問題を解決することは、要素の一意性の問題を解決することと少なくとも同じくらい難しいことに注意してください。重複する要素がある場合、答えは0になるからです。

要素の一意性の問題はO(n lg n)解決するのに時間がかかるため、この問題も少なくともそれほど難しいものでなければなりません。あなたが提案した反復ソートの解決策はO(n lg n)であるため、これ以上の漸近的な最悪の場合のアルゴリズムは利用できません。

ただし、wikiの記事に記載されているように、最悪の場合の実行時間が悪化するアルゴリズムがありますが、予想される実行時間は線形です。そのような方法の1つがこの記事で説明されていますが、かなり複雑に思えます。

于 2012-09-02T07:17:01.083 に答える
5

2番目のソリューションは、Perlスペースで自分で作成したソートを使用しているという非常に単純な理由で高速になりますが、2番目のソリューションでは、組み込みのPerlを使用する機会がありますsort。 C機能と非常に高速です。このような小さな入力では、最初の入力が少ない場合でも、最初の入力が勝つことはほぼ不可能です。

于 2012-09-02T07:11:26.907 に答える
2

2番目のアルゴリズムの方がおそらく優れています。最初のアルゴリズムでは、挿入ソートを使用していますが、これは他のいくつかのソートアルゴリズムよりも効率が低くなります。

于 2012-09-02T07:03:56.210 に答える
1

私たちはPerlについて話しているので、最も効率的なソートアルゴリズムについてあまり不思議に思うべきではありません。Perlで何かを自分で実装することは、組み込みを使用するよりも遅くなるはずです。楽しみのために、私はこの小さなスクリプトを実行しました(わかりやすくすることを目的としています)。

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個の乱数を使用して配列を設定しました。このスクリプトは(私の遅いラップトップでは)0.42秒以内に終了します。起動とアレイの初期化に約0.12秒を使用することを考えると、メインアルゴリズムは約0.3秒を使用します。O(n)を<0.02秒で終了する必要があると仮定すると…ああ、待ってください、それはそれほど多くありません…(5000要素で)

より速く必要な場合は、Inline::Cを使用してアルゴリズムを記述します。

于 2012-09-02T07:13:57.090 に答える
1

最も近いペアの問題の単純なランダム化されたふるいアルゴリズムは、最も近いペアの問題のO(n)ランダム化アルゴリズムを説明し、1次元の最も近いペアのO(n log log n)決定論的アルゴリズムを与える別の論文も参照します。関数にアクセスできる場合は問題が発生しfloorます。

于 2012-09-02T15:47:24.460 に答える
0
  1. 配列の番号を黒赤木にコピーします
  2. これにより、log(n)の特定の間隔に数値があるかどうかをテストできます。
  3. d=infを設定します
  4. 変数iの配列をループします
  5. 間隔(id、i + d)に数値がある場合は、dを|i-thatNumber|に等しく設定します。

dを見つけるには〜n*lnかかります

于 2012-09-02T06:43:39.487 に答える