我经常使用 TList/TObjectList 和 TStringList(带有相关对象),用于各种任务,有时直接使用它们,有时作为更复杂结构的基础。虽然排序功能通常足够好,但我有时需要进行稳定排序,而这两个列表都使用快速排序。
有没有简单的方法实现 TList 和/或 TStringList 的稳定排序?我是否必须编写自己的排序程序,还是可以通过使用一些巧妙的技巧来完成 TStringListSortCompare/TListSortCompare?
我经常使用 TList/TObjectList 和 TStringList(带有相关对象),用于各种任务,有时直接使用它们,有时作为更复杂结构的基础。虽然排序功能通常足够好,但我有时需要进行稳定排序,而这两个列表都使用快速排序。
有没有简单的方法实现 TList 和/或 TStringList 的稳定排序?我是否必须编写自己的排序程序,还是可以通过使用一些巧妙的技巧来完成 TStringListSortCompare/TListSortCompare?
你需要编写自己的排序程序。
你可以阅读当前的快速排序实现,编写自己的稳定版本(例如合并排序或其他任何稳定变体)。
一些技巧:
Count
,可以使用快速指针数组(TList.List[]
),而不是较慢的Items[]
或GetItem()
:子方法调用和范围检查会减慢执行速度;这个问题很久远了,但我为未来读者提供我的答案:最近我也需要这个,并采用了在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
对于任何使用泛型的人,这里有一个现成的插入排序和归并排序实现,两种都是稳定的排序算法。
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;
从类似问题在Delphi中如何用稳定排序替换StringList.Sort?,由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);