10

double の配列から n 最小 (0 ではない) を見つける必要があります (配列samplesと呼びましょう)。これをループで何度も行う必要があるため、実行速度が重要です。最初に配列をソートしてから、最初の 10 個の値 (0 ではない) を取得しようとしましたが、Array.Sort は高速であると言われていますが、ボトルネックになりました。

const int numLowestSamples = 10;

double[] samples;

double[] lowestSamples = new double[numLowestSamples];

for (int count = 0; count < iterations; count++) // iterations typically around 2600000
{
    samples = whatever;
    Array.Sort(samples);
    lowestSamples = samples.SkipWhile(x => x == 0).Take(numLowestSamples).ToArray();
}

したがって、最初の n 値を最初に読み取り、それらを並べ替え、次にサンプル内の他のすべての値をループして、並べ替えられたlowSamples配列の最後の値よりも値が小さいかどうかを確認するという、別の、しかしクリーンではない解決策を試しました。値が小さい場合は、配列内の値に置き換えて、配列を再度並べ替えます。これは約 5 倍高速であることが判明しました。

const int numLowestSamples = 10;

double[] samples;

List<double> lowestSamples = new List<double>();

for (int count = 0; count < iterations; count++) // iterations typically around 2600000
{
    samples = whatever;

    lowestSamples.Clear();

    // Read first n values
    int i = 0;
    do
    {
        if (samples[i] > 0)
            lowestSamples.Add(samples[i]);

        i++;
    } while (lowestSamples.Count < numLowestSamples)

    // Sort the array
    lowestSamples.Sort();

    for (int j = numLowestSamples; j < samples.Count; j++) // samples.Count is typically 3600
    {
        // if value is larger than 0, but lower than last/highest value in lowestSamples
        // write value to array (replacing the last/highest value), then sort array so
        // last value in array still is the highest
        if (samples[j] > 0 && samples[j] < lowestSamples[numLowestSamples - 1])
        {
            lowestSamples[numLowestSamples - 1] = samples[j];
            lowestSamples.Sort();
        }
    }
}

これは比較的高速に機能しますが、より高速で優れたソリューションを考え出すように誰かに挑戦したかったのです。

4

6 に答える 6

3

これを選択アルゴリズムと呼びます。

この Wiki ページには、いくつかの一般的な解決策があります。

http://en.wikipedia.org/wiki/Selection_algorithm#Selecting_k_smallest_or_largest_elements

(ただし、C# に変換するには少し作業が必要です)

QuickSelect アルゴリズムを使用して n 番目に低い要素を見つけ、配列を反復処理して各要素 <= その要素を取得することができます。

C# での QuickSelect の例を次に示します: http://dpatrickcaldwell.blogspot.co.uk/2009/03/more-ilist-extension-methods.html

于 2012-06-26T14:50:02.317 に答える
2

lowSamples を繰り返し並べ替える代わりに、サンプルを配置する場所に挿入するだけです。

int samplesCount = samples.Count;

for (int j = numLowestSamples; j < samplesCount; j++)
{
    double sample = samples[j];

    if (sample > 0 && sample < currentMax)
    {
        int k;

        for (k = 0; k < numLowestSamples; k++)
        {
           if (sample < lowestSamples[k])
           {
              Array.Copy(lowestSamples, k, lowestSamples, k + 1, numLowestSamples - k - 1);
              lowestSamples[k] = sample;

              break;
           }
        }

        if (k == numLowestSamples)
        {
           lowestSamples[numLowestSamples - 1] = sample;
        }

        currentMax = lowestSamples[numLowestSamples - 1];
    }
}

ここで、numLowestSamples を非常に大きくする必要がある場合 (samples.count のサイズに近づく)、より高速なプライオリティ キューを使用することができます (通常、新しいサンプルを挿入するために、O(n/ 2) ここで、n は numLowestSamples です)。プライオリティ キューは、新しい値を効率的に挿入し、O(logn) 時間で最大値を打ち消すことができます。

numLowestSamples が 10 の場合、実際には必要ありません。特に、複雑なデータ構造ではなく double のみを扱っているためです。ヒープと小さな numLowestSamples を使用すると、ヒープ ノード (ほとんどの優先キューはヒープを使用します) にメモリを割り当てるオーバーヘッドは、おそらく検索/挿入の効率向上よりも大きくなります (テストは重要です)。

于 2012-06-26T15:17:49.223 に答える
2

理想的には、コレクションに対して 1 つのパスのみを作成する必要があるため、ソリューションは非常に洗練されています。ただし、その前に数字を昇格させるだけでよい場合は、挿入ごとにサブリスト全体を再利用しています。ただし、10 個の要素をソートすることはほとんど無視でき、これを強化してもあまり効果がありません。ソリューションの最悪のシナリオ (無駄なパフォーマンスの観点から) は、最初から 9 つの最小の数字がある場合です。したがって、後続の各数字が <lowestSamples[numLowestSamples - 1]であることがわかり、既にソートされたリストをソートすることになります (これは最悪のケースです)クイックソートのシナリオ)。

要するに、使用する数値が非常に少ないため、これを行うためにマネージ言語を使用するオーバーヘッドを考えると、数学的な改善はあまりありません。

クールなアルゴリズムに称賛を!

于 2012-06-26T14:49:50.240 に答える
2

あなたの考えは正しいと思います。つまり、1 つのパス スルーと最小サイズの並べ替えられたデータ構造体の維持は、一般的に最速です。これに対するパフォーマンスの改善は最適化です。

最適化は次のようになります。1) パススルーごとに結果をソートしています。これは小さなサイズでは最速かもしれませんが、大きなセットでは最速ではありません。2 つのアルゴリズムを考えてみましょう。1 つは特定のしきい値を下回る場合、もう 1 つは (ヒープ ソートのように) しきい値を上回る場合です。2)最小セットから削除する必要がある値を追跡します(現在、最後の要素を見て行っています)。追い出される値以上の値の挿入と並べ替えをスキップできます。

于 2012-06-26T15:51:12.010 に答える
2

2 つの異なるアイデア:

  1. 配列をソートする代わりに、単一のInsertion Sortパスを実行するだけです。新しく追加された項目だけが順序付けされていないことは既にわかっているので、その知識を使用してください。
  2. Heap Sortを見てください。バイナリの最大ヒープを構築し (最小から最大に並べ替える場合)、インデックス 0 の最大要素を、まだヒープの一部である最後の要素と交換することにより、ヒープからの要素の削除を開始します。ここで、配列を最大要素から最小要素の順に並べ替えるふりをした場合、10 個の要素を並べ替えた後で並べ替えを停止できます。配列の最後の 10 個の要素が最小になり、残りの配列は配列表現のバイナリ ヒープのままです。それがウィキペディアのクイックソートベースの選択アルゴリズムとどのように比較されるかはわかりません。選択する要素の数に関係なく、ヒープの構築は常に配列全体に対して行われます。
于 2012-06-26T15:33:53.800 に答える
1

最小ヒープを維持して、パフォーマンスの違いを測定したいと思うかもしれません。これは、私が取り組んできたフィボナッチ ヒープと呼ばれるデータ構造です。少し手間がかかるかもしれませんが、少なくとも私の仮説をテストすることはできます。

public sealed class FibonacciHeap<TKey, TValue>
{
    readonly List<Node> _root = new List<Node>();
    int _count;
    Node _min;

    public void Push(TKey key, TValue value)
    {
        Insert(new Node {
            Key = key,
            Value = value
        });
    }       

    public KeyValuePair<TKey, TValue> Peek()
    {
        if (_min == null)
            throw new InvalidOperationException();
        return new KeyValuePair<TKey,TValue>(_min.Key, _min.Value);
    }       

    public KeyValuePair<TKey, TValue> Pop()
    {
        if (_min == null)
            throw new InvalidOperationException();
        var min = ExtractMin();
        return new KeyValuePair<TKey,TValue>(min.Key, min.Value);
    }

    void Insert(Node node)
    {
        _count++;
        _root.Add(node);
        if (_min == null)
        {
            _min = node;
        }
        else if (Comparer<TKey>.Default.Compare(node.Key, _min.Key) < 0)
        {
            _min = node;
        }
    }

    Node ExtractMin()
    {
        var result = _min;
        if (result == null)
            return null;
        foreach (var child in result.Children)
        {
            child.Parent = null;
            _root.Add(child);
        }
        _root.Remove(result);
        if (_root.Count == 0)
        {
            _min = null;
        }
        else
        {
            _min = _root[0];
            Consolidate();
        }
        _count--;
        return result;
    }

    void Consolidate()
    {
        var a = new Node[UpperBound()];
        for (int i = 0; i < _root.Count; i++)
        {
            var x = _root[i];
            var d = x.Children.Count;
            while (true)
            {   
                var y = a[d];
                if (y == null)
                    break;                  
                if (Comparer<TKey>.Default.Compare(x.Key, y.Key) > 0)
                {
                    var t = x;
                    x = y;
                    y = t;
                }
                _root.Remove(y);
                i--;
                x.AddChild(y);
                y.Mark = false;
                a[d] = null;
                d++;
            }
            a[d] = x;
        }
        _min = null;
        for (int i = 0; i < a.Length; i++)
        {
            var n = a[i];
            if (n == null)
                continue;
            if (_min == null)
            {
                _root.Clear();
                _min = n;
            }
            else
            {
                if (Comparer<TKey>.Default.Compare(n.Key, _min.Key) < 0)
                {
                    _min = n;
                }
            }
            _root.Add(n);
        }
    }

    int UpperBound()
    {
        return (int)Math.Floor(Math.Log(_count, (1.0 + Math.Sqrt(5)) / 2.0)) + 1;
    }

    class Node
    {
        public TKey Key;
        public TValue Value;
        public Node Parent;
        public List<Node> Children = new List<Node>();
        public bool Mark;

        public void AddChild(Node child)
        {
            child.Parent = this;
            Children.Add(child);
        }

        public override string ToString()
        {
            return string.Format("({0},{1})", Key, Value);
        }
    }
}
于 2012-06-26T14:47:17.510 に答える