Алгоритм быстрой сортировки неправильно назначает точку опоры

Я видел эту фантастическую визуализацию алгоритма быстрой сортировки: http://www.youtube.com/watch?v=Z5nSXTnD1I4

Я почувствовал, что действительно понял принципы быстрой сортировки, и с помощью нескольких руководств в Интернете приступил к созданию собственной быстрой сортировки.
Вот что у меня получилось:

public void quickSort(int[] a, int left, int right) {

    int index = partition(a, left, right);
    if (left < index - 1)
      quickSort(a, left, index);
    if (index < right)
      quickSort(a, index + 1, right);
}

private int partition (int[] a, int left, int right) {
    int i = left - 1;
    int j = right + 1;
    int pivot = a[0];

    while (i < j) {

        i++;

        while (a[i] < pivot)
            i++;

        j--;

        while (a[j] > pivot)
            j--;

        if (i < j)
            swap (a, i, j);
    }
return i;
}   

private void swap (int[] a, int i, int j) {
    int temp = a[i];
    a[i] = a[j];
    a[j] = temp;
}

Значения слева и справа следующие:

left = 0
right = array size - 1

К сожалению, вывод не правильный. Проблема, по-видимому, заключается в моей трактовке оси. В визуализации, которую я наблюдал, инструктор физически убрал стержень, а указатель не указывал ни на что. Он продолжил обучение, и когда он дошел до точки, где i и j (то, что я называю левым и правым) оба указывали на одно и то же пустое место, он вставил точку опоры и продолжил.

Поскольку я физически удерживаю стержень на месте, мне трудно правильно его отсортировать.

В этом коде я работаю с вводом:

4 8 1 6 3 7 2 5

Я получаю вывод:

1 3 2 6 8 7 4 5

Как только значение «4» (то есть опорная точка) отсортировано в самом начале алгоритма, я никогда не прибегаю к нему, что сбивает все с толку. Кроме того, я думаю, что с методом quickSort что-то не так.

Может ли кто-нибудь дать мне несколько советов? Спасибо.

Правка: две правки, которые были здесь, были удалены, поскольку они содержали ненужную и неправильную информацию. Один из них изменил точку поворота на: (влево + вправо) / 2. Это, конечно, было неправильно по причинам, объясненным в ответах ниже.


person Andrew Martin    schedule 23.02.2013    source источник
comment
Закрытие как слишком локализованное.   -  person djechlin    schedule 24.02.2013
comment
@djechlin: я думал, что он нацелен на определенный алгоритм программирования, как указано в FAQ. Если я ошибаюсь, не могли бы вы порекомендовать, на какой бирже стека спросить об этом?   -  person Andrew Martin    schedule 24.02.2013
comment
@djechlin он задает проблему с кодированием, показывая код. Как это может быть слишком локализовано??   -  person Thorbjørn Ravn Andersen    schedule 24.02.2013
comment
@djechlin Еще не решил, полностью ли я согласен в этом случае, но, по крайней мере, подождите некоторое время, чтобы у вопроса был шанс получить ответ.   -  person Bernhard Barker    schedule 24.02.2013
comment
@ ThorbjørnRavnAndersen Этот вопрос вряд ли поможет будущим посетителям, потому что у будущих посетителей вряд ли возникнут проблемы с «моим» кодом, где «мой» означает код Эндрю Мартина. Будущие посетители, скорее всего, зададут почти тот же вопрос, потому что у них немного другая проблема. Например: stackoverflow.com/questions/6905554/, < href="http://stackoverflow.com/questions/13394014/implementation-of-quick-sort" title="реализация быстрой сортировки"> stackoverflow.com/questions/13394014/ и т. д.   -  person djechlin    schedule 24.02.2013
comment
@djechlin: Я хотел бы отметить, что в FAQ говорится: вы должны задавать только практические вопросы, на которые можно ответить, основанные на реальных проблемах, с которыми вы сталкиваетесь. Я не вижу, где. Как уже было сказано, если вы знаете другой сайт Stack Exchange, на котором я должен опубликовать это, пожалуйста, порекомендуйте его.   -  person Andrew Martin    schedule 24.02.2013
comment
@AndrewMartin см. stackoverflow.com/questions/how-to-ask -› сделайте его актуальным для других. Если это невозможно сделать, то это не подходит для любого StackExchange. Очевидно, что это мое мнение о политике SE, и другие комментаторы не согласны со мной, но в любом случае я призываю вас развивать умение формулировать вопросы, актуальные для других, потому что если ваш вопрос актуален для других, вы сформулировали его таким образом, чтобы вы могли обратиться за помощью к тем, кто сделал то же самое с похожей проблемой.   -  person djechlin    schedule 24.02.2013
comment
@djechlin Вы ошиблись. Совершенно нормально задавать реальную проблему кодирования при переполнении стека. Я думаю, возможно, вы имеете в виду программистов.SE. В любом случае не стесняйтесь спрашивать у Meta, чтобы уточнить.   -  person Thorbjørn Ravn Andersen    schedule 24.02.2013
comment
@ ThorbjørnRavnAndersen, вы поняли - meta.stackexchange.com/questions/168775/   -  person djechlin    schedule 24.02.2013
comment
@djechlin: Будет интересно посмотреть, что они скажут на мете. Я понимаю вашу логику, но если бы это правило применялось строго, я бы сказал, что половина сообщений на SO была бы удалена, так как многие из них чрезвычайно специфичны для очень маленькой вещи.   -  person Andrew Martin    schedule 24.02.2013
comment
@AndrewMartin - см. мета-ссылку в комментарии выше (если вы пропустили)   -  person djechlin    schedule 24.02.2013
comment
@djechlin: я это видел - поэтому и прокомментировал. Спасибо   -  person Andrew Martin    schedule 24.02.2013
comment
@djechlin: Просто чтобы вы знали, что я изменил название вопроса, теперь на него дан ответ. Надеюсь, это будет понятно людям, которые могут искать подобную проблему в будущем.   -  person Andrew Martin    schedule 24.02.2013
comment
@AndrewMartin это огромное улучшение, и если бы я был более сообразительным, я бы поискал это редактирование, а не проголосовал за закрытие. Теперь это кажется более согласующимся с сообщением Кейт в метапотоке, в котором вопрос оказывается потенциально более общим, чем это звучало вначале.   -  person djechlin    schedule 24.02.2013


Ответы (4)


Мне пришлось избавиться от раздела, потому что вам нужны и i, и j. Это должно выглядеть так:

public void quickSort(int[] a, int left, int right) {

    int i = left; // Was -1 
    int j = right; // Was +1
    int pivot = a[left + (right - left) / 2]; // Pivot is the value of the middle index, not the index itself
    while (i <= j) { // Changed terminating condition
        //   i++;  Not needed
        while (a[i] < pivot) { 
            i++;
        }
        //    j++; Not needed
        while (a[j] > pivot) {
            j--;
        }
        if (i <= j) {  // Changed terminating condition
            swap(a, i, j);
            i++;  // You need to progress the indexes after the swap
            j--;
        }
    }

    System.out.println(Arrays.toString(a));
    if (left < j) {  // Changed condition
        quickSort(a, left, j);
    }
    if (i < right) { 
        quickSort(a, i, right); // was i + 1
    }
}

Выход:

[4, 5, 1, 2, 3, 7, 6, 8]
[1, 5, 4, 2, 3, 7, 6, 8]
[1, 3, 2, 4, 5, 7, 6, 8]
[1, 2, 3, 4, 5, 7, 6, 8]
[1, 2, 3, 4, 5, 7, 6, 8]
[1, 2, 3, 4, 5, 6, 7, 8]
[1, 2, 3, 4, 5, 6, 7, 8]
[1, 2, 3, 4, 5, 6, 7, 8]
person user000001    schedule 23.02.2013
comment
Работает отлично. Вы, сэр, настоящая легенда. Я изучу различия между вашим кодом и моим и попытаюсь извлечь из него уроки. - person Andrew Martin; 24.02.2013
comment
Без проблем. Прокомментируйте здесь, если вам нужны какие-либо разъяснения. - person user000001; 24.02.2013
comment
Привет, Можете ли вы объяснить эту строку немного дальше? int pivot = a[левый + (правый - левый) / 2]; - person Oliver Ciappara; 06.05.2015
comment
@OliverCiappara Эта строка означает, что мы выбираем в качестве значения pivot элемент, который находится посередине между left и right. - person user000001; 06.05.2015

хорошо, очевидно, что вы получили принятый ответ. однако я хотел бы упомянуть, что логика вашего раздела может быть реализована проще, только с одним циклом for (или while), без цикла гнезда:

int partition(final int[] a, final int left, final int right) {
        // set the last element as pivot
        final int pivot = a[right];
        int i = left - 1, j = left;
        for (; j < right; j++) 
            if (a[j] < pivot) {
                i++;
                swap(a, i, j);
            }       
        // swap a[i+1] and pivot
        swap(a, i + 1, right);
        return i + 1;
    }

и в вашем методе quickSort:

if (left < index)
  quickSort(a, left, index-1);
if (index < right)
  quickSort(a, index + 1, right);

Надеюсь, поможет

person Kent    schedule 23.02.2013
comment
Все это помогает. Я изучу это и попытаюсь извлечь из этого уроки. Спасибо - person Andrew Martin; 24.02.2013
comment
@AndrewMartin , j = left - он там - person Bernhard Barker; 24.02.2013
comment
@AndrewMartin нет, мне не нужен int j, он был объявлен перед циклом for. читай коды, чувак. - person Kent; 24.02.2013
comment
Извинения - я понятия не имел, что вы можете объявить цикл for таким образом. Извините за глупое предложение и еще раз спасибо за ваш ответ. - person Andrew Martin; 24.02.2013

int pivot = a[0];

должно быть

int pivot = a[left];

Что, с заменой swap (a, i, j); на swap (a, i--, j++); и все работает нормально.

Почему вышеуказанное изменение:

Сводка должна быть первым элементом в диапазоне, а не первым элементом.

И не должно быть в этой середине, как здесь:

int pivot = a[(left + right) / 2];

Неважно, какой элемент вы хотите сделать опорным, проще всего всегда менять местами выбранный элемент с первым элементом и продолжать как обычно. Могут быть и другие способы сделать что-то, но они, вероятно, будут более сложными.

Итак, вы можете сказать:

swap(left, (left + right) / 2);
int pivot = a[left];

который очень похож на приведенный выше (не идентичен), просто с ним намного проще иметь дело.

person Bernhard Barker    schedule 23.02.2013
comment
Кто-то еще рекомендовал pivot = a[left + (right - left)/2], что, я думаю, соответствует медиане трех правил. Тем не менее, это то, что я действительно намеревался сделать в первую очередь, и, конечно же, полностью ошибся. Спасибо за это. - person Andrew Martin; 24.02.2013
comment
@AndrewMartin a[left + (right - left) / 2] - это не медиана 3 (даже не близко). Для медианы 3 найдите медиану первого, среднего и последнего элементов и поменяйте местами этот элемент с элементом в первой позиции, затем продолжите как обычно. - person Bernhard Barker; 24.02.2013
comment
Просто погуглил медиану из трех и понял, как я ошибался. Спасибо - person Andrew Martin; 24.02.2013

Я думаю, что метод partition должен возвращать j вместо i.

Еще одна проблема в вашем коде - ваши условия остановки:

вместо двух отдельных условий я бы изменил его на одно условие:

if (left < right)  {

  do partition & recursive calls

}

Полный код:

public void quickSort(int[] a, int left, int right) {
    if (left < right) {
      int index = partition(a, left, right);
      quickSort(a, left, index);
      quickSort(a, index + 1, right);
    }
}

private int partition (int[] a, int left, int right) {
    int i = left - 1;
    int j = right + 1;
    int pivot = a[(left+right)/2];

    while (i < j) {

        i++;

        while (a[i] < pivot)
            i++;

        j--;

        while (a[j] > pivot)
            j--;

        if (i < j)
            swap (a, i, j);
    }
    return j;
}   

private void swap (int[] a, int i, int j) {
    int temp = a[i];
    a[i] = a[j];
    a[j] = temp;
}
person Eran    schedule 23.02.2013
comment
Это может понадобиться, но, к сожалению, это возвращает ошибку StackOverflow - вероятно, из-за ошибки в другом месте. - person Andrew Martin; 24.02.2013
comment
Вы должны применить это изменение к исходному коду. Ваше изменение в edit2 неверно. Вы должны изменить i и j перед циклом, поскольку они инициализируются значениями, находящимися за пределами диапазона массива, который вы разбиваете. - person Eran; 24.02.2013
comment
Я изменил свою личную копию до edit2 и изменил return int на j, однако он все еще генерирует ошибку StackOverflow. - person Andrew Martin; 24.02.2013
comment
У вас есть отладчик? StackOverflow означает, что метод разделения не разделяет ввод (т. е. один из разделов пуст), что вызывает бесконечную рекурсию. - person Eran; 24.02.2013
comment
Да, сейчас я прохожу каждый шаг в отладчике Eclipse (используя ваши правки). Просто не могу понять, как остановить ошибку! - person Andrew Martin; 24.02.2013
comment
Я надеюсь, что в первом редактировании вы хотели сказать pivot = a[(left + right) / 2], а не pivot = (left + right) / 2 - person Eran; 24.02.2013
comment
На самом деле, я заметил это, но уже изменил кое-какой другой код. Удаление этих изменений и выполнение этого также исправили это. Спасибо за ваш ответ, Эран, это было очень полезно. - person Andrew Martin; 24.02.2013