C# List.Sort分析

问题

// 递增排序
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.
$(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 keys, IComparer comparer)
{
    IntrospectiveSort(keys, comparer.Compare);
}
internal static void IntrospectiveSort(Span keys, Comparison 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 keys, Comparison 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 keys, int depthLimit, Comparison 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 keys, Comparison 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 keys, Comparison 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 keys, Comparison 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 keys, int i, int n, Comparison 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 a, int i, int j)
{
   Debug.Assert(i != j);

   T t = a[i];
   a[i] = a[j];
   a[j] = t;
}

你可能感兴趣的:(细节,数据结构与算法,c#,数据结构,算法)