Pregunta

Creo que QuickSort en algunas condiciones específicas puede provocar una excepción de desbordamiento de pila.

Hay dos formas básicas de seleccionar el elemento pivote durante el proceso de clasificación: el valor pivote puede ser el elemento en el medio del rango ordenado o el elemento elegido al azar (dentro del rango ordenado).¿El segundo método (aleatorio) es menos propenso a desbordarse que el primero?¿Podrías aconsejarme?

Aquí está mi versión de clasificación rápida (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;

¡Gracias por tu consejo de antemano!

Mariusz.

¿Fue útil?

Solución

El uso de cualquier elemento en un índice específico (primero, último o medio) como el elemento de pivote siempre incurre en el riesgo de degeneración con conjuntos de datos específicos. Primer y último elemento es particularmente malo porque degeneran con los datos ordenados previamente (o casi) preclasificados, que es bastante común. El elemento medio es menos problemático en la práctica, pero todavía vulnerables a los conjuntos de datos construido maliciosamente.

Uso de un elemento aleatorio significa degeneración sólo puede ocurrir a través de la mala suerte puro (suponiendo que el RNG no es predecible por un atacante hipotético), por lo que es una buena táctica. Otra mejora que reduce significativamente la probabilidad de ser golpeado por que la mala suerte sería el uso de la mediana de 3 (o 5, o más) elementos escogidos al azar, pero tiene que ser ponderado frente a la complejidad adicional y el tiempo de ejecución de este incurre.

Otros consejos

Una manera probabilística para mejorar la eficiencia es escoger 3 elementos aleatorios y utilizar el valor medio (el que no es el más grande ni el menos).

También puede utilizar una pila de discos para empujar y hacer estallar los límites y escribir un bucle en lugar de hacer llamadas recursivas (también usará menos memoria ya que no tendrá que ser replicado por todas las llamadas el puntero a la matriz ).

EDIT: Me he dado cuenta de que el procedimiento interno no toma el puntero como parámetro, por lo que se olvide esa parte ^ _ ^ de todos modos, que la pila tiene más información que sólo los parámetros de la función, por lo que será aún más eficiente de la memoria (y el punto principal es que el montón eran la pila de datos se asigna es generalmente más grandes que la pila de proceso).

Gracias por tus respuestas.

Fortran, gracias por sus sugerencias sobre cómo crear un método no recursivo.Basándome en ellos, logré hacer una clasificación rápida iterativa y parece estar funcionando correctamente :).

Aquí está el código:

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;

Espero haberlo implementado de manera óptima.Utilicé una matriz dinámica para almacenar los límites de clasificación (cada par de elementos es el límite inferior y superior).

Este método iterativo parece ser un poco más lento que el recursivo, pero creo que no importa tanto.

Si notas algún error o conoces alguna manera de optimizar el método, te agradeceré que me lo hagas saber.

¡Gracias!

Mariusz.

Una implementación quicksort decente utiliza O (log n) espacio de pila. Logra que por la clasificación de la primera subserie más pequeño. Peor de los casos si no lo hace es la situación en la que el pivote es el elemento más grande y se intenta clasificar un subconjunto que es sólo un pequeño cada vez. Esto se produce al utilizar los datos ya almacenados como entrada y toma como pivote el elemento de la derecha.

Su aplicación explícita pila es más lento y sufre del mismo problema (aunque ahora es del montón en lugar de pila).

Otra cosa que falta es un cambio a la ordenación por inserción cuando el subconjunto es pequeño (5-25 elementos). También echa un vistazo a las preguntas quicksort de doble pivote en este sitio.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top