【问题标题】:Easy way to add stable sorting to TList and TStringList向 TList 和 TStringList 添加稳定排序的简单方法
【发布时间】:2011-10-26 18:32:27
【问题描述】:

我使用 TList/TObjectList 和 TStringList(与关联的对象)来完成大量任务,无论是按原样还是作为更复杂结构的基础。虽然排序功能通常足够好,但有时我需要进行stable 排序,并且两个列表都使用快速排序。

为 TList 和/或 TStringList 实现稳定排序的最简单方法是什么?我必须编写自己的排序例程,还是可以通过 TStringListSortCompare/TListSortCompare 使用一些巧妙的技巧来完成?

【问题讨论】:

标签: delphi list sorting stable-sort


【解决方案1】:

您必须编写自己的排序例程。

您可以阅读当前的快速排序实现,并编写自己的稳定版本(例如合并排序或any other stable variant)。

一些技巧:

  • 如果您确定索引为 Count,则可以使用快速指针数组 (TList.List[]) 而不是较慢的 Items[]GetItem():子方法调用和范围检查会减慢执行速度;
  • 比较功能大部分时间是搜索的速度瓶颈 - 所以要注意这部分;
  • 如果您需要速度,请在真实(例如随机)数据上使用真实的分析器 - 但在加快速度之前先做好准备;
  • 从现有的排序实现开始;
  • 为了最大限度地减少堆栈空间,您可以使用临时记录来存储和在递归调用之间共享变量。

【讨论】:

  • 我以为我必须自己写,但不得不问。我希望有一些“神奇”的把戏 :-) 想想看,Borland/Embarcadero 没有让 TList.Sort 稳定不是很奇怪吗?我不是专家,但我相信有一些排序算法具有与快速排序相同的性能,并且内存使用量仍然相当适中。啊,好吧。
  • 就所有书籍而言,QuickSort 是一种众所周知的排序算法,同时速度快、易于编码和调试,并且内存使用率低。稳定的变体(如合并或插入排序)更复杂,并且不能提供更好的性能。 VCL 中的排序不需要稳定,因此 QuickSort 是通用排序实现的最佳候选者。
【解决方案2】:

这个问题已经很老了,但这里是我给未来读者的答案: 我最近也需要这个,并改编了 Julian Bucknall 的“The Tomes of Delphi Algorithms and Data Structures”一书中的实现。 TList、TObjectList 和后代类的实现。它适用于 Delphi 2009 到 XE7 以及可能的其他版本: http://alexandrecmachado.blogspot.com.br/2015/02/merge-sort-for-delphi.html

【讨论】:

  • 不错!谢啦。但是我注意到没有单元测试。你对代码的可靠性有多大信心?你会在生产代码中使用它吗?
  • 我写了一些单元测试,不幸的是排序似乎并不完全稳定:(我会将测试发布到你的博客。
  • 原代码有bug。感谢您找到并告知。代码是固定的,是的,我将在生产中使用它。以下是有关问题和修复的信息:alexandrecmachado.blogspot.com.br/2015/03/…
  • 酷。感谢您修复它。
  • 不鼓励仅链接答案。
【解决方案3】:

来自类似的问题How Can I Replace StringList.Sort with a Stable Sort in Delphi?,在 lkessler 的评论中链接到这里,我需要复制到这里非常简单的技巧,正如问题中提到的那样。

您只需将初始订单号添加到要排序的数据中并在CustomSort比较函数中添加最后一个比较条件来比较此初始订单号,即可轻松使快速排序行为稳定。

简单、快速、智能。在每个可排序项上只花费一个额外的整数(或字节,或者使用一些保留的存储空间,如 TComponent.Tag,如果您对 TComponents 进行排序),并在它们上进行一个初始化循环。

TObjectToSort = class
  ...
  Index: Integer;
end;

function MyStableSortComparer(List: TStringList; Index1, Index2: Integer): Integer;
var
  o1, o2: TObjectToSort; 
begin
  o1 := TObjectToSort(List.Objects[Index1]);
  o2 := TObjectToSort(List.Objects[Index2]);
  ...
  if Result = 0 then
    Result := o1.Index - o2.Index;
end;


for i := 0 to MyStrtingList.Count - 1 do
  TObjectToSort(MyStrtingList.Objects[i]).Index := i;
MyStrtingList.CustomSort(MyStableSortComparer);

【讨论】:

    【解决方案4】:

    对于任何使用泛型的人来说,这里是插入和合并排序的即用型实现,这两种算法都是稳定的排序算法。

    uses Generics.Defaults, Generics.Collections;
    
    type
      TMySort = class
      public
        class procedure InsertionSort<T>(AArray: TArray<T>; FirstIndex, LastIndex: Integer; const AComparer: IComparer<T>); static;
        class procedure MergeSort<T>(AArray: TArray<T>; FirstIndex, LastIndex: Integer; const AComparer: IComparer<T>); static;
      end;
    
    implementation
    
    class procedure TMySort.InsertionSort<T>(AArray: TArray<T>; FirstIndex, LastIndex: Integer; const AComparer: IComparer<T>);
    var
      UnsortedIdx, CompareIdx: Integer;
      AItem: T;
    begin
      for UnsortedIdx := Succ(FirstIndex) to LastIndex do begin
        AItem := AArray[UnsortedIdx];
        CompareIdx := UnsortedIdx - 1;
        while (CompareIdx >= FirstIndex) and (AComparer.Compare(AItem, AArray[CompareIdx]) < 0) do begin
          AArray[CompareIdx + 1] := AArray[CompareIdx]; { shift the compared (bigger) item to the right }
          Dec(CompareIdx);
        end;
        AArray[CompareIdx + 1] := AItem;
      end;
    end;
    
    class procedure TMySort.MergeSort<T>(AArray: TArray<T>; FirstIndex, LastIndex: Integer; const AComparer: IComparer<T>);
    const
      MinMergeSortLimit = 16;
    var
      LeftLast, RightFirst: Integer;
      LeftIdx, RightIdx, SortedIdx: Integer;
      LeftCount: Integer;
      TmpLeftArray: TArray<T>;
    begin
      if (LastIndex - FirstIndex) < MinMergeSortLimit then
        { sort small chunks with insertion sort (recursion ends here)}
        TMySort.InsertionSort<T>(AArray, FirstIndex, LastIndex, AComparer)
      else begin
        { MERGE SORT }
        { calculate the index for splitting the array in left and right halves }
        LeftLast := (FirstIndex + LastIndex) div 2;
        RightFirst := LeftLast + 1;
        { sort both halves of the array recursively }
        TMySort.MergeSort<T>(AArray, FirstIndex, LeftLast, AComparer);
        TMySort.MergeSort<T>(AArray, RightFirst, LastIndex, AComparer);
        { copy the first half of the array to a temporary array }
        LeftCount := LeftLast - FirstIndex + 1;
        TmpLeftArray := System.Copy(AArray, FirstIndex, LeftCount);
        { setup the loop variables }
        LeftIdx := 0;  { left array to merge -> moved to TmpLeftArray, starts at index 0 }
        RightIdx := RightFirst; { right array to merge -> second half of AArray }
        SortedIdx := FirstIndex - 1; { range of merged items }
        { merge item by item until one of the arrays is empty }
        while (LeftIdx < LeftCount) and (RightIdx <= LastIndex) do begin
          { get the smaller item from the next items in both arrays and move it
            each step will increase the sorted range by 1 and decrease the items still to merge by 1}
          Inc(SortedIdx);
          if AComparer.Compare(TmpLeftArray[LeftIdx], AArray[RightIdx]) <= 0 then begin
            AArray[SortedIdx] := TmpLeftArray[LeftIdx];
            Inc(LeftIdx);
          end else begin
            AArray[SortedIdx] := AArray[RightIdx];
            Inc(RightIdx);
          end;
        end;
        { copy the rest of the left array, if there is any}
        while (LeftIdx < LeftCount) do begin
          Inc(SortedIdx);
          AArray[SortedIdx] := TmpLeftArray[LeftIdx];
          Inc(LeftIdx);
        end;
        { any rest of the right array is already in place }
      end;
    end;
    

    该实现是针对数组进行的,也适用于 TList/TObjectList(因为它们的 Items 属性是一个数组)。

    var
      AList: TList<T>;
      AComparer: IComparer<T>;
    begin
      ...
      TMySort.MergeSort<T>(AList.List, 0, AList.Count-1, AComparer);
      ...
    end;
    

    除了稳定之外,根据我的经验,这种合并排序实现确实显示出比内置快速排序更好的性能(尽管它使用更多内存)。

    【讨论】:

      猜你喜欢
      • 2010-09-21
      • 2015-11-10
      • 1970-01-01
      • 2019-03-13
      • 1970-01-01
      • 1970-01-01
      • 2010-09-08
      • 1970-01-01
      • 2010-09-21
      相关资源
      最近更新 更多