C++ - один из самых популярных языков программирования в мире. Отсортировать данные в C++ можно несколькими способами. В этой статье мы подробно разберем две функции: стандартную библиотечную qsort() и более современную std::sort. Узнайте, как ими пользоваться, сравните производительность и выберите оптимальный вариант для ваших задач.
Qsort - стандартная сортировка в C
Функция qsort() была добавлена в стандартную библиотеку C еще в далеком 1989 году. Она реализует так называемую "быструю сортировку", хотя на самом деле это необязательно именно алгоритм quicksort. Реализация зависит от компилятора.
Главное преимущество qsort - ее универсальность. С помощью передачи указателя на сравнительную функцию, qsort может отсортировать практически любые данные. Это объясняет ее популярность и долгую "жизнь" в C.
Давайте посмотрим, как вызывается qsort:
qsort(void *array, size_t count, size_t size, int (*compar)(const void *, const void*));
Здесь:
- array - указатель на сортируемый массив
- count - количество элементов в массиве
- size - размер одного элемента в байтах
- compar - указатель на сравнительную функцию
Давайте посмотрим, как отсортировать простой массив целых чисел:
int values[] = { 5, 3, 6, 2, 4 }; int compare(const void* a, const void* b) { return ( *(int*)a - *(int*)b ); } qsort(values, 5, sizeof(int), compare);
В сравнительной функции мы приводим указатели к int, разименовываем их и вычитаем. Таким образом числа сортируются по возрастанию. Чтобы отсортировать строки, нам понадобится функция strcmp():
char *names[] = {"John", "Bob", "Alice"}; int compare(const void* a, const void* b) { return strcmp(*(char**)a, *(char**)b); } qsort(names, 3, sizeof(char*), compare);
А для структур данных можно создать специальную сравнительную функцию, которая будет сравнивать нужные поля:
// структура struct Person { char name[100]; int age; }; // массив структур Person people[] = {...}; // сравнение по полю age int compareByAge(const void* a, const void* b) { Person *p1 = (Person *)a; Person *p2 = (Person *)b; return (p1->age - p2->age); } // сортировка qsort(people, 5, sizeof(Person), compareByAge);
Как видно из примеров, qsort довольно проста в использовании. Но у нее есть и недостатки:
- Невозможность прервать выполнение посередине
- Сложность анализа производительности
- Нестабильность порядка одинаковых элементов
По скорости qsort обычно уступает специализированным алгоритмам вроде timsort или introsort, которые используются в более современных библиотеках. Но с оптимизациями, такими как вынос сравнений в отдельную функцию, ее можно значительно ускорить.
Сортировка в С++ с помощью std::sort
Функция std::sort появилась в стандартной библиотеке C++ (STL) в 1994 году. В отличие от универсальной qsort, она предназначена специально для работы с контейнерами в C++.
По скорости std::sort на порядок превосходит qsort на эквивалентных данных благодаря более эффективным алгоритмам и оптимизациям компилятора.
Давайте посмотрим на прототипы std::sort:
// для массивов template< class RandomIt > void sort( RandomIt first, RandomIt last ); // для контейнеров template< class Container > void sort( Container& c );
Синтаксис гораздо проще, чем у qsort. Для массива нужно передать только итераторы на начало и конец, а для контейнера - сам контейнер:
std::vector<int> vec = {5, 3, 6, 2, 4}; std::sort(vec.begin(), vec.end()); std::list<Person> people = {...}; std::sort(people);
По умолчанию элементы сортируются по возрастанию. Чтобы изменить порядок, можно передать функцию сравнения:
bool compareDesc(int a, int b) { return a > b; } std::sort(vec.begin(), vec.end(), compareDesc);
Вот основные преимущества std::sort перед qsort:
- Более высокая производительность на эквивалентных данных
- Простота использования
- Стабильность порядка одинаковых элементов
- Возможность дополнительных оптимизаций (например, параллельная сортировка)
Но есть и недостаток - std::sort работает только с контейнерами С++, в отличие от универсальной qsort.
Таким образом, при работе с контейнерами в С++ предпочтительно использовать std::sort. А для сортировки произвольных массивов по-прежнему актуальна старая добрая qsort().
c sort
c sort
c sort
функция sort c
Производительность qsort
Как уже упоминалось, по скорости работы qsort обычно уступает более современным алгоритмам сортировки. Однако с помощью нескольких оптимизаций можно значительно улучшить ее производительность.
Во-первых, очень важно миниминмаксиминмизировать количество сравнений внутри цикла сортировки. Для этого сравнение лучше вынести в отдельную функцию. Таким образом достигается эффект инлайнинга, и сравнения выполняются значительно быстрее.
Во-вторых, если позволяет тип данных, имеет смысл использовать алгоритмы, основанные на целочисленнй арифметике. Например, для типа int вместо вызова strcmp() можно просто вычитать числа.
В-третьих, для массивов большого размера важно использовать рекурсивную qsort. Рекурсия позволяет минимизировать накладные расходы за счет разбиения задачи на подзадачи.
Тщательная оптимизация сравнительной функции и грамотный выбор алгоритма сортировки могут ускорить работу qsort в разы.
Стабильность порядка элементов
Одним из недостатков классической qsort является то, что порядок следования одинаковых элементов после сортировки не гарантируется. Например, если в исходном массиве было [2, 5, 2, 3], то после сортировки может получиться и [2, 2, 3, 5] и [2, 3, 5, 2].
Такая неопределенность часто нежелательна. К счастью, можно модифицировать алгоритм сортировки так, чтобы получить стабильный порядок элементов. Для этого достаточно добавить проверку на равенство элементов внутри цикла сравнения.
if (compar(a, b) > 0) // меняем местами else if (compar(a, b) == 0) // оставляем как есть else // меняем местами
Подобная модификация гарантирует, что порядок равных элементов сохранится. Этот прием можно использовать и в std::sort для получения стабильной сортировки.
Тонкая настройка std::sort
Хотя std::sort и так очень быстра, иногда требуется дополнительная оптимизация производительности. Существует несколько способов это сделать.
Во-первых, можно воспользоваться параллельными версиями std::sort, такими как __gnu_parallel::sort, которые распараллеливают сортировку между несколькими потоками. Это особенно эффективно на многопроцессорных системах.
Во-вторых, для очень больших наборов данных имеет smysl использовать сортировку с предварительной индексацией. Сначала создается вспомогательный индекс, по которому затем происходит сортировка. Это позволяет уменьшить количество сравнений.
В-третьих, можно настроить размер порции (chunk size) при сортировке. Уменьшение размера порции способствует лучшей локализации данных и кэшированию.
Грамотная тонкая настройка параметров std::sort позволяет ускорить ее работу в определенных сценариях.
Выбор между qsort и std::sort
Итак, мы детально рассмотрели две основные функции сортировки в C/C++. Как же выбрать между qsort и std::sort для конкретной задачи?
В целом, при работе исключительно с контейнерами С++ следует отдавать предпочтение std::sort. Она проще в использовании и быстрее работает. А вот для сортировки произвольных массивов по-прежнему удобнее использовать универсальную qsort.
Конечно, возможны и гибридные решения, когда сначала выполняется предварительная сортировка через std::sort, а затем уточняющая сортировка через qsort с пользовательской функцией сравнения. Таким образом можно получить максимальную производительность.
В любом случае, знание сильных сторон каждой функции позволит выбрать оптимальный для конкретной задачи вариант сортировки данных.