System.Array.Sort<T> 是.NET内置的排序方法, 灵活且高效, 大家都学过一些排序算法,比如冒泡排序,插入排序,堆排序等,不过你知道这个方法背后使用了什么排序算法吗?

先说结果, 实际上 Array.Sort 不止使用了一种排序算法, 为了保证不同的数据量的排序场景,都能有一个高性能的表现,实现中包括了插入排序,堆排序和快速排序, 接下来从通过源码看看它都做了哪些事情。

Array.Sort

https://source.dot.net/#System.Private.CoreLib/Array.cs,ec5718fae85b7640

public static void Sort<T>(T[] array)
{if (array == null)ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);if (array.Length > 1){var span = new Span<T>(ref MemoryMarshal.GetArrayDataReference(array), array.Length);ArraySortHelper<T>.Default.Sort(span, null);}
}

这里我们对 int 数组进行排序, 先看一下这个Sort方法, 当数组的长度大于1时, 会先把数组转成 Span 列表, 然后调用了内部的ArraySortHelper的Default对象的Sort方法。

ArraySortHelper

[TypeDependency("System.Collections.Generic.GenericArraySortHelper`1")]
internal sealed partial class ArraySortHelper<T>: IArraySortHelper<T>
{private static readonly IArraySortHelper<T> s_defaultArraySortHelper = CreateArraySortHelper();public static IArraySortHelper<T> Default => s_defaultArraySortHelper;[DynamicDependency("#ctor", typeof(GenericArraySortHelper<>))]private static IArraySortHelper<T> CreateArraySortHelper(){IArraySortHelper<T> defaultArraySortHelper;if (typeof(IComparable<T>).IsAssignableFrom(typeof(T))){defaultArraySortHelper = (IArraySortHelper<T>)RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(GenericArraySortHelper<string>), (RuntimeType)typeof(T));}else{defaultArraySortHelper = new ArraySortHelper<T>();}return defaultArraySortHelper;}
}

Default 会根据是否实现了 IComparable<T> 接口来创建不同的 ArraySortHelper, 因为上面我对int数组进行排序, 所以调用的是 GenericArraySortHelper 的Sort方法。

GenericArraySortHelper

https://source.dot.net/#System.Private.CoreLib/ArraySortHelper.cs,280

internal sealed partial class GenericArraySortHelper<T>where T : IComparable<T>{// Do not add a constructor to this class because ArraySortHelper<T>.CreateSortHelper will not execute it#region IArraySortHelper<T> Memberspublic void Sort(Span<T> keys, IComparer<T>? comparer){try{if (comparer == null || comparer == Comparer<T>.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<byte>));if (nanLeft == keys.Length){return;}keys = keys.Slice(nanLeft);}IntroSort(keys, 2 * (BitOperations.Log2((uint)keys.Length) + 1));}}else{ArraySortHelper<T>.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<T> 排序的主要内容,接着往下看

https://source.dot.net/#System.Private.CoreLib/ArraySortHelper.cs,404

private static void IntroSort(Span<T> keys, 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(Span<T> keys)
{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!;}
}

HeapSort

if (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(Span<TKey> keys, Span<TValue> 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<TKey> keys, Span<TValue> values, int i, int n)
{TKey d = keys[i - 1];TValue dValue = 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;
}

QuickSort

int 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(Span<TKey> keys, Span<TValue> 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<T> 排序的内部实现, 发现它使用了插入排序,堆排序和快速排序,大家有兴趣可以看一下Java或者Golang的排序实现,希望对您有用。

.NET 排序 Array.SortT 实现分析相关推荐

  1. 简单排序算法时间空间复杂度分析及应用(4)-二分插入排序

    简单排序算法时间空间复杂度分析及应用(4)-二分插入排序 背景: 顾名思义,这个二分插入排序是直接插入排序的进化版,主要变化的地方就是在内循环部分,即外循环的循环节点在确定区域的位置查询方式由原来的直 ...

  2. Python 搜索、排序、复杂度分析

    Python 搜索.排序.复杂度分析 算法是计算机程序的一个基本的构建模块.评价算法质量的最基本的标准是正确性,另一个重要的标准是运行时间性能.当在一台真实.资源有限的计算机上运行一个算法的时候,经济 ...

  3. php关键词匹配度排序,MySQL_mysql 关键词相关度排序方法详细示例分析,小项目有时需要用到关键词搜 - phpStudy...

    mysql 关键词相关度排序方法详细示例分析 小项目有时需要用到关键词搜索相关性排序,用sphinx显得杀鸡用牛刀,就用mysql的order by对付下. 方法一: select * from ar ...

  4. 山东大学软件学院大二下数据结构课程设计---排序算法的性能分析

    文章目录 一.题目 二.界面图 主界面 比较和移动次数饼图 比较不同表长的对话框 验证稳定性的对话框 课设录屏 三.题目分析 四.基本思路 五.项目结构 1.开发环境 2.结构介绍 3.关键点及难点 ...

  5. 编程方法学23:搜索排序与算法效率分析

    前言 本笔记是斯坦福公开课,编程方法学的学习笔记. 总体而言,这门课讲了很多很基础的东西,具有很强的通用性. 正文 本次的笔记对应的是第二十三节课,这堂课是助教来讲搜索排序与算法效率分析的知识. 1线 ...

  6. 排序算法:希尔排序算法实现及分析

    希尔排序算法介绍 希尔排序是D.LShell 与1957年提出来的一种排序算法,在这之前排序算法的时间复杂度都是O(n^2),希尔排序算法是突破这个时间复杂度的第一批算法之一.我们知道直接插入排序算法 ...

  7. 排序算法:简单选择排序算法实现及分析

    简单选择排序算法介绍 简单选择排序(Simple Selection Sort)就是通过n-1次关键字排序之间的比较,从n-i+1个记录中选择关键字最小的记录,并和第i(1<=i<=n)记 ...

  8. c语言大作业成绩分析问题,河南科技大学c语言课程设计-综合排序设计报告-成绩分析问题.doc...

    河南科技大学c语言课程设计-综合排序设计报告-成绩分析问题.doc 河南科技大学综合程序设计报告成绩分析问题学院电气工程学院年级专业电子161学号161404110104学生姓名李恺指导教师赵老师1. ...

  9. 数据结构实验:内部排序算法的性能分析

    文章目录 前言 一.问题描述 二.问题分析 三.实验结果及分析 (1)实验数据描述 (2)实验结果 (3)性能分析 四.源代码 前言 记录下本学期的数据结构实验 本实验主要集中于比较几种内部排序算法 ...

最新文章

  1. 动态改变_【清涧一小动态】改变从学习做起,教育从家庭出发——延安市家庭教育协会助力清涧县第一小学全方位提升育人水平...
  2. 如何针对产品销售设计一套有效的奖励和惩罚销售措施?
  3. ListBox的使用
  4. 对 makefile 中 $*和静态模式规则结合的学习
  5. 10 PP配置-生产主数据-工作中心相关-定义工作中心公式
  6. 黄聪:主机宝安装wordpress注意事项
  7. 拼多多开卖劳斯莱斯,直降122万,10万人表示想拼!
  8. 程序员离无人值班有多远?
  9. LeetCode 第 3 题(Longest Substring Without Repeating Characters)
  10. 各种变换的原理----DX版本
  11. C++编程语言的应用方向有哪些?
  12. 两种方式实现java生成Excel
  13. RMAN数据库完全备份和恢复
  14. Keil 系列软件安装(一)Keil C51(Keil4)
  15. python体脂率计算
  16. 深度学习笔记(四)——神经网络和深度学习(浅层神经网络)
  17. keyshot手机渲染教程_keyshot渲染教程
  18. Matlab 打不开 无法运行 win10 系统 卡在启动界面没有反应 语言bug
  19. 百度地图 pc浏览器获取经纬度
  20. 《后浪》- 那些口口声声 “一代不如一代”的人 应该看看你们

热门文章

  1. lnmp_auto:自动化安装lnmp环境脚本
  2. 【java设计模式之Command(菜单命令) 】
  3. 用字符串表达式访问JSON数据(java,fastjson)
  4. python ev3图形化编程软件下载_mPython(图形化编程软件)
  5. Spring Boot 2.x(六):优雅的统一返回值
  6. [Leetcode Week15]Populating Next Right Pointers in Each Node
  7. CSS选择器的权重与优先规
  8. 用PHP去掉文件头的Unicode签名(BOM)
  9. apple tv 开发_如何防止Apple TV进入睡眠状态
  10. macos剪切_如何使用macOS的内置“ Kill and Yank”作为替代剪切和粘贴