复习一下排序算法,及各排序算法的时间复杂度(了解简单时间复杂度的计算)
1.常见时间复杂度的分类
时间复杂度 | 描述 | 例子 |
---|---|---|
O(1) | 常数时间:与输入大小无关 | 访问数组中的某元素 |
O(log n) | 对数时间:规模缩小一半 | 二分查找 |
O(n) | 线性时间:与输入成正比 | 遍历数组 |
O(n log n) | 线性对数时间 | 快速排序、归并排序 |
O(n^2) | 二次方时间:嵌套循环 | 冒泡排序、插入排序 |
O(2^n) | 指数时间 | 未优化递归解决斐波那契数列 |
O(n!) | 阶乘时间 | 解决全排列问题 |
2.排序时间复杂度(按常见度粗略排序)
排序算法 | 最好时间复杂度 | 最坏时间复杂度 | 平均时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
冒泡排序 | O(n) | O(n²) | O(n²) | O(1) | 稳定 |
选择排序 | O(n²) | O(n²) | O(n²) | O(1) | 不稳定 |
插入排序 | O(n) | O(n²) | O(n²) | O(1) | 稳定 |
希尔排序 | O(n log n) | O(n²) | O(n¹.³) ~ O(n log²n) | O(1) | 不稳定 |
快速排序 | O(n log n) | O(n²) | O(n log n) | O(log n) | 不稳定 |
归并排序 | O(n log n) | O(n log n) | O(n log n) | O(n) | 稳定 |
堆排序 | O(n log n) | O(n log n) | O(n log n) | O(1) | 不稳定 |
计数排序 | O(n + k) | O(n + k) | O(n + k) | O(k) | 稳定 |
桶排序 | O(n + k) | O(n²) | O(n + k) | O(n + k) | 稳定 |
基数排序 | O(nk) | O(nk) | O(nk) | O(n + k) | 稳定 |
3.常见排序算法简要代码实现
1. 冒泡排序(Bubble Sort)
// 冒泡排序:每轮将最大值“冒”到最后
void bubble_sort(int arr[], int n) {
for (int i = 0; i < n - 1; i++) {
// 每次比较相邻两个元素,将大的移到右边
for (int j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
// 交换两个元素
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
2. 选择排序(Selection Sort)
// 选择排序:每轮选择最小值放到前面
void selection_sort(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) {
int temp = arr[i];
arr[i] = arr[min_idx];
arr[min_idx] = temp;
}
}
}
3. 插入排序(Insertion Sort)
// 插入排序:将每个元素插入到前面有序序列中
void insertion_sort(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;
}
}
4. 快速排序(Quick Sort)
// 快速排序:分治法,选择一个基准值,将小的放左边,大的放右边
// 分区函数,返回基准值最终位置
int partition(int arr[], int low, int high) {
int pivot = arr[low]; // 选择第一个元素作为基准
int left = low;
int right = high;
while (left < right) {
// 从右往左找比 pivot 小的
while (left < right && arr[right] >= pivot)
right--;
arr[left] = arr[right]; // 移动到左边空位
// 从左往右找比 pivot 大的
while (left < right && arr[left] <= pivot)
left++;
arr[right] = arr[left]; // 移动到右边空位
}
arr[left] = pivot; // 基准放到最终位置
return left;
}
// 快排主函数
void quick_sort(int arr[], int low, int high) {
if (low < high) {
int pivot_idx = partition(arr, low, high);
quick_sort(arr, low, pivot_idx - 1); // 排左边
quick_sort(arr, pivot_idx + 1, high); // 排右边
}
}
5. 归并排序(Merge Sort)
// 合并两个有序子数组:arr[left..mid] 和 arr[mid+1..right]
void merge(int arr[], int left, int mid, int right) {
int n1 = mid - left + 1;
int n2 = right - mid;
// 临时数组
int L[n1], R[n2];
// 拷贝数据
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];
int i = 0, j = 0, k = left;
// 合并两个有序数组
while (i < n1 && j < n2) {
if (L[i] <= R[j])
arr[k++] = L[i++];
else
arr[k++] = R[j++];
}
// 复制剩余元素
while (i < n1) arr[k++] = L[i++];
while (j < n2) arr[k++] = R[j++];
}
// 归并排序主函数
void merge_sort(int arr[], int left, int right) {
if (left < right) {
int mid = (left + right) / 2;
merge_sort(arr, left, mid); // 排左半部分
merge_sort(arr, mid + 1, right); // 排右半部分
merge(arr, left, mid, right); // 合并
}
}
四、常见算法介绍
-
冒泡排序
原理:两两比较相邻元素,将较大的元素逐步“冒泡”到数组末尾。
特点:简单易懂,但性能较低。
适用场景:小数据量或数据接近有序时。
-
选择排序
原理:每次从未排序部分选择最小(或最大)的元素,放到已排序部分末尾。
特点:实现简单,但多次交换导致效率低。
适用场景:数据量小,稳定性不要求高。
-
插入排序
原理:将未排序元素逐一插入到已排序部分的适当位置。
特点:对几乎有序的数据效果较好。
适用场景:小规模数据、接近有序数据。
-
快速排序
原理:选定一个基准值,将数组划分为小于和大于基准值的两部分,递归排序。
特点:平均性能优越,但最坏情况下效率低(如有序数组)。
适用场景:大规模数据,效率高要求。
-
归并排序
原理:将数组递归分成两部分,分别排序后合并。
特点:稳定且效率高,但需要额外空间。
适用场景:需要稳定性,且内存足够的场景。