堆栈溢出错误在C#中实现Quicksort(带有重复项)

时间:2016-02-25 08:53:49

标签: c# stack-overflow quicksort

我正在开发一个项目,我必须实现Quicksort。当我在一个随机整数数组(大小为100,000)上运行Quicksort时,我遇到了堆栈溢出问题。我相信我遇到了涉及重复值的错误。我试图研究这个问题,有人提到要考虑何时data[left] == pivot == data[right],但我不知道在我的情况下如何处理。

这是我的课程,所以我需要使用这种分区方法,并根据说明创建了Quicksort方法。任何帮助将不胜感激。我想知道我做错了什么。

public static int Partition(int[] a, int left, int right, int pivotIndex) {
   int temp;
   int pivotValue = a[pivotIndex];
   a[pivotIndex] = a[right];
   a[right] = pivotValue;

   int store = left;
   for (int i = left; i < right; i++) {
      if (a[i] < pivotValue) {
         temp = a[store];
         a[store] = a[i];
         a[i] = temp;
         store++;
      }
   }
   temp = a[right];
   a[right] = a[store];
   a[store] = temp;

   return store;
}

public static void Quicksort(int[] a, int left, int right) {
   if ((right - left) <= 5)
      InsertionSort(a);
   else if (left < right) {
      int mid = ((right - left) / 2) + left;
      int pivot = Math.Max(Math.Min(a[left], a[right]), Math.Min(Math.Max(a[left], a[right]), a[mid]));
      int pivotIndex = Array.IndexOf(a, pivot);

      Partition(a, left, right, pivotIndex);
      Quicksort(a, left, (pivotIndex - 1));
      Quicksort(a, (pivotIndex + 1), right);
   }
}

3 个答案:

答案 0 :(得分:0)

首先,我建议您在排序之前对数组进行混洗,或至少检查中值(或ninther)并在分区期间使用它,它将概率性地保证O(NLogN)。

此外,如果您有重复项,最好使用3 way partitioning。下面是使用三向分区和混洗的快速排序实现。

public static void Quicksort(int[] a) {
    if (a == null) throw new ArgumentNullException("Array is null");
    Shuffle(a);
    Quicksort(a, 0, a.Length - 1);
}

private static void Quicksort(int[] a, int left, int right) {
    if ((right - left) <= 5)
        InsertionSort(a);
    else if (left <= right) {
        int lt = left, gt = right;
        int v = a[left];
        int i = left;
        while (i <= gt) {
            if      (a[i] < v) Swap(a, lt++, i++);
            else if (a[i] > v) Swap(a, i, gt--);
            else              i++;
        }

        // a[lo..lt-1] < v = a[lt..gt] < a[gt+1..hi]. 
        Quicksort(a, left, lt - 1);
        Quicksort(a, gt + 1, right);
    }
}

public static void Shuffle(int[] a) {
    if (a == null) throw new ArgumentNullException("Array is null");
    int n = a.Length;
    var theRandom = new Random();
    for (int i = 0; i < n; i++) {
        int r = i + theRandom.Next(n-i);     // between i and n-1
        int temp = a[i];
        a[i] = a[r];
        a[r] = temp;
    }
}

private  static void InsertionSort<T>(T[] array) where T:IComparable<T>
{
    for (var i = 1; i < array.Length; i++)
    {
        var value = array[i];
        var j = i - 1;
        while ((j >= 0) && (array[j].CompareTo(value) > 0))
        {
            array[j + 1] = array[j--];
        }
        array[j + 1] = value;
    }
}

private static void Swap(int[] a, int i, int j) {
    int tmp = a[i];
    a[i] = a[j];
    a[j] = tmp;
}

答案 1 :(得分:0)

您的问题与您选择数据透视索引的方式有关。

虽然您正确选择三个值的中位数,但您没有正确找到该中位数的索引。

首先,这是不正确的:

int pivotIndex = Array.IndexOf(a, pivot);

这将找到整个a[]数组中第一次出现中值的索引。

至少它应该是这样的:

int pivotIndex = Array.IndexOf(a, pivot, left, right-left+1);

然而,仍有几个问题:

  1. 您将对整个分区上的数据透视值进行线性搜索。这效率非常低。
  2. 无论实际指数是什么,它都会找到第一次出现的中值。
  3. 如果左侧,右侧或中间值相同,则应选择中间索引,但线性搜索将选择左侧索引。
  4. 解决方案是改变计算三个中值的方式,以便计算实际索引而不仅仅是值。这更加繁琐!

答案 2 :(得分:0)

class QuickSortExample
{
    static int Partition(int[] arr, int left, int right)
    {
        int pivot = arr[left + ((right - left) / 2)]; // use a midpoint pivot to prevent worst case O(n log n), although pivot = arr[left] is fine

        while (left < right)
        {
            while (arr[left] < pivot)
                left++;
            while (arr[right] > pivot)
                right--;

            // handle duplicate entries
            if (arr[right] == pivot && arr[left] == pivot)
                left++;

            if (left < right)
            {
                int tmp = arr[right];
                arr[right] = arr[left];
                arr[left] = tmp;
            }
        }
        return right;
    }

    static void QuickSort(int[] arr, int left, int right)
    {
        if(left < right)
        {
            int pivot = Partition(arr, left, right);

            if (pivot > 1)
                QuickSort(arr, left, pivot - 1);

            if(pivot + 1 < right)
                QuickSort(arr, pivot + 1, right);
        }
    }

    static void TestQuickSort(int[] arr)
    {
        Console.WriteLine("Initial array[" + arr.Length + "] = { " + string.Join<int>(", ", arr) + " }");

        QuickSort(arr, 0, arr.Length - 1);

        Console.WriteLine("Sorted  array[" + arr.Length + "] = { " + string.Join<int>(", ", arr) + " }\n");
    }

    static void QuicksortTestRandom(int maxSize)
    {
        Random r = new Random();
        int[] randomValues = new int[r.Next(maxSize)];
        for (int i = 0; i < randomValues.Length; i++)
        {
            randomValues[i] = r.Next(-999, 999);
        }
        TestQuickSort(randomValues);
    }

    static void Main(string[] args)
    {
        Console.WriteLine("QuickSort (Recursive), complexity = O(n log n)\n");

        int[][] TestArrays = new int[][] {
            new int[] { 6, 2, 5, 8, 1, 10, 0, 3, 7, 9, 4 },
            new int[] { 3, 5, 4, 2, 5, 3, 5, 2, 4, 3, 5, 5, 4, 1, 4 },
            new int[] { 67, 12, 95, 56, 1, 85, 85, 1, 100, 23, 60, 9, 0, 85, 0, 85, 85, 90, 85, 85 },
            new int[] { 1 },
            new int[] { 0, 0 },
            new int[] { 1, 0 },
            new int[] { 0, 1 },
            new int[] { 1, 0, 2 },
            new int[] { 1, 0, 1, 0 },
            new int[]  {2, 1, 2, 1, 2, 1, 2, 1 },
            new int[] { 1, 0, -1 },
            new int[] { },
            new int[] { 1, 3, 6, 2, 7, 5, -1, 4, 8, 9, 0 },
        };

        foreach(int[] arr in TestArrays)
        {
            TestQuickSort(arr);
        }

        QuicksortTestRandom(16);

        Console.WriteLine("done... press enter to end.\n");
        Console.ReadLine();
    }
}