QuickSort和堆栈溢出异常

时间:2009-06-03 11:49:05

标签: delphi stack-overflow quicksort

我认为QuickSort在某些特定条件下可能会导致堆栈溢出异常。

在排序过程中有两种基本的方法可以选择枢轴元素 - 枢轴值可以是排序范围中间的元素,也可以是随机选择的元素(在排序范围内)。第二种方法(随机)是否比第一种方法更容易发生堆栈溢出?你能告诉我吗?

这是我的快速排序版本(Delphi):

procedure QuickSort(lLowBound, lHighBound: integer; lCompare: TListSortCompare;
  lSwap: TListSortSwap);

  procedure Sort(lLowIndex, lHighIndex: integer);
  var
    lLeft: Integer;
    lRight: Integer;
    lPivot: Integer;
    lLeftCompare: Integer;
    lRightCompare: Integer;
  begin
    repeat
      lLeft := lLowIndex;
      lRight := lHighIndex;
      lPivot := (lLowIndex + lHighIndex) div 2; //the pivot as the element in the middle
      //lPivot := lLowIndex + Random(lHighIndex - lLowIndex + 1); //the pivot chosen randomly
      repeat
        lLeftCompare := lCompare(lLeft, lPivot);
        while lLeftCompare < 0 do
        begin
          Inc(lLeft);
          lLeftCompare := lCompare(lLeft, lPivot);
        end;
        lRightCompare := lCompare(lRight, lPivot);
        while lRightCompare > 0 do
        begin
          Dec(lRight);
          lRightCompare := lCompare(lRight, lPivot);
        end;

        if lLeft <= lRight then
        begin
          if not ((lLeftCompare = 0) and (lRightCompare = 0)) then
          begin
            lSwap(lRight, lLeft);

            if lPivot = lLeft then
              lPivot := lRight
            else if lPivot = lRight then
              lPivot := lLeft;
          end;
          Inc(lLeft);
          Dec(lRight);
        end;
      until lLeft > lRight;

      if (lLowIndex < lRight) then
        Sort(lLowIndex, lRight);

      lLowIndex := lLeft;
    until lLeft >= lHighIndex;
  end;

begin
  if lHighBound > lLowBound then
    Sort(lLowBound, lHighBound);
end;

提前感谢您的建议!

马里乌什。

4 个答案:

答案 0 :(得分:4)

使用特定索引(first,last或middle)中的任何元素作为pivot元素始终会产生使用特定数据集退化的风险。第一个和最后一个元素特别糟糕,因为它们使用预先排序(或接近预先排序)的数据退化,这很常见。中间元素在实践中问题较少,但仍然容易受到恶意构建的数据集的影响。

使用随机元素意味着退化只能通过纯粹的坏运气发生(假设RNG不能被假想的攻击者预测),所以这是一个很好的策略。进一步改进可以显着降低遭遇运气不佳的可能性,即使用随机选择的3个(或5个或更多)元素的中位数,但必须对其产生的额外复杂性和运行时间进行加权。

答案 1 :(得分:2)

提高效率的一种概率方法是选择3个随机元素并使用中间值(不是最大值也不是最小值)。

您还可以使用一堆记录来推送和弹出边界并编写循环而不是进行递归调用(因为指向数组的指针不需要为所有调用复制,因此它将使用更少的内存)。

编辑:我注意到内部过程没有将指针作为参数,所以忘记那个部分^ _ ^无论如何,堆栈帧有更多的信息而不仅仅是函数的参数,所以它仍然是更高效的内存(主要的一点是堆分配的数据堆通常比进程堆栈​​大。)

答案 2 :(得分:0)

感谢您的回答。

Fortran,感谢您提出有关制作非递归方法的建议。基于它们,我设法进行迭代快速排序,它似乎正常工作:)。

以下是代码:

procedure QuickSortI(lLowBound, lHighBound: integer; lCompare: TListSortCompare;
  lSwap: TListSortSwap);
var
  lLeft: Integer;
  lRight: Integer;
  lPivot: Integer;
  lLeftCompare: Integer;
  lRightCompare: Integer;
  lStack: array of integer;
  lStackLen: integer;
begin
  if lHighBound > lLowBound then
  begin
    lStackLen := 2;
    SetLength(lStack, lStackLen);
    lStack[lStackLen - 1] := lLowBound;
    lStack[lStackLen - 2] := lHighBound;

    repeat
      lLowBound := lStack[lStackLen - 1];
      lHighBound := lStack[lStackLen - 2];
      SetLength(lStack, lStackLen - 2);
      Dec(lStackLen, 2);

      lLeft := lLowBound;
      lRight := lHighBound;
      lPivot := (lLowBound + lHighBound) div 2;
      repeat
        lLeftCompare := lCompare(lLeft, lPivot);
        while lLeftCompare < 0 do
        begin
          Inc(lLeft);
          lLeftCompare := lCompare(lLeft, lPivot);
        end;
        lRightCompare := lCompare(lRight, lPivot);
        while lRightCompare > 0 do
        begin
          Dec(lRight);
          lRightCompare := lCompare(lRight, lPivot);
        end;

        if lLeft <= lRight then
        begin
          if not ((lLeftCompare = 0) and (lRightCompare = 0)) then
          begin
            lSwap(lRight, lLeft);

            if lPivot = lLeft then
              lPivot := lRight
            else if lPivot = lRight then
              lPivot := lLeft;
          end;
          Inc(lLeft);
          Dec(lRight);
        end;
      until lLeft > lRight;

      if (lHighBound > lLeft) then
      begin
        Inc(lStackLen, 2);
        SetLength(lStack, lStackLen);
        lStack[lStackLen - 1] := lLeft;
        lStack[lStackLen - 2] := lHighBound;
      end;

      if (lLowBound < lRight) then
      begin
        Inc(lStackLen, 2);
        SetLength(lStack, lStackLen);
        lStack[lStackLen - 1] := lLowBound;
        lStack[lStackLen - 2] := lRight;
      end;

    until lStackLen = 0;
  end;
end;

我希望我以最佳方式实施它。我使用动态数组来存储排序边界(每对项目都是低限和高限)。

这种迭代方法似乎比递归方法稍慢,但我认为这并不重要。

如果您发现错误或者您知道优化方法的方法,如果您让我知道,我将不胜感激。

谢谢!

马里乌什。

答案 3 :(得分:0)

一个不错的快速排序实现使用O(log n)堆栈空间。它通过首先对最小的子阵列进行排序来实现这一点。最糟糕的情况是,如果你不这样做,那么枢轴是最大元素的情况,你尝试排序每次只有一个较小的子阵列。当您使用已排序的数据作为输入并将其作为右侧元素的轴时,会发生这种情况。

您的显式堆栈实现速度较慢,并且遇到同样的问题(尽管它现在是堆而不是堆栈)。

缺少的另一件事是当子阵列很小(5-25个元素)时切换到插入排序。另请参阅本网站上的双枢轴快速排序问题。