对单链接列表进行并行排序

伊恩·博伊德(Ian Boyd)

有什么算法值得对链表进行并行排序吗?

众所周知,合并排序是用于对链表进行排序的最佳算法

大多数合并排序是根据数组进行解释的,每个合并都将递归排序。这使得并行化变得微不足道:分别对每个半部分进行排序,然后将两个半部分合并。

但是,链表中没有“中途”点;链表直到结束:

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

我现在执行的实现一次遍历列表以获取计数,然后递归拆分计数,直到我们将节点与其进行比较NextNode递归会记住两个半部分在哪里。

这意味着链接列表的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算法手册

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

一个帕斯卡实现将是:

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)

奖励阅读

菲利普

Mergesort非常适合并行排序。将列表分为两半,并分别对它们进行排序,然后合并结果。如果您需要两个以上的并行排序过程,请递归执行。如果您碰巧没有无限多的CPU,则可以在一定的响应深度(必须通过测试确定)上省略并行化。

顺便说一句,将列表分成大小大致相同的两半的常用方法是弗洛伊德(Floyd)的循环查找算法,也称为野兔和乌龟方法:

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;
}

之后,listlist2是两个大小大致相同的列表。将它们串联将产生原始列表。当然,fast = fast->next->next还需要进一步关注;这仅是为了展示一般原理。

在此处输入图片说明

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章