6

それだけの価値があるリンクされたリストの並列ソートを行うアルゴリズムはありますか?

マージ ソートがリンク リストのソートに使用する最適なアルゴリズムであることはよく知られています。

ほとんどのマージ ソートは、各半分が再帰的にソートされる配列の観点から説明されています。これにより、並列化が簡単になります。各半分を個別にソートしてから、2 つの半分をマージします。

しかし、リンクされたリストには「中間」ポイントがありません。リンクされたリストは最後まで続く:

頭 → [a] → [b] → [c] → [d] → [e] → [f] → [g] → [h] → [i] → [j] → ...

私が今持っている実装は、リストを一度歩いてカウントを取得し、ノードをそれと比較するまでカウントを再帰的に分割しますNextNode。再帰は、2 つの半分がどこにあるかを記憶するように処理します。

これは、リンクされたリストの MergeSort がリストを直線的に進むことを意味します。リストを直線的に進める必要があるように見えるので、並列化できないと思います。私が想像できる唯一の方法は、次のとおりです。

  • リストをたどってカウントを取得する O(n)
  • 中間点に到達するためにリストの半分を歩くO(n/2)
  • 次に、各半分を並べ替えますO(n log n)

しかし、(a,b) と (c,d) の並べ替えを別のスレッドNextNodeで並列化したとしても、並べ替え中の誤った共有によって並列化の利点が失われると思います。

リンクされたリストをソートするための並列アルゴリズムはありますか?

配列マージソートアルゴリズム

配列に対してマージソートを実行するための標準アルゴリズムは次のとおりです。

algorithm Merge-Sort
    input:
        an array, A (the values to be sorted)
        an integer, p (the lower bound of the values to be sorted)
        an integer, r (the upper bound of the values to be sorted)

    define variables:
        an integer, q (the midpoint of the values to be sorted)

    q ← ⌊(p+r)/2⌋
    Merge-Sort(A, p, q)   //sort the lower half
    Merge-Sort(A, q+1, r) //sort the upper half   
    Merge(A, p, q, r)     

このアルゴリズムは、任意のインデックス アクセスを使用して、配列用に設計され、意図されています。リンクされたリストに適したものにするには、変更する必要があります。

リンク リスト マージ ソート アルゴリズム

これは、(シングルスレッド)単一リンクリスト、マージソート、単一リンクリストをソートするために現在使用しているアルゴリズムです。これは、Gonnet + Baeza Yates Handbook of Algorithmsから来ています。

algorithm sort:
    input:
        a reference to a list, r (pointer to the first item in the linked list)
        an integer, n (the number of items to be sorted)
    output:
        a reference to a list (pointer to the sorted list)

    define variables:
        a reference to a list, A (pointer to the sorted top half of the list)
        a reference to a list, B (pointer to the sorted bottom half of the list)
        a reference to a list, temp (temporary variable used to swap)

    if r = nil then
        return nil

    if n > 1 then
        A ← sort(r, ⌊n/2⌋ )
        B ← sort(r, ⌊(n+1)/2⌋ )
        return merge( A, B )

    temp ← r
    r ← r.next
    temp.next ← nil
    return temp

Pascalの実装は次のようになります。

function MergeSort(var r: list; n: integer): list;
begin
   if r = nil then 
       Result := nil
   else if n > 1 then
      Result := Merge(MergeSort(r, n div 2), MergeSort(r, (n+1) div 2) )
   else
   begin
      Result := r;
      r := r.next;
      Result.next := nil;
   end
end;

トランスコーディングが機能する場合は、オンザフライの C# 変換を次に示します。

list function MergeSort(ref list r, Int32 n)
{
   if (r == null)
      return null;

    if (n > 1)
    {
       list A = MergeSort(r, n / 2);
       list B = MergeSort(r, (n+1) / 2);
       return Merge(A, B);
    }
    else
    {
       list temp = r;
       r = r.next;
       temp.next = null;
       return temp;
    }
}

私が今必要としているのは、リンクされたリストをソートするための並列アルゴリズムです。マージソートである必要はありません。

n個のアイテムが単一のキャッシュラインに収まる次のn 個のアイテムをコピーし、それらを使用してタスクを生成することを提案する人もいます。

サンプルデータ

algorithm GenerateSampleData
    input:
        an integer, n (the number of items to generate in the linked list)
    output:
        a reference to a node (the head of the linked list of random data to be sorted)

    define variables:
        a reference to a node, head (the returned head)
        a reference to a node, item (an item in the linked list)
        an integer, i (a counter)

    head ← new node
    item ← head        

    for i ← 1 to n do
        item.value ← Random()
        item.next ← new node
        item ← item.next

    return head

したがって、次のように呼び出して、300,000 個のランダムなアイテムのリストを生成できます。

head := GenerateSampleData(300000);

ベンチマーク

Time to generate 300,000 items    568 ms

MergeSort 
    count splitting variation   3,888 ms (baseline)

MergeSort
    Slow-Fast midpoint finding  3,920 ms (0.8% slower)

QuickSort
    Copy linked list to array       4 ms
    Quicksort array             5,609 ms
    Relink list                     5 ms
    Total                       5,625 ms (44% slower)

ボーナスリーディング

4

5 に答える 5

5

Mergesort は並列ソートに最適です。リストを 2 つに分割し、それぞれを並行して並べ替えてから、結果をマージします。2 つ以上の並列並べ替えプロセスが必要な場合は、再帰的に行います。無限に多くの CPU を持っていない場合は、特定の反復深度 (テストによって決定する必要があります) で並列化を省略できます。

ところで、リストをほぼ同じサイズの 2 つの半分に分割する通常のアプローチは、ウサギとカメのアプローチとしても知られるフロイドのサイクル検索アルゴリズムです。

Node MergeSort(Node head)
{
   if ((head == null) || (head.Next == null))
      return head; //Oops, don't return null; what if only head.Next was null

   Node firstHalf = head;
   Node middle = GetMiddle(head);
   Node secondHalf = middle.Next;
   middle.Next = null; //cut the two halves

   //Sort the lower and upper halves
   firstHalf = MergeSort(firstHalf);
   secondHalf = MergeSort(secondHalf);

   //Merge the sorted halves 
   return Merge(firstHalf, secondHalf);
}

Node GetMiddle(Node head)
{
   if (head == null || head.Next == null)
       return null;

   Node slow = head;
   Node fast = head;
   while ((fast.Next != null) && (fast.Next.Next != null))
   {
       slow = slow.Next;
       fast = fast.Next.Next;
   }
   return slow;
}

その後、ほぼ同じサイズの 2 つのリストがありますlistlist2それらを連結すると、元のリストが生成されます。もちろん、fast = fast->next->nextさらに注意が必要です。これは、一般的な原則を示すためのものです。

ここに画像の説明を入力

于 2013-11-02T15:30:50.043 に答える
0

マージソートは分割統治アルゴリズムです。

配列は途中で分割するのが得意です。

連結リストは途中で分割すると効率が悪いので、リストを見ていくうちに分割していきましょう。

Take the first  element and put it in list 1.
Take the second element and put it in list 2.
Take the third  element and put it in list 1.
...

これで、リストが効率的に半分に分割されました。ボトムアップ マージ ソートを使用すると、最初のリストを奇数と偶数に分割しながら、マージ ステップを開始できます。

于 2013-11-02T06:16:47.613 に答える
0

2 つの方法でマージ ソートを実行できます。最初にリストを半分に分割し、両方の部分に再帰的にマージソートを適用して結果をマージします。しかし、別のアプローチがあります。リストをペアに分割し、結果である単一のリストが得られるまで、ペアのペアを再帰的にマージできます。ghc haskell での Data.List.sort の実装例を参照してください。このアルゴリズムは、開始時に適切な量のペアのプロセス (またはスレッド) を生成し、それらの結果を 1 つになるまでマージすることによって並列化できます。

于 2013-11-02T19:45:39.610 に答える
0

非効率的な解決策として、クイックソート アルゴリズムを使用します。リストの最初の要素がピボットとして使用され、ソートされていないリストが 3 つに分割されます (これには O(n) の時間がかかります)。次に、下位および上位のサブリストを別のスレッドで再帰的に並べ替えます。結果は、下部のサブリストをピボットに等しいキーのサブリストと連結し、次に上部のサブリストを O(1) の追加ステップで連結することによって取得されます (遅いマージの代わりに)。

于 2015-02-13T10:25:30.853 に答える