问题
// 递增排序
list.Sort((num1, num2) => num1 > num2 ? 1 : -1);
// 递减排序
list.Sort((num1, num2) => num1 >= num2 ? -1 : 1);
// 报错如下
Unhandled exception. System.ArgumentException: Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yi
elds different results. IComparer: 'System.Comparison`1[System.UInt32]'.
at System.Collections.Generic.ArraySortHelper`1.Sort(Span`1 keys, Comparison`1 comparer)
at System.Collections.Generic.List`1.Sort(Comparison`1 comparison)
at Program.<Main>$(String[] args) in E:\Own\SVN\do_server\ET\Tools\TestConsoleApp\TestConsoleApp\Program.cs:line 6
解决办法
如果list中的存在相同的元素100以上,上面的写法会出现下面的报错,主要是因为上面的写法无法判断两个相同的元素是否相同(a value does not compare equal to itself)
将相关的比较函数改为如下就可以:
// 递增排序
list.Sort((num1, num2) => num1 >= num2 ? 1 : -1);
// 递减排序
list.Sort((num1, num2) => num1 > num2 ? -1 : 1);
排序核心代码
这里可以了解到,C#库里,将排序根据数组的长度、高度(根据长度算出树的高度)来确定每一次排序使用的算法.
主要包括:
比较排序 -> length [1,3]
插入排序 -> length [4, 阈值]
堆排序 -> depth [0, 0]
快排序 -> length [阈值+1, ..]
// 核心处理入口
public void Sort(Span<T> keys, IComparer<T> comparer)
{
IntrospectiveSort(keys, comparer.Compare);
}
internal static void IntrospectiveSort(Span<T> keys, Comparison<T> comparer)
{
Debug.Assert(comparer != null);
if (keys.Length > 1)
{
IntroSort(keys, 2 * (BitOperations.Log2((uint)keys.Length) + 1), comparer);
}
}
// 如果keys[i] > keys[j] 则交换位置
private static void SwapIfGreater(Span<T> keys, Comparison<T> comparer, int i, int j)
{
Debug.Assert(i != j);
if (comparer(keys[i], keys[j]) > 0)
{
T key = keys[i];
keys[i] = keys[j];
keys[j] = key;
}
}
// 开始真正的排序
private static void IntroSort(Span<T> keys, int depthLimit, Comparison<T> comparer)
{
Debug.Assert(!keys.IsEmpty);
Debug.Assert(depthLimit >= 0);
Debug.Assert(comparer != null);
int partitionSize = keys.Length;
while (partitionSize > 1)
{
if (partitionSize <= Array.IntrosortSizeThreshold)
{
// 如果未排序的元素 <= 3,则直接比较交换
if (partitionSize == 2)
{
SwapIfGreater(keys, comparer, 0, 1);
return;
}
if (partitionSize == 3)
{
SwapIfGreater(keys, comparer, 0, 1);
SwapIfGreater(keys, comparer, 0, 2);
SwapIfGreater(keys, comparer, 1, 2);
return;
}
// 插入排序算法
// 元素的范围在 [4, Array.IntrosortSizeThreshold] 时使用
InsertionSort(keys.Slice(0, partitionSize), comparer);
return;
}
if (depthLimit == 0)
{
// 堆排序算法
HeapSort(keys.Slice(0, partitionSize), comparer);
return;
}
depthLimit--;
// 快排序算法
// 获取本次快排的 分割点(根据快排的特点,每一轮排序都会确定一个元素的最终位置)
int p = PickPivotAndPartition(keys.Slice(0, partitionSize), comparer);
// 将 [p+1, p+partitionSize) 这个范围的数据再次放入 IntroSort 进行下一轮排序
// Note we've already partitioned around the pivot and do not have to move the pivot again.
IntroSort(keys[(p+1)..partitionSize], depthLimit, comparer);
// 处理下一次的循环的排序范围
partitionSize = p;
}
}
// 插入排序
private static void InsertionSort(Span<T> keys, Comparison<T> comparer)
{
for (int i = 0; i < keys.Length - 1; i++)
{
T t = keys[i + 1];
int j = i;
while (j >= 0 && comparer(t, keys[j]) < 0)
{
keys[j + 1] = keys[j];
j--;
}
keys[j + 1] = t;
}
}
// 快排序算法
private static int PickPivotAndPartition(Span<T> keys, Comparison<T> comparer)
{
Debug.Assert(keys.Length >= Array.IntrosortSizeThreshold);
Debug.Assert(comparer != null);
int hi = keys.Length - 1;
// Compute median-of-three. But also partition them, since we've done the comparison.
int middle = hi >> 1;
// 挑选一个合适的middle点,(如果middle点选择的不好,会导致算法的时间复杂度退化为O(n^2),如果默认选择第一个,而数组本身已经有序,就会出现这种问题)
// 这里其实就是,保证middle点 的值位于 一个元素和最后一个元素之间
// Sort lo, mid and hi appropriately, then pick mid as the pivot.
SwapIfGreater(keys, comparer, 0, middle); // swap the low with the mid point
SwapIfGreater(keys, comparer, 0, hi); // swap the low with the high
SwapIfGreater(keys, comparer, middle, hi); // swap the middle with the high
T pivot = keys[middle];
Swap(keys, 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.
// 先右再左比较,确定最后 keys[middle]的最终位置
while (left < right)
{
// 从右到左循环,遇到第一个大于pivot的值结束(这里假设comparer是满足递增排序的)
while (comparer(keys[++left], pivot) < 0) ;
// 从左到右循环,遇到第一个大于pivot的值结束(这里假设comparer是满足递增排序的)
while (comparer(pivot, keys[--right]) < 0) ;
if (left >= right)
break;
// 将比 pivot大的值放在右边,小的值放在左边
Swap(keys, left, right);
}
// 放 keys[middle]的到最终位置
// Put pivot in the right location.
if (left != hi - 1)
{
Swap(keys, left, hi - 1);
}
return left;
}
// 堆排序
private static void HeapSort(Span<T> keys, Comparison<T> comparer)
{
Debug.Assert(comparer != null);
Debug.Assert(!keys.IsEmpty);
int n = keys.Length;
for (int i = n >> 1; i >= 1; i--)
{
DownHeap(keys, i, n, comparer);
}
for (int i = n; i > 1; i--)
{
Swap(keys, 0, i - 1);
DownHeap(keys, 1, i - 1, comparer);
}
}
// 将最大的或最小的元素放到 i-1 处
private static void DownHeap(Span<T> keys, int i, int n, Comparison<T> comparer)
{
Debug.Assert(comparer != null);
T d = keys[i - 1];
while (i <= n >> 1)
{
int child = 2 * i;
if (child < n && comparer(keys[child - 1], keys[child]) < 0)
{
child++;
}
if (!(comparer(d, keys[child - 1]) < 0))
break;
keys[i - 1] = keys[child - 1];
i = child;
}
keys[i - 1] = d;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void Swap(Span<T> a, int i, int j)
{
Debug.Assert(i != j);
T t = a[i];
a[i] = a[j];
a[j] = t;
}