Delphi 快速排序和堆栈溢出异常

Delphi 快速排序和堆栈溢出异常,delphi,stack-overflow,quicksort,Delphi,Stack Overflow,Quicksort,我认为某些特定条件下的快速排序可能会导致堆栈溢出异常 在排序过程中有两种基本的选择枢轴元素的方法-枢轴值可以是排序范围中间的元素或者随机选择的元素(在排序范围内)。第二种方法(随机)是否比第一种方法更容易发生堆栈溢出?你能给我提个建议吗 以下是我的快速排序(Delphi)版本: 过程快速排序(LLOWBOND,lHighBound:integer;lCompare:TListSortCompare; lSwap:tlistortswap); 过程排序(lLowIndex,lHighIndex:i

我认为某些特定条件下的快速排序可能会导致堆栈溢出异常

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

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

过程快速排序(LLOWBOND,lHighBound:integer;lCompare:TListSortCompare;
lSwap:tlistortswap);
过程排序(lLowIndex,lHighIndex:integer);
变量
lLeft:整数;
lRight:整数;
lPivot:整数;
lLeftCompare:整数;
lRightCompare:整数;
开始
重复
lLeft:=lLowIndex;
lRight:=lHighIndex;
lPivot:=(lLowIndex+lHighIndex)第2分部//作为中间元素的枢轴
//lPivot:=lLowIndex+随机(lHighIndex-lLowIndex+1)//轴心是随机选择的
重复
lLeftCompare:=lCompare(lLeft,lPivot);
而lLeftCompare<0 do
开始
公司(lLeft);
lLeftCompare:=lCompare(lLeft,lPivot);
结束;
lRightCompare:=lCompare(lRight,lPivot);
当lRightCompare>0时,请执行以下操作
开始
十二月(右);
lRightCompare:=lCompare(lRight,lPivot);
结束;
如果不正确;
如果(lLowIndex=lHighIndex;
结束;
开始
如果lHighBound>LLOWBOND,则
排序(LLOWBOND、lHighBound);
结束;
提前谢谢你的建议


Mariusz.

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

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


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

在特定索引处使用任何元素(第一个、最后一个或中间)因为pivot元素总是会带来特定数据集退化的风险。第一个和最后一个元素尤其糟糕,因为它们会退化预排序(或几乎预排序)的数据,这是很常见的。中间元素在实践中问题较少,但仍然容易受到恶意构造的数据集的攻击


使用随机元素意味着退化只能通过纯粹的厄运发生(假设RNG不可由假设的攻击者预测),因此这是一个好策略。显著降低被厄运击中可能性的进一步改进是使用中位数3(或5,或更多)随机选择的元素,但它必须根据额外的复杂性和运行时间进行加权。

感谢您的回答

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;
过程快速排序(LLOWBOND,lHighBound:integer;lCompare:TLISTORTCOMPARE;
lSwap:tlistortswap);
变量
lLeft:整数;
lRight:整数;
lPivot:整数;
lLeftCompare:整数;
lRightCompare:整数;
lStack:整数数组;
lStackLen:整数;
开始
如果lHighBound>LLOWBOND,则
开始
lStackLen:=2;
设置长度(lStack,lStackLen);
lStack[lStackLen-1]:=lLowBound;
lStack[lStackLen-2]:=lHighBound;
重复
lLowBound:=lStack[lStackLen-1];
lHighBound:=lStack[lStackLen-2];
设置长度(lStack,lStackLen-2);
12月(伊斯塔克伦,2);
lLeft:=lLowBound;
lRight:=lHighBond;
lPivot:=(低低音+低低音)第2部分;
重复
lLeftCompare:=lCompare(lLeft,lPivot);
而lLeftCompare<0 do
开始
公司(lLeft);
lLeftCompare:=lCompare(lLeft,lPivot);
结束;
lRightCompare:=lCompare(lRight,lPivot);
当lRightCompare>0时,请执行以下操作
开始
十二月(右);
lRightCompare:=lCompare(lRight,lPivot);
结束;
如果不正确;
如果(lHighBond>lLeft),则
开始
公司(lStackLen,2),;
设置长度(lStack,lStackLen);
lStack[lStackLen-1]:=lLeft;
lStack[lStackLen-2]:=lHighBound;
结束;
如果(lLowBound
我希望我以最佳方式实现它。我使用了一个动态数组来存储排序边界(每对项都是下限和上限)

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

如果您注意到错误或知道优化方法的方法,如果您让我知道,我将不胜感激

谢谢


Mariusz.

一个合适的快速排序实现使用O(logn)堆栈空间。它通过首先对最小的子数组进行排序来实现这一点。如果不这样做,最糟糕的情况是枢轴是最大的元素,并且您尝试对一个子数组进行排序,每次只对一个子数组进行排序。当您使用已排序的数据作为输入,并将正确的元素作为轴心时,就会发生这种情况

显式堆栈实现速度较慢,并且会受到
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;