Сортировка пузырьком — не самый быстрый алгоритм сортировки, но обычно именно его первым изучают студенты по специальности «Информатика», и неспроста. Освоив его подход, легче понять принцип работы алгоритмов сортировки на фундаментальном уровне. Изучим, как реализовать его на C++ эффективно.
Базовый алгоритм
При пузырьковой сортировке многократно проходится список, соседние элементы сравниваются и, если расположены в неправильном порядке, меняются местами. Вот простейшая реализация:
void bubbleSort(std::vector<int>& arr) {
int n = arr.size();
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
std::swap(arr[j], arr[j + 1]);
}
}
}
}
Разберем подробнее:
- Внешний цикл выполняется
n-1раз. - Внутренним циклом сравниваются соседние элементы.
- Если элемент больше следующего, они меняются местами.
Оптимизированная реализация
Базовая версия — рабочая, но мы сделаем ее более эффективной. Вот оптимизированная версия: если массив уже отсортирован, она завершается раньше:
void bubbleSortOptimized(std::vector<int>& arr) {
int n = arr.size();
bool swapped;
for (int i = 0; i < n - 1; i++) {
swapped = false;
for (int j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
std::swap(arr[j], arr[j + 1]);
swapped = true;
}
}
// Если перемен мест не случилось, массив уже отсортирован
if (!swapped) {
break;
}
}
}
Оптимизация выполняется за счет отслеживания, не случилось ли перестановок во время прохождения. Если их не было, массив отсортирован и сортировка завершается.
Версия template для любого типа данных
Сделаем сортировку пузырьком рабочей с любым типом данных, которым поддерживается сравнение:
template<typename T>
void bubbleSort(std::vector<T>& arr) {
int n = arr.size();
bool swapped;
for (int i = 0; i < n - 1; i++) {
swapped = false;
for (int j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
std::swap(arr[j], arr[j + 1]);
swapped = true;
}
}
if (!swapped) {
break;
}
}
}
Реальные применения
1. Сортировка пользовательских объектов
class Student {
private:
std::string name;
float gpa;
public:
Student(std::string n, float g) : name(std::move(n)), gpa(g) {}
// Методы-получатели
const std::string& getName() const { return name; }
float getGPA() const { return gpa; }
// Оператор сравнения для сортировки
bool operator>(const Student& other) const {
return gpa > other.gpa;
}
};
void sortStudentsByGPA() {
std::vector<Student> students = {
Student("Alice", 3.8),
Student("Bob", 3.5),
Student("Charlie", 3.9)
};
bubbleSort(students); // Используется версия «template»
// Выводятся отсортированные результаты
for (const auto& student : students) {
std::cout << student.getName() << ": "
<< student.getGPA() << "\n";
}
}
2. Сортировка с пользовательскими компараторами
template<typename T, typename Compare>
void bubbleSort(std::vector<T>& arr, Compare comp) {
int n = arr.size();
bool swapped;
for (int i = 0; i < n - 1; i++) {
swapped = false;
for (int j = 0; j < n - 1 - i; j++) {
if (comp(arr[j], arr[j + 1])) {
std::swap(arr[j], arr[j + 1]);
swapped = true;
}
}
if (!swapped) {
break;
}
}
}
// Пример применения с пользовательским компаратором
void sortWithCustomRules() {
std::vector<Student> students = {
Student("Alice", 3.8),
Student("Bob", 3.5),
Student("Charlie", 3.9)
};
// Сортируются по длине имени
bubbleSort(students, [](const Student& a, const Student& b) {
return a.getName().length() > b.getName().length();
});
}
3. Визуальное отображение сортировки
class SortVisualizer {
private:
std::vector<int> data;
static const int DELAY_MS = 100;
void clearScreen() {
std::cout << "\033[2J\033[1;1H"; // Управляющие символы ANSI
}
void drawBars() {
for (int num : data) {
std::cout << std::string(num, '#') << '\n';
}
std::this_thread::sleep_for(
std::chrono::milliseconds(DELAY_MS)
);
}
public:
SortVisualizer(std::vector<int> arr) : data(std::move(arr)) {}
void visualBubbleSort() {
int n = data.size();
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - 1 - i; j++) {
if (data[j] > data[j + 1]) {
std::swap(data[j], data[j + 1]);
clearScreen();
drawBars();
}
}
}
}
};
Анализ производительности
Измерим, как выполняется сортировка пузырьком при разных размерах входных данных:
class SortAnalyzer {
private:
static std::vector<int> generateRandomArray(int size) {
std::vector<int> arr(size);
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(1, 1000);
for (int& num : arr) {
num = dis(gen);
}
return arr;
}
public:
static void analyzeBubbleSort() {
std::vector<int> sizes = {100, 1000, 10000};
for (int size : sizes) {
auto arr = generateRandomArray(size);
auto start = std::chrono::high_resolution_clock::now();
bubbleSort(arr);
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<
std::chrono::milliseconds
>(end - start);
std::cout << "Size " << size << ": "
<< duration.count() << "ms\n";
}
}
};
Типичные проблемы и их решения
1. Лишние сравнения
// Плохо: даже после того, как массив отсортирован, сравнения продолжаются
void inefficientBubbleSort(std::vector<int>& arr) {
int n = arr.size();
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - 1; j++) { // Неверно: должно быть «n–1–i»
if (arr[j] > arr[j + 1]) {
std::swap(arr[j], arr[j + 1]);
}
}
}
}
// Хорошо: при каждом проходе сравнения сокращаются
void efficientBubbleSort(std::vector<int>& arr) {
int n = arr.size();
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
std::swap(arr[j], arr[j + 1]);
}
}
}
}
2. Управление памятью с указателями
// Безопасная версия для массивов указателей
template<typename T>
void bubbleSortPointers(std::vector<T*>& arr) {
int n = arr.size();
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - 1 - i; j++) {
// Безопасно разыменовываются указатели
if (*arr[j] > *arr[j + 1]) {
std::swap(arr[j], arr[j + 1]);
}
}
}
}
Сортировка пузырьком в коде на продакшене обычно не используется, так как std::sort предпочтительнее. Тем не менее, понимая эти принципы работы, проще освоить алгоритмы сортировки в целом. Рассмотренные здесь концепции — сравнение элементов, перестановка значений, оптимизация на основе состояния массива — применимы и к алгоритмам сортировки посложнее.
Не забывайте, что ключ к пониманию пузырьковой сортировки — это визуализация того, как элементы «всплывают» на свои места. При каждом проходе массива наибольший несортированный элемент перемещается к своей конечной позиции в конце массива.
Читайте также:
- C++: полное руководство по бинарной сортировке
- C++: полное руководство по кортежам
- C++: полное руководство по «приведению вверх»
Читайте нас в Telegram, VK и Дзен
Перевод статьи ryan: Bubble Sort in C++: Complete Guide





