Ошибка сегментации при реализации метода быстрой сортировки Хоара

Вопросы и ответы

Согласно условиям задачи, требуется отсортировать массив структур. Данные должны быть отсортированы следующим образом: при сравнении двух участников тот, кто решил больше задач, будет выше. Если количество решённых задач одинаково, то сначала идёт участник с меньшим штрафом. Если штрафы также равны, то первым будет тот, чей логин стоит раньше в лексикографическом порядке. Сортировка должна выполняться по методу Хоара. Алгоритм работает, если я сравниваю только числа, но при использовании компаратора для некоторых данных возникает ошибка “Segmentation fault (core dumped)”. Я не могу понять, что я делаю не так?
Мой код показан ниже:

// https://ru.wikipedia.org/wiki/Быстрая_сортировка

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

struct Participants {
    std::string login;
    int complited_tasks;
    int fine;
    Participants(std::string l, int c, int f) {
        login = l;
        complited_tasks = c;
        fine = f;
    }
};

void print_arr(std::vector<Participants> a);
void quick_sort(std::vector<Participants>& arr, int indx_left, int indx_right);
int partition(std::vector<int>& arr, int indx_left, int indx_right);
void get_participants(std::vector<Participants>& p);
bool is_better(Participants p1, Participants p2);

void get_test_data(std::vector<Participants>& p);

int main() {
    std::vector<Participants> p;

    // get_participants(p);
    get_test_data(p);

    quick_sort(p, 0, p.size() - 1);

    print_arr(p);

    return 0;
}

// создаёт вектор данных, при которых возникает ошибка
void get_test_data(std::vector<Participants>& p) {
    p.push_back(Participants("tufhdbi", 76, 58));
    p.push_back(Participants("rqyoazgbmv", 59, 78));
    p.push_back(Participants("qvgtrlkmyrm", 35, 27));
    p.push_back(Participants("tgcytmfpj", 70, 27));
    p.push_back(Participants("xvf", 84, 19));
    p.push_back(Participants("jzpnpgpcqbsmczrgvsu", 30, 3));
    p.push_back(Participants("evjphqnevjqakze", 92, 15));
    p.push_back(Participants("wwzwv", 87, 8));
    p.push_back(Participants("tfpiqpwmkkduhcupp", 1, 82));
    p.push_back(Participants("tzamkyqadmybky", 5, 81));
    p.push_back(Participants("amotrxgba", 0, 6));
    p.push_back(Participants("easfsifbzkfezn", 100, 28));
    p.push_back(Participants("kivdiy", 70, 47));
}

// функция чтения данных
void get_participants(std::vector<Participants>& p) {
    std::string inp;

    std::getline(std::cin, inp);
    int n = std::stoi(inp);

    for (int i = 0; i < n; i++) {

        std::vector<std::string> split_inp;
        std::string t;

        std::getline(std::cin, inp);

        for (unsigned int j = 0; j < inp.size(); j++) {
            if (inp[j] == ' ') {
                split_inp.push_back(t);
                t.clear();
                continue;
            }
            t += inp[j];
        }
        split_inp.push_back(t);
        p.push_back(Participants(split_inp[0], 
                                std::stoi(split_inp[1]), 
                                std::stoi(split_inp[2])));
    }
}

void print_arr(std::vector<Participants> a) {
    for (unsigned int i = 0; i < a.size(); i++) {
        std::cout << a[i].login << '\n';
    }
}

int partition(std::vector<Participants>& arr, int indx_left, int indx_right) {
    Participants pivot = arr[rand() % arr.size()];
    int i = indx_left - 1, j = indx_right + 1;

    while (true) {
        do {
            i++;
        } while (is_better(arr[i], pivot));

        do {
            j--;
        } while (!is_better(arr[j], pivot));

        if (i >= j)
            return j;

        std::swap(arr[i], arr[j]);
    }
}

// функция сравнения двух элементов 
// возвращает true, если первый элемент лучше второго
bool is_better(Participants p1, Participants p2) {
    bool p1_better = false;

    if (p1.complited_tasks > p2.complited_tasks) {
        p1_better = true;
    }

    else if (p1.complited_tasks == p2.complited_tasks) {
        if (p1.fine < p2.fine) 
            p1_better = true;

        else if (p1.fine == p2.fine) {
            if (std::lexicographical_compare(p1.login.begin(), p1.login.end(),
                                            p2.login.begin(), p2.login.end()))
                p1_better = true;
        }
    }
    return p1_better;
}

void quick_sort(std::vector<Participants>& arr, int indx_left, int indx_right) {
    if (indx_left < indx_right) {
        int stop_indx = partition(arr, indx_left, indx_right);
        quick_sort(arr, indx_left, stop_indx);
        quick_sort(arr, stop_indx + 1, indx_right);
    }
    return;
}

С входными данными в виде

input:        
5
alla 4 100  
gena 6 1000
gosha 2 90 
rita 2 90
timofey 4 80

output:
gena
timofey
alla
gosha
rita

или

input:
5
alla 0 0        
gena 0 0       
gosha 0 0       
rita 0 0        
timofey 0 0

output:
alla
gena
gosha
rita
timofey  

Всё работает корректно, но возникает ошибка при этом тесте:

input:                        
13
tufhdbi 76 58        
rqyoazgbmv 59 78      
qvgtrlkmyrm 35 27     
tgcytmfpj 70 27        
xvf 84 19                
jzpnpgpcqbsmczrgvsu 30 3 
evjphqnevjqakze 92 15  
wwzwv 87 8            
tfpiqpwmkkduhcupp 1 82  
tzamkyqadmybky 5 81
amotrxgba 0 6   
easfsifbzkfezn 100 28
kivdiy 70 47

output: 
easfsifbzkfezn
evjphqnevjqakze
wwzwv
xvf
tufhdbi
tgcytmfpj
kivdiy
rqyoazgbmv
qvgtrlkmyrm
jzpnpgpcqbsmczrgvsu
tzamkyqadmybky
tfpiqpwmkkduhcupp
amotrxgba

Как i, так и j могут выйти за пределы вектора в этих циклах:

        do {
            i++;
        } while (is_better(arr[i], pivot));

        do {
            j--;
        } while (!is_better(arr[j], pivot));

Когда это происходит, возникает неопределённое поведение — возможно, сегментация.

Причина, по которой i или j могут выйти за границы, двояка:

  1. Вы выбрали опорный элемент случайным образом из всего массива, в то время как вам нужно выбрать его из текущей партиции. В результате вы можете выбрать опорный элемент, который больше, чем все элементы в текущей партиции, или меньше, чем все они. В алгоритме Хоара очень важно, чтобы циклы встретили опорный элемент в какой-то момент. Если это не гарантировано, алгоритм может сломаться.

  2. Ваш второй внутренний цикл продолжается, когда arr[j] равен опорному элементу, но в этом случае он должен завершиться. Если вы позволите циклу продолжаться в этом случае, вы рискуете, что условие while никогда не станет истинным. Для алгоритма жизненно важно, чтобы цикл завершался, когда встречается опорный элемент.

Вот ваша функция partition с минимальными изменениями, чтобы исправить эти две проблемы:

int partition(std::vector<Participants>& arr, int indx_left, int indx_right) {
    // Опорный элемент должен выбираться из текущей партиции:
    Participants pivot = arr[indx_left + rand() % (indx_right + 1 - indx_left)];

    int i = indx_left - 1, j = indx_right + 1;

    while (true) {
        do {
            i++;
        } while (is_better(arr[i], pivot));

        do {
            j--;
        // инвертируем аргументы и не отрицательно, чтобы цикл завершился, когда равен 
        } while (is_better(pivot, arr[j])); 

        if (i >= j)
            return j;

        std::swap(arr[i], arr[j]);
    }
}

Ответ

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

Проблема

Ошибка "Segmentation fault (core dumped)" возникает из-за выхода за границы векторов. В вашем коде переменные индексов i и j могут выйти за пределы массива, если не будет должной проверки. Это связано с тем, что вы выбираете опорный элемент случайным образом из всего массива вместо текущего подмассива, и условия цикла не гарантируют, что i и j останутся в пределах допустимых значений.

Решение

Для устранения этой проблемы вам нужно изменить два момента в функции partition:

  1. Выбор опорного элемента нужно производить из текущего подмассива, а не всего массива.
  2. Внутренний цикл для j должен выходить, если элемент равен опорному, чтобы предотвратить зацикливание.

Исправленный код

Вот исправленный вариант вашей функции partition:

int partition(std::vector<Participants>& arr, int indx_left, int indx_right) {
    // Опорный элемент выбирается из текущего подмассива
    Participants pivot = arr[indx_left + rand() % (indx_right + 1 - indx_left)];

    int i = indx_left - 1, j = indx_right + 1;

    while (true) {
        // Ищем элемент, который больше опорного
        do {
            i++;
        } while (is_better(arr[i], pivot));

        // Ищем элемент, который меньше опорного
        do {
            j--;
        } while (is_better(pivot, arr[j])); 

        // Если индексы пересеклись, возвращаем j
        if (i >= j)
            return j;

        // Меняем местами элементы
        std::swap(arr[i], arr[j]);
    }
}

Дополнительные рекомендации

  1. Проверка границ массива: Каждую итерацию цикла не забудьте проверять, не вышли ли i и j за границы. Это можно сделать с помощью дополнительного условия, хотя в нашем случае после исправления основной проблемы это уже не должно происходить.

  2. Случайный выбор опорного элемента: Использование случайного выбора опорного элемента может привести к неоптимальным случаям. Тем не менее, в вашем случае это было применимо.

  3. Логика сравнения: Логика в функции is_better тоже может быть улучшена, например, с помощью кода:
bool is_better(const Participants& p1, const Participants& p2) {
    return (p1.complited_tasks > p2.complited_tasks) ||
           (p1.complited_tasks == p2.complited_tasks && p1.fine < p2.fine) ||
           (p1.complited_tasks == p2.complited_tasks && p1.fine == p2.fine && p1.login < p2.login);
}

Заключение

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

Оцените материал
Добавить комментарий

Капча загружается...