.NET|.NET 排序 Array.Sort 实现示例
目录
- Array.Sort
- ArraySortHelper
- GenericArraySortHelper
- IntroSort
- InsertionSort
- 总结
先说结果, 实际上 Array.Sort 不止使用了一种排序算法, 为了保证不同的数据量的排序场景,都能有一个高性能的表现,实现中包括了插入排序,堆排序和快速排序, 接下来从通过源码看看它都做了哪些事情。
Array.Sort
【.NET|.NET 排序 Array.Sort 实现示例】https://source.dot.net/#System.Private.CoreLib/Array.cs,ec5718fae85b7640
public static void Sort(T[] array){if (array == null)ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); if (array.Length > 1){var span = new Span (ref MemoryMarshal.GetArrayDataReference(array), array.Length); ArraySortHelper .Default.Sort(span, null); }}
这里我们对 int 数组进行排序, 先看一下这个Sort方法, 当数组的长度大于1时, 会先把数组转成 Span 列表, 然后调用了内部的ArraySortHelper的Default对象的Sort方法。
ArraySortHelper
[TypeDependency("System.Collections.Generic.GenericArraySortHelper`1")]internal sealed partial class ArraySortHelper: IArraySortHelper {private static readonly IArraySortHelper s_defaultArraySortHelper = CreateArraySortHelper(); public static IArraySortHelper Default => s_defaultArraySortHelper; [DynamicDependency("#ctor", typeof(GenericArraySortHelper<>))]private static IArraySortHelper CreateArraySortHelper(){IArraySortHelper defaultArraySortHelper; if (typeof(IComparable ).IsAssignableFrom(typeof(T))){defaultArraySortHelper = (IArraySortHelper )RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(GenericArraySortHelper), (RuntimeType)typeof(T)); }else{defaultArraySortHelper = new ArraySortHelper (); }return defaultArraySortHelper; }}
Default 会根据是否实现了 IComparable
GenericArraySortHelper
https://source.dot.net/#System.Private.CoreLib/ArraySortHelper.cs,280
internal sealed partial class GenericArraySortHelperwhere T : IComparable {// Do not add a constructor to this class because ArraySortHelper .CreateSortHelper will not execute it#region IArraySortHelper Memberspublic void Sort(Span keys, IComparer ? comparer){try{if (comparer == null || comparer == Comparer .Default){if (keys.Length > 1){// For floating-point, do a pre-pass to move all NaNs to the beginning// so that we can do an optimized comparison as part of the actual sort// on the remainder of the values.if (typeof(T) == typeof(double) ||typeof(T) == typeof(float) ||typeof(T) == typeof(Half)){int nanLeft = SortUtils.MoveNansToFront(keys, default(Span )); if (nanLeft == keys.Length){return; }keys = keys.Slice(nanLeft); }IntroSort(keys, 2 * (BitOperations.Log2((uint)keys.Length) + 1)); }}else{ArraySortHelper .IntrospectiveSort(keys, comparer.Compare); }}catch (IndexOutOfRangeException){ThrowHelper.ThrowArgumentException_BadComparer(comparer); }catch (Exception e){ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_IComparerFailed, e); }}
首先会判断排序的类型是否是浮点型, 如果是的会做一些排序的调整优化,然后调用了 IntroSort 方法,并传入了两个参数,第一个Keys就是数组的Span列表,那第二个是什么呢? 它是一个int类型的depthLimit参数,这里简单点理解就是算出数组的深度,因为后边会根据这个值进行递归操作,然后进入到 IntroSort 方法。
IntroSort
到这个方法这里就清晰很多了, 这是Array.Sort
https://source.dot.net/#System.Private.CoreLib/ArraySortHelper.cs,404
private static void IntroSort(Spankeys, int depthLimit){Debug.Assert(!keys.IsEmpty); Debug.Assert(depthLimit >= 0); int partitionSize = keys.Length; while (partitionSize > 1){if (partitionSize <= Array.IntrosortSizeThreshold){if (partitionSize == 2){SwapIfGreater(ref keys[0], ref keys[1]); return; }if (partitionSize == 3){ref T hiRef = ref keys[2]; ref T him1Ref = ref keys[1]; ref T loRef = ref keys[0]; SwapIfGreater(ref loRef, ref him1Ref); SwapIfGreater(ref loRef, ref hiRef); SwapIfGreater(ref him1Ref, ref hiRef); return; }InsertionSort(keys.Slice(0, partitionSize)); return; }if (depthLimit == 0){HeapSort(keys.Slice(0, partitionSize)); return; }depthLimit--; int p = PickPivotAndPartition(keys.Slice(0, partitionSize)); // Note we've already partitioned around the pivot and do not have to move the pivot again.IntroSort(keys[(p+1)..partitionSize], depthLimit); partitionSize = p; }}
第一次进入方法时,partitionSize 就是数组的长度, 这里有一个判断条件,如下, IntrosortSizeThreshold 是一个值为16的常量,它是一个阈值, 如果数组的长度小于等于16, 那么使用的就是插入排序(InsertionSort), 为什么是16呢?这里通过注释了解到, 从经验上来看, 16及以下得数组长度使用插入排序的效率是比较高的。
if (partitionSize <= Array.IntrosortSizeThreshold){if (partitionSize == 2){SwapIfGreater(ref keys[0], ref keys[1]); return; }if (partitionSize == 3){ref T hiRef = ref keys[2]; ref T him1Ref = ref keys[1]; ref T loRef = ref keys[0]; SwapIfGreater(ref loRef, ref him1Ref); SwapIfGreater(ref loRef, ref hiRef); SwapIfGreater(ref him1Ref, ref hiRef); return; }InsertionSort(keys.Slice(0, partitionSize)); return; }
InsertionSort
如果数组的长度小于等于3时, 直接进行对比交换, 如果长度大约3并且小于等于16的话, 使用插入排序(InsertionSort), 方法内容如下:
https://source.dot.net/#System.Private.CoreLib/ArraySortHelper.cs,537
private static void InsertionSort(Spankeys){for (int i = 0; i < keys.Length - 1; i++){T t = Unsafe.Add(ref MemoryMarshal.GetReference(keys), i + 1); int j = i; while (j >= 0 && (t == null || LessThan(ref t, ref Unsafe.Add(ref MemoryMarshal.GetReference(keys), j)))){Unsafe.Add(ref MemoryMarshal.GetReference(keys), j + 1) = Unsafe.Add(ref MemoryMarshal.GetReference(keys), j); j--; }Unsafe.Add(ref MemoryMarshal.GetReference(keys), j + 1) = t!; }}HeapSortif (depthLimit == 0){HeapSort(keys.Slice(0, partitionSize)); return; }depthLimit--;
因为后边是递归操作,所以每次 depthLimit 都会减1, 当深度为0排序还没有完成的时候,就会直接使用堆排序(HeapSort),方法内容如下:
https://source.dot.net/#System.Private.CoreLib/ArraySortHelper.cs,990
private static void HeapSort(Spankeys, Span values){Debug.Assert(!keys.IsEmpty); int n = keys.Length; for (int i = n >> 1; i >= 1; i--){DownHeap(keys, values, i, n); }for (int i = n; i > 1; i--){Swap(keys, values, 0, i - 1); DownHeap(keys, values, 1, i - 1); }}private static void DownHeap(Span keys, Span values, int i, int n){TKey d = keys[i - 1]; TValue dValue = https://www.it610.com/article/values[i - 1]; while (i <= n>> 1){int child = 2 * i; if (child < n && (keys[child - 1] == null || LessThan(ref keys[child - 1], ref keys[child]))){child++; }if (keys[child - 1] == null || !LessThan(ref d, ref keys[child - 1]))break; keys[i - 1] = keys[child - 1]; values[i - 1] = values[child - 1]; i = child; }keys[i - 1] = d; values[i - 1] = dValue; }QuickSortint p = PickPivotAndPartition(keys.Slice(0, partitionSize), values.Slice(0, partitionSize)); IntroSort(keys[(p+1)..partitionSize], values[(p+1)..partitionSize], depthLimit); partitionSize = p;
这里调用了另外一个方法 PickPivotAndPartition,
Pivot 基准, Partition 分区, 这就是快速排序呀!而且还是使用了尾递归的快速排序,其中也使用了三数取中法,方法内容如下
https://source.dot.net/#System.Private.CoreLib/ArraySortHelper.cs,945
private static int PickPivotAndPartition(Spankeys, Span values){Debug.Assert(keys.Length >= Array.IntrosortSizeThreshold); int hi = keys.Length - 1; // Compute median-of-three.But also partition them, since we've done the comparison.int middle = hi >> 1; // Sort lo, mid and hi appropriately, then pick mid as the pivot.SwapIfGreaterWithValues(keys, values, 0, middle); // swap the low with the mid pointSwapIfGreaterWithValues(keys, values, 0, hi); // swap the low with the highSwapIfGreaterWithValues(keys, values, middle, hi); // swap the middle with the highTKey pivot = keys[middle]; Swap(keys, values, middle, hi - 1); int left = 0, right = hi - 1; // We already partitioned lo and hi and put the pivot in hi - 1.And we pre-increment & decrement below.while (left < right){if (pivot == null){while (left < (hi - 1) && keys[++left] == null) ; while (right > 0 && keys[--right] != null) ; }else{while (GreaterThan(ref pivot, ref keys[++left])) ; while (LessThan(ref pivot, ref keys[--right])) ; }if (left >= right)break; Swap(keys, values, left, right); }// Put pivot in the right location.if (left != hi - 1){Swap(keys, values, left, hi - 1); }return left; }
总结
本文主要介绍了System.Array.Sort
到此这篇关于.NET 排序 Array.Sort
推荐阅读
- 一个选择排序算法
- 排序(归并排序)
- 【图解】9张图彻底搞懂堆排序
- ASP.NET|ASP.NET Core应用开发思维导图
- java|微软认真聆听了开源 .NET 开发社区的炮轰( 通过CLI 支持 Hot Reload 功能)
- vue|vue 上移 下移 删除 排序
- 必须掌握的八种基本排序算法
- 【排序】插入排序算法
- 排序之冒泡和选择
- 2019-03-02|2019-03-02 排序