Ошибка сегментации быстрой сортировки

Я реализовал алгоритм быстрой сортировки на C и правильно работает с векторами, содержащими менее 30 000 элементов. Но у него ошибка сегментации в соответствии

swap(&vet[sup], &vet[(sup-inf+1)/2]);

с большими векторами. Вот полный алгоритм:

void swap(int* a, int* b){
    int temp = *a;
    *a = *b;
    *b = temp;
} 
int partition(int inf, int sup, int vet[]){
    while (inf<sup){
        while (vet[inf]<=vet[sup] && inf<sup){inf++;}
        while (vet[inf]<=vet[sup] && inf<sup){sup--;}
        if (inf<sup){swap(&vet[inf], &vet[sup]);}
    }
    return inf;
}
void median (int inf, int sup, int vet[]){ //select the median of the first, last and midle value of the vector
    if (vet[inf]>vet[(sup-inf+1)/2])
        swap(&vet[inf], &vet[(sup-inf+1/2)]);
    if (vet[sup]>vet[(sup-inf+1)/2])
        swap(&vet[sup], &vet[(sup-inf+1)/2]);  
    else if (vet[sup]<vet[inf])
        swap(&vet[sup], &vet[inf]);    
}
void quicksort(int inf, int sup, int vet[]){
    if (inf<sup){
        median(inf, sup, vet);    
        int piv=partition(inf, sup, vet);
        quicksort(inf, piv-1, vet);
        quicksort(piv+1, sup, vet);
    }
}

Я пытался написать быструю сортировку с минимальным объявлением переменных, но все равно получал ошибку сегментации. Я не знаю, является ли это переполнением стека или проблемой с указателем, которая возникает только с большими векторами.

Извините за мой плохой английский, я все еще учусь

Что вам говорит отладчик? sup за пределами поля? Как оно вышло за пределы? vet недействителен? Как оно стало недействительным? sup/2+1 за пределами поля? Как оно вышло за пределы? (Я бы присмотрел sup/2+1.)

Raymond Chen 25.05.2024 16:13

Объясните, пожалуйста, ближайшей резиновой утке, зачем вам sup/2+1.

n. m. could be an AI 25.05.2024 16:28

При векторе из 50 000 элементов возникает ошибка сегментации с sup=9996. sup/2+1 — это средний элемент вектора, он необходим, потому что ось — это медиана первого, последнего и среднего элемента.

Thallysson 25.05.2024 16:41

«Sup/2+1 — это средний элемент вектора». Правда? Какого вектора? Пожалуйста, скажите об этом очень четко.

n. m. could be an AI 25.05.2024 16:44

Вектор результата каждого вызова быстрой сортировки

Thallysson 25.05.2024 16:47

Итак, вы вызываете quicksort с помощью inf=10000 и sup=10020. Середина какого вектора sup/2+1?

n. m. could be an AI 25.05.2024 16:50

Объясните далее, что именно делает median. В комментарии написано, что он "выбирает медиану первого, последнего и среднего значения". Что он делает с выбранным значением? Как это влияет на последующие операции?

n. m. could be an AI 25.05.2024 17:09

Кстати, если inf==sup, то (inf+sup)/2+1 посередине чего?

n. m. could be an AI 25.05.2024 17:15

Медиана работает, выбирая медианный элемент и заменяя его последним. Раздел работает, выбирая последний элемент в качестве опорного. inf==sup никогда не появится из-за if (inf<sup) в начале быстрой сортировки.

Thallysson 25.05.2024 17:29

«Раздел работает, выбирая последний элемент в качестве опорного». Вы действительно это проверяли? Добавьте printf ("Pivot index %d between %d and %d\n", piv, inf, sup);, запустите и посмотрите, что произойдет.

n. m. could be an AI 25.05.2024 17:38

Я уже делал это раньше, и да, все работает правильно. Код отлично работает с векторами, содержащими 30 000 и менее элементов. Проблема в том, что большие векторы

Thallysson 25.05.2024 17:52
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
11
78
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В вашем коде есть несколько проблем:

  • ваш стиль кодирования трудно читать и отлаживать: например, если втиснуть тест и код замены в одну строку, невозможно определить, какая часть вызывает сбой. Пишите по одному оператору в строке и разбивайте управляющие операторы на несколько строк.

  • трюк с xor подстановки кода устарел: он загадочный, потенциально неопределенный и неэффективный. Хуже того: он не работает, если a и b указывают на одно и то же место. Используйте более простой код:

    void swap(int *a, int *b) {
        int temp = *a;
        *a = *b;
        *b = temp;
    }
    
  • код раздела неверен: необходимо сохранить начальное значение sup, чтобы все сравнения выполнялись со значением опорной точки, и вам необходимо заменить опорную точку на окончательный индекс inf:

    int partition(int inf, int sup, int vet[]) {
        int pivot = sup;
        while (inf < sup) {
            while (vet[inf] <= vet[pivot] && inf < sup) {
                inf++;
            }
            while (vet[sup] >= vet[pivot] && inf < sup) {
                sup--;
            }
            swap(&vet[inf], &vet[sup]);
        }
        swap(&vet[inf], &vet[pivot]);
        return inf;
    }
    
  • это возможно, хотя и маловероятно, что набор данных, предоставленный вашему коду, будет создан так, чтобы вызвать переполнение стека. Возможно, вам захочется изучить классическую статью Дуга Макилроя: убийственный противник быстрой сортировки, в которой объясняется, как построить такой вектор. Простую контрмеру, которая предотвращает потенциальное переполнение стека, но не уменьшает квадратичную временную сложность, легко реализовать: рекурсия только на меньшей половине и цикл на большей половине.

  • более вероятная причина сбоя в том, что вы не выбираете средний элемент в срезе с помощью sup/2+1, вам следует написать (inf + (sup - inf + 1) / 2, следовательно, опорная точка выбрана неправильно, поэтому алгоритм может рекурсировать слишком много раз даже для случайного вектора.

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

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

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int partition(int inf, int sup, int vet[]) {
    int pivot = sup;
    while (inf < sup) {
        while (vet[inf] <= vet[pivot] && inf < sup) {
            inf++;
        }
        while (vet[sup] >= vet[pivot] && inf < sup) {
            sup--;
        }
        swap(&vet[inf], &vet[sup]);
    }
    swap(&vet[inf], &vet[pivot]);
    return inf;
}

// Select the median of the first, last and middle value of the vector
// and move it to vet[sup]
void median(int inf, int sup, int vet[]) {
    int m = inf + (sup - inf + 1) / 2;
    // set vet[m] = min(vet[inf], vet[m])
    if (vet[inf] > vet[m]) {
        swap(&vet[inf], &vet[m]);
    }
    if (vet[sup] > vet[m]) {
        // set vet[sup] = min(vet[sup], min(vet[m], vet[inf]))
        swap(&vet[sup], &vet[m]);
    } else
    if (vet[sup] < vet[inf]) {
        // set vet[sup] = min(vet[sup], max(vet[m], vet[inf]))
        swap(&vet[sup], &vet[inf]);
    }    
}

void quicksort(int inf, int sup, int vet[]) {
    while (inf < sup) {
        median(inf, sup, vet);    
        int piv = partition(inf, sup, vet);
        // recurse on the smaller half, loop on the other half
        if (piv - inf < sup - piv) {
            quicksort(inf, piv - 1, vet);
            inf = piv + 1;
        } else {
            quicksort(piv + 1, sup, vet);
            sup = piv - 1;
        }
    }
}

int main(int argc, char *argv[]) {
    size_t n = argc < 2 ? 30000 : strtol(argv[1], NULL, 0);
    int *array = calloc(n, sizeof(*array));
    int res = 0;

    if (array == NULL) {
        fprintf(stderr, "cannot allocate memory for %zu elements\n", n);
        return 1;
    }

    srand(time(NULL));
    for (size_t i = 0; i < n; i++) {
        array[i] = rand();
    }

    quicksort(0, n - 1, array);
    for (size_t i = 1; i < n; i++) {
        if (array[i] < array[i - 1]) {
            printf("sort error at %zu: %d > %d\n",
                   i, array[i - 1], array[i]);
            res = 1;
            break;
        }
    }
    free(array);
    return res;
}

Спасибо за ответ, я ценю это. Но я не смог это проверить, потому что тестировать слишком дорого. При 30000 элементах он выполняется больше минуты и даже не завершается, старый алгоритм делает это за 2,5 секунды.

Thallysson 25.05.2024 17:59

Но я редактирую код, чтобы облегчить чтение

Thallysson 25.05.2024 18:25

@Thallysson: извините, у меня была ошибка: sup = piv; должно было быть sup = piv - 1;, и я пропустил еще одну ошибку в вашей функции partition, вызывающую огромную неэффективность. Я дополнил ответ простым тестом, использующим случайный массив. Эта версия сортирует 100 миллионов случайных целых чисел за 1,4 секунды на моем ноутбуке M2.

chqrlie 25.05.2024 22:11

Другие вопросы по теме