排序算法——详解
排序算法
(冒泡、选择、插入、快排、归并、堆排、计数、桶、基数)
稳定性 (Stability): 如果排序算法能保证,当待排序序列中存在值相等的元素时,排序后这些元素的相对次序保持不变,那么该算法就是稳定的。 例如:如果原始序列中有两个相同的元素 A
和 B
(A在B前面),排序后 A 仍然在 B 的前面,则该排序算法是稳定的。
- 冒泡排序 (Bubble Sort)
-
思想: 重复遍历待排序的列表,比较相邻的两个元素,如果顺序错误就交换它们,直到没有元素可以交换,即排序完成。每次遍历都会将最大(或最小)的元素“冒泡”到其最终位置。
-
C++ 示例 (伪代码):
void bubbleSort(int arr[], int n) {for (int i = 0; i < n - 1; ++i) {bool 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;} }
- 选择排序 (Selection Sort)
-
思想: 每次遍历(或扫描)整个待排序的无序部分,找到其中最小(或最大)的元素,然后将其放到无序部分的起始位置。重复这个过程直到所有元素都排好序。
-
C++ 示例 (伪代码):
void selectionSort(int arr[], int n) {for (int i = 0; i < n - 1; ++i) {int min_idx = i;for (int j = i + 1; j < n; ++j) {if (arr[j] < arr[min_idx]) {min_idx = j;}}if (min_idx != i) {std::swap(arr[i], arr[min_idx]);}} }
- 插入排序 (Insertion Sort)
-
思想: 将一个元素插入到已经排序好的部分。从第二个元素开始,将其与前面已排序的元素进行比较,如果它比前面的元素小,就将前面的元素后移,直到找到合适的位置插入该元素。
-
C++ 示例 (伪代码):
void insertionSort(int arr[], int n) {for (int i = 1; i < n; ++i) {int key = arr[i];int j = i - 1;// 将比 key 大的元素向后移动while (j >= 0 && arr[j] > key) {arr[j+1] = arr[j];j--;}arr[j+1] = key; // 插入 key} }
- 快速排序 (Quick Sort)
-
思想: 采用分治策略。从数组中选择一个元素作为“基准”(pivot),将数组分割成两个子数组:一个子数组的所有元素都小于基准,另一个子数组的所有元素都大于基准。然后递归地对这两个子数组进行排序。
-
C++ 示例 (手写实现见下文)。
#include <iostream> #include <vector> #include <algorithm> // For std::swap// 辅助函数:分区操作 // 返回基准元素最终的索引 int partition(std::vector<int>& arr, int low, int high) {int pivot = arr[high]; // 选择最后一个元素作为基准int i = (low - 1); // 小于基准的元素区域的结束索引for (int j = low; j <= high - 1; j++) {// 如果当前元素小于或等于基准if (arr[j] <= pivot) {i++; // 增加小于基准的元素区域的结束索引std::swap(arr[i], arr[j]);}}std::swap(arr[i + 1], arr[high]); // 将基准元素放到正确的位置return (i + 1); }// 快速排序主函数 void quickSort(std::vector<int>& arr, int low, int high) {if (low < high) {// pi 是分区点索引,arr[pi] 现在在正确的位置int pi = partition(arr, low, high);// 递归地对左右两部分进行排序quickSort(arr, low, pi - 1);quickSort(arr, pi + 1, high);} }/* // 示例使用 int main() {std::vector<int> arr = {10, 7, 8, 9, 1, 5};int n = arr.size();std::cout << "Original array: ";for (int x : arr) {std::cout << x << " ";}std::cout << std::endl;quickSort(arr, 0, n - 1);std::cout << "Sorted array (Quick Sort): ";for (int x : arr) {std::cout << x << " ";}std::cout << std::endl;return 0; } */
- 归并排序 (Merge Sort)
-
思想: 采用分治策略。将待排序的序列递归地分成两半,直到每个子序列只包含一个元素(这被认为是自然有序的)。然后将这些有序的子序列两两合并,形成更大的有序序列,直到所有序列合并成一个完整的有序序列。
-
C++ 示例 (手写实现见下文)。
#include <iostream> #include <vector> #include <algorithm> // Not strictly needed for merge, but good for general use// 辅助函数:合并两个有序子数组 void merge(std::vector<int>& arr, int left, int mid, int right) {int n1 = mid - left + 1;int n2 = right - mid;// 创建临时数组std::vector<int> L(n1);std::vector<int> R(n2);// 复制数据到临时数组 L[] 和 R[]for (int i = 0; i < n1; i++) {L[i] = arr[left + i];}for (int j = 0; j < n2; j++) {R[j] = arr[mid + 1 + j];}// 合并临时数组回到 arr[left..right]int i = 0; // L[] 的起始索引int j = 0; // R[] 的起始索引int k = left; // 合并后数组的起始索引while (i < n1 && j < n2) {if (L[i] <= R[j]) {arr[k] = L[i];i++;} else {arr[k] = R[j];j++;}k++;}// 复制 L[] 中剩余的元素 (如果有)while (i < n1) {arr[k] = L[i];i++;k++;}// 复制 R[] 中剩余的元素 (如果有)while (j < n2) {arr[k] = R[j];j++;k++;} }// 归并排序主函数 void mergeSort(std::vector<int>& arr, int left, int right) {if (left >= right) { // 基本情况:如果只有一个或零个元素return;}int mid = left + (right - left) / 2; // 计算中间点,防止溢出mergeSort(arr, left, mid); // 递归排序左半部分mergeSort(arr, mid + 1, right); // 递归排序右半部分merge(arr, left, mid, right); // 合并已排序的两半 }/* // 示例使用 int main() {std::vector<int> arr = {12, 11, 13, 5, 6, 7};int n = arr.size();std::cout << "Original array: ";for (int x : arr) {std::cout << x << " ";}std::cout << std::endl;mergeSort(arr, 0, n - 1);std::cout << "Sorted array (Merge Sort): ";for (int x : arr) {std::cout << x << " ";}std::cout << std::endl;return 0; } */
- 堆排序 (Heap Sort)
-
思想: 利用堆(通常是最大堆)的数据结构。首先将待排序的序列构建成一个最大堆。然后,重复地将堆顶元素(最大值)与堆的最后一个元素交换,并缩小堆的范围,重新调整剩余元素以保持堆的性质。
-
C++ 示例 (伪代码):
void heapify(int arr[], int n, int i) {int largest = i; // 初始化最大元素为根int left = 2 * i + 1; // 左子节点int right = 2 * i + 2; // 右子节点if (left < n && arr[left] > arr[largest])largest = left;if (right < n && arr[right] > arr[largest])largest = right;if (largest != i) {std::swap(arr[i], arr[largest]);heapify(arr, n, largest); // 递归地堆化受影响的子树} }void heapSort(int arr[], int n) {// 构建最大堆 (从最后一个非叶子节点开始)for (int i = n / 2 - 1; i >= 0; i--)heapify(arr, n, i);// 一个个从堆顶取出元素for (int i = n - 1; i > 0; i--) {std::swap(arr[0], arr[i]); // 将当前最大元素移到数组末尾heapify(arr, i, 0); // 对剩余元素进行堆化} }
- 计数排序 (Counting Sort)
-
思想: 非比较排序。适用于待排序元素是整数,且范围不大的情况。它统计每个数字出现的次数,然后根据统计结果将数字按顺序输出。
-
C++ 示例 (伪代码):
void countingSort(int arr[], int n) {int max_val = arr[0];for (int i = 1; i < n; ++i) {if (arr[i] > max_val) {max_val = arr[i];}}std::vector<int> count(max_val + 1, 0);std::vector<int> output(n);for (int i = 0; i < n; ++i) {count[arr[i]]++;}for (int i = 1; i <= max_val; ++i) {count[i] += count[i - 1]; // 累加计数,表示小于等于当前值的元素个数}// 从后向前遍历,保证稳定性for (int i = n - 1; i >= 0; --i) {output[count[arr[i]] - 1] = arr[i];count[arr[i]]--;}for (int i = 0; i < n; ++i) {arr[i] = output[i];} }
- 桶排序 (Bucket Sort)
-
思想: 非比较排序。将待排序的元素分散到有限数量的桶中,每个桶再分别进行排序(可以使用其他排序算法,如插入排序),最后将所有桶中的元素依次取出。
-
C++ 示例 (伪代码):
void bucketSort(float arr[], int n) {// 创建 n 个空桶std::vector<std::vector<float>> buckets(n);// 将元素放入对应的桶中for (int i = 0; i < n; ++i) {int bucket_idx = n * arr[i]; // 假设元素在 [0, 1) 之间buckets[bucket_idx].push_back(arr[i]);}// 对每个桶进行排序 (可以使用插入排序或其他简单排序)for (int i = 0; i < n; ++i) {std::sort(buckets[i].begin(), buckets[i].end()); // 使用std::sort简化}// 将桶中的元素按顺序放回原数组int index = 0;for (int i = 0; i < n; ++i) {for (float val : buckets[i]) {arr[index++] = val;}} }
- 基数排序 (Radix Sort)
-
思想: 非比较排序。根据数字的各个位(或基数)进行排序。从最低有效位(LSD)或最高有效位(MSD)开始,对每一位使用稳定的子排序算法(通常是计数排序)进行排序。
-
C++ 示例 (伪代码,通常结合计数排序实现):
// 辅助函数:根据指定位数对数组进行计数排序 void countSortForRadix(int arr[], int n, int exp) {std::vector<int> output(n);std::vector<int> count(10, 0); // 0-9for (int i = 0; i < n; i++) {count[(arr[i] / exp) % 10]++;}for (int i = 1; i < 10; i++) {count[i] += count[i - 1];}for (int i = n - 1; i >= 0; i--) {output[count[(arr[i] / exp) % 10] - 1] = arr[i];count[(arr[i] / exp) % 10]--;}for (int i = 0; i < n; i++) {arr[i] = output[i];} }void radixSort(int arr[], int n) {int max_val = arr[0];for (int i = 1; i < n; ++i) {if (arr[i] > max_val) {max_val = arr[i];}}// 对每一位进行计数排序for (int exp = 1; max_val / exp > 0; exp *= 10) {countSortForRadix(arr, n, exp);} }
算法名称 | 最佳时间复杂度 | 平均时间复杂度 | 最坏时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
冒泡排序 | O(n) | O(n2) | O(n2) | O(1) | 稳定 |
选择排序 | O(n2) | O(n2) | O(n2) | O(1) | 不稳定 |
插入排序 | O(n) | O(n2) | O(n2) | O(1) | 稳定 |
快速排序 | O(nlogn) | O(nlogn) | O(n2) | O(logn) (递归栈) | 不稳定 |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 不稳定 |
计数排序 | O(n+k) | O(n+k) | O(n+k) | O(k) | 稳定 |
桶排序 | O(n+k) | O(n+k) | O(n2) (桶内不均匀) | O(n+k) | 稳定 (依赖桶内排序) |
基数排序 | O(d(n+k)) | O(d(n+k)) | O(d(n+k)) | O(n+k) | 稳定 (依赖子排序) |
- 注:
- n 表示待排序元素的数量。
- k 表示待排序元素的范围(例如,计数排序中最大值-最小值+1)。
- d 表示数字的位数(基数排序)。
- 快速排序的最坏时间复杂度 O(n2) 发生在输入已经有序或接近有序,且选择的基准不好的情况下。
- 桶排序的最坏时间复杂度 O(n2) 发生在所有元素都分到同一个桶里,并且桶内排序算法是 O(n2) 的时候。