Можно ли реализовать быструю сортировку в C без стека и рекурсии - PullRequest
1 голос
/ 05 марта 2019

Я нашел этот пост Как выполнить итеративную быструю сортировку без использования стека в c? , но предложенный ответ действительно использует встроенный массив стека!(Допускается только постоянное количество дополнительного пространства)

Ответы [ 5 ]

1 голос
/ 06 марта 2019

Код на странице в ссылка делает смелое утверждение:

STACK Моя реализация не использует стек для хранения данных ...

Тем не менее, определение функции имеет много переменных с автоматическим хранением, среди них 2 массива с 1000 записями, которые в конечном итоге будут использовать фиксированный, но существенный объем стекового пространства:

//  quickSort
//
//  This public-domain C implementation by Darel Rex Finley.
//
//  * Returns YES if sort was successful, or NO if the nested
//    pivots went too deep, in which case your array will have
//    been re-ordered, but probably not sorted correctly.
//
//  * This function assumes it is called with valid parameters.
//
//  * Example calls:
//    quickSort(&myArray[0],5); // sorts elements 0, 1, 2, 3, and 4
//    quickSort(&myArray[3],5); // sorts elements 3, 4, 5, 6, and 7

bool quickSort(int *arr, int elements) {

  #define  MAX_LEVELS  1000

  int  piv, beg[MAX_LEVELS], end[MAX_LEVELS], i=0, L, R ;

  beg[0]=0; end[0]=elements;
  while (i>=0) {
    L=beg[i]; R=end[i]-1;
    if (L<R) {
      piv=arr[L]; if (i==MAX_LEVELS-1) return NO;
      while (L<R) {
        while (arr[R]>=piv && L<R) R--; if (L<R) arr[L++]=arr[R];
        while (arr[L]<=piv && L<R) L++; if (L<R) arr[R--]=arr[L]; }
      arr[L]=piv; beg[i+1]=L+1; end[i+1]=end[i]; end[i++]=L; }
    else {
      i--; }}
  return YES; }

Стиль отступов очень запутанный. Вот переформатированная версия:

#define MAX_LEVELS  1000

bool quickSort(int *arr, int elements) {
    int piv, beg[MAX_LEVELS], end[MAX_LEVELS], i = 0, L, R;

    beg[0] = 0;
    end[0] = elements;
    while (i >= 0) {
        L = beg[i];
        R = end[i] - 1;
        if (L < R) {
            piv = arr[L];
            if (i == MAX_LEVELS - 1)
                return NO;
            while (L < R) {
                while (arr[R] >= piv && L < R)
                    R--;
                if (L < R)
                    arr[L++] = arr[R];
                while (arr[L] <= piv && L < R)
                    L++;
                if (L < R)
                    arr[R--] = arr[L];
            }
            arr[L] = piv;
            beg[i + 1] = L + 1;
            end[i + 1] = end[i];
            end[i++] = L;
        } else {
            i--;
        }
    }
    return YES;
}

Обратите внимание, что 1000 является большим, но недостаточным для патологических случаев на умеренно больших массивах, которые уже отсортированы. Функция возвращает NO для таких массивов только с размером 1000, что недопустимо.

Гораздо меньшего значения будет достаточно для улучшенной версии алгоритма, в которой больший диапазон помещается в массив, а цикл повторяется в меньшем диапазоне. Это гарантирует, что массив из N записей может обрабатывать набор из 2 N записей. Он по-прежнему имеет квадратичную сложность по времени для отсортированных массивов, но по крайней мере может отсортировать массивы всех возможных размеров.

Вот модифицированная и инструментальная версия:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define MAX_LEVELS  64

int quickSort(int *arr, size_t elements) {
    size_t beg[MAX_LEVELS], end[MAX_LEVELS], L, R;
    int i = 0;

    beg[0] = 0;
    end[0] = elements;
    while (i >= 0) {
        L = beg[i];
        R = end[i];
        if (L + 1 < R--) {
            int piv = arr[L];
            if (i == MAX_LEVELS - 1)
                return -1;
            while (L < R) {
                while (arr[R] >= piv && L < R)
                    R--;
                if (L < R)
                    arr[L++] = arr[R];
                while (arr[L] <= piv && L < R)
                    L++;
                if (L < R)
                    arr[R--] = arr[L];
            }
            arr[L] = piv;
            if (L - beg[i] > end[i] - R) { 
                beg[i + 1] = L + 1;
                end[i + 1] = end[i];
                end[i++] = L;
            } else {
                beg[i + 1] = beg[i];
                end[i + 1] = L;
                beg[i++] = L + 1;
            }
        } else {
            i--;
        }
    }
    return 0;
}

int testsort(int *a, size_t size, const char *desc) {
    clock_t t = clock();
    size_t i;

    if (quickSort(a, size)) {
        printf("%s: quickSort failure\n", desc);
        return 1;
    }
    for (i = 1; i < size; i++) {
        if (a[i - 1] > a[i]) {
            printf("%s: sorting error: a[%zu]=%d > a[%zu]=%d\n",
                   desc, i - 1, a[i - 1], i, a[i]);
            return 2;
        }
    }
    t = clock() - t;
    printf("%s: %zu elements sorted in %.3fms\n",
           desc, size, t * 1000.0 / CLOCKS_PER_SEC);
    return 0;
}

int main(int argc, char *argv[]) {
    size_t i, size = argc > 1 ? strtoull(argv[1], NULL, 0) : 1000;
    int *a = malloc(sizeof(*a) * size);
    if (a != NULL) {
        for (i = 0; i < size; i++)
            a[i] = rand();
        testsort(a, size, "random");
        for (i = 0; i < size; i++)
            a[i] = i;
        testsort(a, size, "sorted");
        for (i = 0; i < size; i++)
            a[i] = size - i;
        testsort(a, size, "reverse sorted");
        for (i = 0; i < size; i++)
            a[i] = 0;
        testsort(a, size, "constant");
        free(a);
    }
    return 0;
}

Выход:

random: 100000 elements sorted in 7.379ms
sorted: 100000 elements sorted in 2799.752ms
reverse sorted: 100000 elements sorted in 2768.844ms
constant: 100000 elements sorted in 2786.612ms

Вот немного модифицированная версия, более устойчивая к патологическим случаям:

#define MAX_LEVELS  48

int quickSort(int *arr, size_t elements) {
    size_t beg[MAX_LEVELS], end[MAX_LEVELS], L, R;
    int i = 0;

    beg[0] = 0;
    end[0] = elements;
    while (i >= 0) {
        L = beg[i];
        R = end[i];
        if (R - L > 1) {
            size_t M = L + ((R - L) >> 1);
            int piv = arr[M];
            arr[M] = arr[L];

            if (i == MAX_LEVELS - 1)
                return -1;
            R--;
            while (L < R) {
                while (arr[R] >= piv && L < R)
                    R--;
                if (L < R)
                    arr[L++] = arr[R];
                while (arr[L] <= piv && L < R)
                    L++;
                if (L < R)
                    arr[R--] = arr[L];
            }
            arr[L] = piv;
            M = L + 1;
            while (L > beg[i] && arr[L - 1] == piv)
                L--;
            while (M < end[i] && arr[M] == piv)
                M++;
            if (L - beg[i] > end[i] - M) {
                beg[i + 1] = M;
                end[i + 1] = end[i];
                end[i++] = L;
            } else {
                beg[i + 1] = beg[i];
                end[i + 1] = L;
                beg[i++] = M;
            }
        } else {
            i--;
        }
    }
    return 0;
}

Выход:

random: 10000000 elements sorted in 963.973ms
sorted: 10000000 elements sorted in 167.621ms
reverse sorted: 10000000 elements sorted in 167.375ms
constant: 10000000 elements sorted in 9.335ms

В заключение:

  • да, быстрая сортировка может быть реализована без рекурсии,
  • нет, это невозможно реализовать без локального автоматического хранения,
  • да только постоянный объем дополнительного пространства необходим, но только потому, что мы живем, это маленький мир, где максимальный размер массива ограничен доступной памятью. Размер 64 для локальных объектов обрабатывает массивы, превышающие размер Интернета, намного больше, чем существующие 64-битные системы.
0 голосов
/ 06 марта 2019

По-видимому, можно реализовать нерекурсивную быструю сортировку только с постоянным объемом дополнительного пространства, как указано здесь . Это основано на работе Седжвика для нерекурсивной формулировки быстрой сортировки. Вместо сохранения граничных значений (низкого и высокого) он, по сути, выполняет линейное сканирование для определения этих границ.

0 голосов
/ 05 марта 2019

Быстрая сортировка по определению является алгоритмом поиска «разделяй и властвуй», идея в том, что вы разбиваете данный массив на более мелкие разделы. Таким образом, вы делите проблему на подзадачи, которые легче решить. При использовании Quicksort без рекурсии вам нужна какая-то структура для хранения разделов, которые вы не используете в то время. Вот почему ответ сообщения использует массив, чтобы сделать быструю сортировку нерекурсивной.

0 голосов
/ 05 марта 2019

Может ли быть реализована быстрая сортировка в C без стека и рекурсии?

Быстрая сортировка требует двух путей для каждого нетривиального разбиения: новое разбиение каждого (под) раздела. Информация о предыдущем разбиении (границы одного из результирующих разделов) должна переноситься на каждое новое разбиение. Вопрос в том, где эта информация живет? В частности, где хранится информация об одном разделе, в то время как программа работает над другим?

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

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

Если вы хотите сходить с ума, вы можете даже вкладывать итерации вместо повторяющихся. Это наложит жесткую верхнюю границу на размер массивов, которые можно обрабатывать, но не настолько узко, как вы думаете. С некоторой тщательностью и несколькими хитростями вы можете обрабатывать массивы из миллиардов элементов с помощью гнезда с 25 циклами. Такое глубокое гнездо было бы безобразным и безумным, но, тем не менее, мыслимым. Человек мог написать это от руки. И в этом случае ряд областей вложенных циклов с их переменными в виде блоков служит эквивалентом стека.

Таким образом, ответ зависит от того, что именно вы подразумеваете под "без стека":

  • да, вместо этого вы можете использовать очередь, хотя она должна иметь примерно такую ​​же емкость, что и элементы для сортировки;
  • да, вы можете использовать массив или другое хранилище последовательных данных для эмуляции формального стека или очереди;
  • да, вы можете закодировать подходящий эквивалент стека непосредственно в структуру вашей программы;
  • да, вы, вероятно, можете придумать другие, более эзотерические версии стеков и очередей;
  • но нет, вы не можете выполнить быструю сортировку, если что-то не выполняет многоуровневую роль хранения данных, для которой традиционно используется стек или эквивалент в стеке.
0 голосов
/ 05 марта 2019

Ну, это возможно, потому что я реализовал быструю сортировку в Fortran IV (это было давно, до того, как язык поддерживал рекурсию - и это было для пари). Однако вам нужно где-то (большой массив) запомнить ваше состояние, когда вы выполняете отдельные части работы.

Это намного проще рекурсивно ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...