1 三种基本排序
1.1 选择排序
基本思想: 每次选择整个序列中最小的值,放在前面。
vector<int> MySelectionSort(vector<int> arr) {
for(int i = 0; i < arr.size()-1;i++){
for (int j = i + 1; j < arr.size(); j++) {
if (arr[j] < arr[i]) {
swap(arr[j], arr[i]);
}
}
}
return arr;
}
- 选择排序的时间复杂度为
O
(
n
2
)
O(n^2)
O(n2),最好情况和最坏情况均为
O
(
n
2
)
O(n^2)
O(n2)。但是注意到,上述代码中,针对每一对比较的元素都进行了交换,会导致运行量增大,改进如下。引入了
minIndex
,用来保存需要交换元素的下表,每轮循环,只需要进行一次元素交换。
vector<int> MySelectionSort(vector<int> arr) {
int n = arr.size();
for (int i = 0; i < n - 1; ++i) {
int minIndex = i;
for (int j = i + 1; j < n; ++j) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
if (minIndex != i) {
swap(arr[i], arr[minIndex]);
}
}
return arr;
}
1.2 冒泡排序
基本思想: 每一轮循环,选出一个最大值,放在最后。
vector<int> MyBubbleSort(vector<int> nums) {
for (int i = 0; i < nums.size() - 1; i++) {
bool flag = false;
for (int j = 0; j < nums.size() - i - 1; j++) {
// 增序
if (nums[j + 1] < nums[j]) {
swap(nums[j + 1], nums[j]);
flag = true; //为True标记此轮有元素进行交换
}
}
if (!flag) break; // 如果没有发生交换,直接退出
}
return nums;
}
- 冒泡排序的时间复杂度为 O ( n 2 ) O(n^2) O(n2),最好情况为 O ( n ) O(n) O(n)和最坏情况均为 O ( n 2 ) O(n^2) O(n2)。
1.3 插入排序
基本思想: 将序列分为有序和无序两个序列,其中有序序列在前,每次从无序序列中取一个元素插入到有序序列中。
vector<int> MyInsertionSort(vector<int> arr) {
for (int i = 1; i < arr.size(); i++) {
int key = arr[i];
int j = i - 1;
// key值是一直不变的,因为需要比较
while (j >= 0 && key < arr[j]) {
arr[j + 1] = arr[j];
j--;
}
// 跳出循环,此时j所指向的位置,arr[j] < key, 因此key值需插入到arr[j+1]位置
arr[j + 1] = key;
}
return arr;
}
- 插入排序的时间复杂度为 O ( n 2 ) O(n^2) O(n2),最好情况为 O ( n ) O(n) O(n)和最坏情况均为 O ( n 2 ) O(n^2) O(n2)。
2 一种高级排序
2.1 快速排序
基本思想: 选择一个基准元素,将序列分为两个子序列,左子序列存放小于基准元素的值,右子序列存放大于基准元素的值。递归地对两个子序列进行排序,直到子序列的长度为1或0。
int partition(vector<int>& nums, int left, int right) {
int pivot = nums[right]; // 选择最右边的元素作为基准值
int i = left - 1; // 定义一个指针,初始时指向左侧子数组的起始位置-1
for (int j = left; j < right; j++) { // 遍历整个数组
if (nums[j] <= pivot) { // 如果当前元素小于等于基准值
i++; // 将左侧子数组的末尾指针向右移动一位
swap(nums[i], nums[j]); // 交换左侧子数组的末尾元素和当前元素
}
}
swap(nums[i + 1], nums[right]); // 将基准值移到正确的位置上
return i + 1; // 返回基准值的索引
}
vector<int> MyQuickSort(vector<int> nums, int left, int right) {
if (left < right) {
int pivot_index = partition(nums, left, right); // 将数组分成左右两部分,并返回基准值的索引
MyQuickSort(nums, left, pivot_index - 1); // 对左侧子数组进行递归排序
MyQuickSort(nums, pivot_index + 1, right); // 对右侧子数组进行递归排序
}
return nums;
}
- 快速排序的时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),最好情况为 O ( n l o g n ) O(nlogn) O(nlogn)和最坏情况均为 O ( n 2 ) O(n^2) O(n2)。
2.2 堆排序
堆排序包含建立堆和调整堆两个操作。元素少的时候在建立堆和调整堆的过程中会产生大开销,因此不适用。
// 最大堆化函数,用于维护最大堆的性质
void maxHeapify(vector<int>& vec, int n, int i) {
int largest = i; // 假设当前节点 i 的值最大
int left = 2 * i + 1; // 左子节点索引
int right = 2 * i + 2; // 右子节点索引
if (left < n && vec[left] > vec[largest])
largest = left;
if (right < n && vec[right] > vec[largest])
largest = right;
if (largest != i) {
swap(vec[i], vec[largest]);
maxHeapify(vec, n, largest); // 递归地最大堆化子树
}
}
// 堆排序函数
void heapSort(vector<int>& vec) {
int n = vec.size();
// 构建最大堆
for (int i = n / 2 - 1; i >= 0; i--)
maxHeapify(vec, n, i);
// 从最大堆中依次取出元素,进行排序
for (int i = n - 1; i > 0; i--) {
swap(vec[0], vec[i]); // 将当前最大值放到数组末尾
maxHeapify(vec, i, 0); // 对新的根节点进行最大堆化
}
}
- 快速排序的时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),最好情况为 O ( n l o g n ) O(nlogn) O(nlogn)和最坏情况均为 O ( n l o g n ) O(nlogn) O(nlogn)。
- 堆排序算法适合元素较多的情况。
2.3 归并排序
归并排序将数组递归的氛围两个子数组,然后对子数组进行排序,最终将两个有序子数组合并为一个有序数组。
// 合并两个有序数组
void merge(std::vector<int>& arr, int left, int mid, int right) {
int n1 = mid - left + 1;
int n2 = right - mid;
// 创建临时数组来存储左右两个子数组
vector<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];
++i;
}
else {
arr[k] = R[j];
++j;
}
++k;
}
// 处理剩余的元素
while (i < n1) {
arr[k] = L[i];
++i;
++k;
}
while (j < n2) {
arr[k] = R[j];
++j;
++k;
}
}
// 归并排序
void mergeSort(std::vector<int>& arr, int left, int right) {
if (left < right) {
int mid = left + (right - left) / 2;
// 分割数组为两个子数组
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
// 合并两个有序子数组
merge(arr, left, mid, right);
}
}
- 无论是最好情况、最坏情况还是平均情况下,其时间复杂度都为 O ( n l o g n ) O(n log n) O(nlogn)。由于归并排序需要额外的空间来存储中间结果,因此其空间复杂度为 O ( n ) O(n) O(n)。
比较
平均时间复杂度 | 最坏时间复杂度 | 最好时间复杂度 | 空间复杂度 | |
---|---|---|---|---|
选择排序 | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( 1 ) O(1) O(1) |
冒泡排序 | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( n ) O(n) O(n) | O ( 1 ) O(1) O(1) |
插入排序 | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( n ) O(n) O(n) | O ( 1 ) O(1) O(1) |
快速排序 | O ( n l o g n ) O(nlogn) O(nlogn) | O ( n 2 ) O(n^2) O(n2) | O ( n l o g n ) O(nlogn) O(nlogn) | O ( l o g n ) O(logn) O(logn) |
堆排序 | O ( n l o g n ) O(nlogn) O(nlogn) | O ( n l o g n ) O(nlogn) O(nlogn) | O ( n l o g n ) O(nlogn) O(nlogn) | O ( 1 ) O(1) O(1) |
堆排序 | O ( n l o g n ) O(nlogn) O(nlogn) | O ( n l o g n ) O(nlogn) O(nlogn) | O ( n l o g n ) O(nlogn) O(nlogn) | O ( n ) O(n) O(n) |