【问题标题】:how was Array.Sort implemented in .NET?Array.Sort 是如何在 .NET 中实现的?
【发布时间】:2011-05-01 10:00:04
【问题描述】:

我在我的编程中使用结构,我使用IComparer根据结构中的值对结构进行排序。

微软是如何实现Array.Sort() 方法的?有没有这方面的文档(参考)? Visual Basic 中所有类型的Sort() 是否都一样?

这是我想要的一个简单示例。

Dim MyArray(6) As Integer
    MyArray(0) = 1
    MyArray(1) = 45
    MyArray(2) = 45
   ' Some Code.....
    '.........
    '..........
    MyArray(3) = 1
    MyArray(4) = 10
    ' Some Code.....
    '.........
    '..........
    MyArray(5) = 1
    MyArray(6) = 57

    Array.Sort(MyArray)

Array.Sort() 会将此数组排序为:(1 1 1 10 45 45 57)

数字 1 是如何排序的?是结束第一个还是将旧的保留在同一索引中?

在我的原始示例中(排序前),MyArray(0) = 1 和排序后的MyArray(0) = 1

这是原来的 1 还是另一个 1(添加到数组中的最新)移到了那个位置?

如果排序后的MyArray(0) = 1应该是排序前的MyArray(5) = 1

【问题讨论】:

    标签: .net vb.net sorting


    【解决方案1】:

    它使用Quicksort 算法,该算法在有效(就地)实施时不稳定。这意味着它不保证相等的值在排序后保留其先前的相对位置。

    例如,如果你有一堆点:

    Point[] points = new Point[]
    {
       new Point(0, 1),
       new Point(0, 2),
       new Point(0, 3),
       new Point(1, 1),
       new Point(1, 2),
       new Point(1, 3)
    };
    

    然后您使用此比较器仅按 x 坐标对这些点进行排序:

    private int CompareByX(Point a, Point b)
    {
        return a.X - b.X;
    }
    

    它只会保证这些点是按它们的 x 坐标排序的,这意味着你很容易得到一个混淆的顺序(当查看 y 坐标时):

    Point(0, 3)
    Point(0, 2)
    Point(0, 1)
    Point(1, 3)
    Point(1, 2)
    Point(1, 1)
    

    [编辑]

    这并不意味着排序算法是不确定的(随机的)。对于相同的输入数据,您将在每次运行时获得相同的输出数据。如果您精确地检查算法,您还可以预测它的实际重组方式,但这是不必要的。只需知道在使用排序例程时会发生这种情况就足够了。

    这是一个针对您的问题的工作示例,尝试更改测试数据大小(Main 中的第一行)并观察数组在每次运行时如何重新组织:

    class Program
    {
        static void Main()
        {
            Point[] points = CreateTestData(1, 4).ToArray();
            DisplayItems("Before", points);
            Array.Sort(points, CompareByX);
            DisplayItems("After", points);
            Console.ReadLine();
        }
    
        private class Point
        {
            public int X { get; private set; }
            public int Y { get; private set; }
            public override string ToString()
            { return string.Format("({0},{1})", X, Y); }
            public Point(int x, int y)
            { X = x; Y = y; }
        }
    
        private static int CompareByX(Point a, Point b)
        { return a.X - b.X; }
    
        private static IEnumerable<Point> CreateTestData(int maxX, int maxY)
        {
            for (int x = 0; x <= 1; x++)
                for (int y = 0; y <= 4; y++)
                    yield return new Point(x, y);
        }
    
        private static void DisplayItems(string msg, Point[] points)
        {
            Console.WriteLine(msg);
            foreach (Point p in points)
                Console.WriteLine(p.ToString());
            Console.WriteLine();
        }
    }
    

    当然,如果你扩展比较器委托以包含 Y 坐标,你就不会有这个问题:

        private static int CompareByX(Point a, Point b)
        {
             if (a.X == b.X) 
                return a.Y - b.Y;
             else
                return a.X - b.X;
        }
    

    【讨论】:

    • 我从你的回答中了解到排序的性质是未知的!?
    • 不,它不是随机的,因为快速排序分区的枢轴点总是取自同一个地方(当前分区的中间)。因此,您每次都会以相同的方式混淆您的价值观。
    【解决方案2】:

    Array.Sort 是一种不稳定的排序,因此相同元素的顺序是未定义的且不守恒的。 MSDN 中Array.Sort 上的文章指出:

    此方法使用快速排序算法。此实现执行不稳定的排序;也就是说,如果两个元素相等,则可能不会保留它们的顺序。相比之下,稳定排序会保留相等元素的顺序。

    另一方面,LINQ 的 OrderBy 方法是稳定的。 OrderBy in the MSDN上的文章称:

    此方法执行稳定排序;也就是说,如果两个元素的键相等,则保留元素的顺序。相反,不稳定的排序不会保留具有相同键的元素的顺序。

    【讨论】:

      【解决方案3】:

      使用.Net Reflector 并亲眼看看...从方法名称看来,它们使用的是 QuickSort 算法:System.Array+SorterObjectArray.QuickSort

      【讨论】:

        【解决方案4】:

        Array.Sort() 与大多数内置排序器一样,在后台的帮助器类中使用 QuickSort 实现。排序相对高效,并且可以使用 IComparable 和 IComparer 接口进行自定义,但不稳定;您的示例中的三个 1 可能以与排序之前不同的相对顺序结束。如果您使用更复杂的结构,您可以看到这一点:

        struct TestStruct
        {
           int a;
           int b;
        }
        
        ...
        
        //As declared, this array is already sorted by both "a" and "b" properties
        var myStructAray = new [] {new TestStruct{a=1,b=1}, new TestStruct{a=1,b=2}, new TestStruct{a=1,b=3});
        
        //QuickSorts myStructArray based on the comparison of the lambda for each element
        var newArray = Array.Sort(myStructArray, x=>x.a); 
        
        //newArray may have a different order as myStructArray at this time
        for(var i=0;i<myStructArray.Count();i++)
        {
           //NUnit assertion; will almost surely fail given a sufficient array length
           Assert.AreEqual(myStructArray[i].b, newArray[i].b);
        }
        

        【讨论】:

          【解决方案5】:

          首先,让我们解决您当前计划中有关 .Net(VB 或 C#)最佳实践的几个问题:

          1. 除非您有充分的理由不这样做,否则优先选择类而不是结构
          2. Avoid using Arrays
          3. 您可以将该数组构建为单行:Dim MyArray() As Integer = {1, 45, 45, 1, 10, 1, 57}

          至于你的问题是否是“相同”的值 1,答案是这取决于你如何看待它。对于一般情况,答案是是否考虑排序算法stable。 .Net 的排序算法不稳定。

          对于这种特定情况,您问错了问题。 1 是 1 是 1。它们之间没有区别。如果您觉得这很重要,我挑战您提供代码以检测原始代码中该列表中任意两个“1”之间的差异(数组索引除外)。

          【讨论】:

          • +1。但这个问题是有道理的,恕我直言。是的,1 和 1 没有区别,但是当排序算法影响更复杂的对象,或者对象或不同的派生类型时,它会有所不同。
          • 仅供参考——您的 VB 代码语法无效。指定显式边界时,数组初始化是不合法的。取出“6”以使其有效。
          【解决方案6】:

          其他答案基于旧文档,因此这里是更新的答案。根据the latest documentation(强调我的):

          .NET Framework 4 及更早版本使用快速排序算法。现在Array.Sort使用内省排序(introsort)算法如下:

          • 如果分区大小少于 16 个元素,则使用插入排序算法。

          • 如果分区数超过2 * Log N,其中N是输入数组的范围,则使用Heapsort算法。

          • 否则,它使用快速排序算法。

          它仍然是一个不稳定的排序。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-05-17
            • 1970-01-01
            • 1970-01-01
            • 2011-10-02
            • 1970-01-01
            • 2023-03-07
            相关资源
            最近更新 更多