Search code examples
c#algorithmsortingmergesort

C# merge sort performance


just a quick note, this is not homework. I'm just trying to brush up on my algorithms. I'm playing around with MergeSort in C# and I've written a recursive method that can sort based on Generics:

class SortAlgorithms
{

    public T[] MergeSort<T> (T[] unsortedArray) where T : System.IComparable<T>
    {
        T[] left, right;
        int middle = unsortedArray.Length / 2;

        left = new T[middle];
        right = new T[unsortedArray.Length - middle];

        if (unsortedArray.Length <= 1)
            return unsortedArray;

        for (int i = 0; i < middle; i++)
        {
            left[i] = unsortedArray[i];
        }

        for (int i = middle; i < unsortedArray.Length; i++)
        {
            right[i - middle] = unsortedArray[i];
        }

        left = MergeSort(left);

        right = MergeSort(right);


        return Merge<T>(left, right);
    }

    private T[] Merge<T> (T[] left, T[] right) where T : System.IComparable<T>
    {
        T[] result = new T[left.Length + right.Length];

        int currentElement = 0;

        while (left.Length > 0 || right.Length > 0)
        {
            if (left.Length > 0 && right.Length > 0)
            {
                if (left[0].CompareTo(right[0]) < 0)
                {
                    result[currentElement] = left[0];
                    left = left.Skip(1).ToArray();
                    currentElement++;
                }
                else
                {
                    result[currentElement] = right[0];
                    right = right.Skip(1).ToArray();
                    currentElement++;
                }
            }
            else if (left.Length > 0)
            {
                result[currentElement] = left[0];
                left = left.Skip(1).ToArray();
                currentElement++;
            }
            else if (right.Length > 0)
            {
                result[currentElement] = right[0];
                right = right.Skip(1).ToArray();
                currentElement++;
            }
        }

        return result;
    }
}

This works but it is painfully slow. I've used System.Diagnostic.StopWatch to check performance against Array.Sort (which uses QuickSort algorithm) to compare against my MergeSort and the difference is so significant I'm wondering if maybe I'm implementing this wrong. Any comments?


Solution

  • I am not a C# programmer, but could the problem be the use of statements like this one?

    left = left.Skip(1).ToArray();
    

    This might be implemented in a way that forces a deep copy of the underlying array. If so, this would drop the performance of merge from O(n) to O(n2), immediately dropping the performance of the resulting merge sort from O(n log n) to O(n2).

    (This is because the recurrence changes from

    T(1) = O(1)

    T(n) ≤ 2T(n / 2) + O(n)

    which has solution T(n) = O(n log n), to

    T(1) = O(1)

    T(n) ≤ 2T(n / 2) + O(n2)

    which has solution T(n) = O(n2).)