Разделение на месте, когда массив может содержать или не содержать сводный элемент

Существует ли алгоритм локального разделения (из вид, используемый в реализации Quicksort), который не полагается на элемент сводной таблицы, присутствующий в массиве ?

Другими словами, элементы массива нужно расположить в таком порядке:

  • Элементы меньше оси поворота (если есть)
  • Элементы, равные оси поворота (если есть)
  • Элементы больше оси поворота (если есть)

Он по-прежнему должен возвращать индекс (после сортировки) сводного элемента, если он присутствует в массиве, или специальное значение, если нет; Это может быть дополнение индекса, в который можно вставить элемент для сохранения порядок (например, возвращаемое значение стандарта Java функция двоичного поиска.)

Реализации, которые я видел, требуют, чтобы индекс элемента поворота был указан в качестве параметра (или всегда был в начале массива). К сожалению, я не знаю заранее, присутствует ли поворотный элемент в массиве (или где он находится в массиве.)


Изменить (в ответ на комментарии meriton): мы также можем предположить, что выполняется одно из следующих условий:

  • Длина массива ‹2, или
  • По крайней мере один элемент ‹= pivot и по крайней мере один элемент> = pivot.

В массиве могут быть повторяющиеся значения (включая дубликаты значения сводной таблицы).


person finnw    schedule 12.10.2011    source источник
comment
Что вы определяете как точку опоры? Как вы решаете, существует ли точка поворота?   -  person flight    schedule 13.10.2011
comment
@quasiverse, дано значение pivot value, вы просто не знаете, где оно находится в массиве (и для его поиска не требуется отдельный проход).   -  person finnw    schedule 13.10.2011


Ответы (3)


Это была интересная проблема. Сделать это можно за один последовательный проход по массиву. Пример кода на C # ниже. Он предполагает массив целых чисел с именем a и значение pivot.

// Skip initial items that are < pivot
int iInsert = 0;
while (iInsert < a.Length && a[iInsert] < pivot)
{
    ++iInsert;
}
// Skip items that are = pivot
int numPivot = 0;
while (iInsert < a.Length && a[iInsert] == pivot)
{
    ++iInsert;
    ++numPivot;
}

int iCurrent = iInsert;
// Items will be added AFTER iInsert.
// Note that iInsert can be -1.
--iInsert;
while (iCurrent < a.Length)
{
    if (a[iCurrent] < pivot)
    {
        if (numPivot == 0)
        {
            ++iInsert;
            int temp = a[iInsert];
            a[iInsert] = a[iCurrent];
            a[iCurrent] = temp;
        }
        else
        {
            ++iInsert;
            int temp = a[iInsert];
            a[iInsert - numPivot] = a[iCurrent];
            a[iCurrent] = temp;
            a[iInsert] = pivot;
        }
    }
    else if (a[iCurrent] == pivot)
    {
        ++iInsert;
        int temp = a[iInsert];
        a[iInsert] = pivot;
        a[iCurrent] = temp;
        ++numPivot;
    }
    ++iCurrent;
}

int firstPivot = iInsert - numPivot + 1;

Вероятно, есть возможности для оптимизации.

Интересным в этом подходе является то, что вы можете легко адаптировать его для построения из потока входящих данных. Вам не нужно было бы знать, сколько предметов поступит. Просто используйте список, размер которого можно изменять динамически. Когда приходит последний элемент, ваш список находится в правильном порядке.

person Jim Mischel    schedule 12.10.2011
comment
это не для a=[20,10,20], pivot=20; Он меняет a на [20,20,10] - person finnw; 14.10.2011
comment
@finnw: Ах да. Печально известная ошибка столбов забора. Я должен знать лучше. Спасибо. Вернуться к доске для рисования . . . - person Jim Mischel; 14.10.2011
comment
@finnw: Я верю, что новый код исправляет эту (и некоторые другие) проблемы. - person Jim Mischel; 14.10.2011

Вам повезло: в последние месяцы кодирования ката была реализована быстрая сортировка. Вот что я придумал:

/**
 * Sorts the elements with indices i such that l <= i < r
 */
private static void qsort(int[] a, int left, int right) {
    int l = left;
    int r = right - 1;

    if (l >= r) {
        return;
    }

    int pivot = a[l];
    l++;
    for (;;) {
        while (l <= r && a[l] <= pivot) l++;
        while (a[r] > pivot  && l < r) r--;

        if (l < r) {
            int t = a[l];
            a[l] = a[r];
            a[r] = t;
        } else {
            break;
        }
    }
    l--;
    a[left] = a[l];
    a[l] = pivot;

    qsort(a, left, l);
    qsort(a, r, right);
}

Как видите, алгоритм использует исходное местоположение точки поворота только для нахождения значения точки поворота и для замены точки поворота на индекс между секциями.

Если мы не знаем, что точка поворота существует, мы бы просто обрабатывали значения, равные оси поворота, как значения ‹pivot, т.е. вместо того, чтобы разделять элементы на три группы, меньшие, равные и большие, чем точка поворота, мы бы разделили на две группы меньше или равны pivot и больше pivot, и рекурсивно повторяются на каждом из этих разделов. Это решение было бы правильным.

Однако завершение больше не будет гарантировано: известно, что QuickSort завершается, потому что каждый шаг рекурсии использует работу с более коротким срезом массива, чем его вызывающий, а срезы массива, как известно, короче, потому что они не содержат элемент поворота. Это больше не верно для вашего модифицированного алгоритма. В самом деле, легко увидеть, что прекращение будет зависеть от вашей стратегии выбора опорного значения.

person meriton    schedule 13.10.2011
comment
Он будет прекращен, пока вы сохраните различие между < pivot и <= pivot, т.е. сохраните значения, равные опорным точкам, в их собственном сегменте и не рекурсивно попадете в этот сегмент. - person finnw; 13.10.2011
comment
Нет, это различие не имеет значения, если может не быть значения, равного значению сводной таблицы. Если значение поворота меньше или больше всех элементов в сегменте, а выбор поворота зависит только от сегмента, рекурсия не завершится. - person meriton; 13.10.2011
comment
Ах, на самом деле есть еще одно ограничение, которое запрещает эту ситуацию. Добавлю к вопросу. - person finnw; 13.10.2011

Другая возможность - разделить метод на два, один, который разбивает на [‹= pivot,> pivot], а другой, который разделяет первую часть этого результата на [‹ pivot,> = pivot].

public static int partitionLE(double[] a, int left, int right, double pivot) {
    double x, y;
    if (left >= right) return left;
    for (;;) {
        while ((x = a[left]) <= pivot) {
            if (++ left >= right) return left;
        }
        while ((y = a[right-1]) > pivot) {
            if (left >= -- right) return left;
        }
        if (left < right) {
            a[left] = y;
            a[right-1] = x;
        } else {
            return left;
        }
    }
}
public static int partitionLT(double[] a, int left, int right, double pivot) {
    double x, y;
    if (left >= right) return left;
    for (;;) {
        while ((x = a[left]) < pivot) {
            if (++ left >= right) return left;
        }
        while ((y = a[right-1]) >= pivot) {
            if (left >= -- right) return left;
        }
        if (left < right) {
            a[left] = y;
            a[right-1] = x;
        } else {
            return left;
        }
    }
}
public static int partition(double[] a, int left, int right, double pivot) {
    int lastP = partitionLE(a, left, right, pivot);
    int firstP = partitionLT(a, left, lastP, pivot);
    if (firstP < lastP) {
        return firstP;
    } else {
        return ~firstP;
    }
}
person finnw    schedule 13.10.2011