文章目录
前言
这一篇打算用来写十大排序,尤其是三大很快的排序:快速、堆、归并,应该也是面试的重中之重。
题目
排序算法 | 时间复杂度(平均) | 时间复杂度(最好) | 时间复杂度(最差) | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
直插排序 | O(n2) | O(n2) | O(n2) | O(1) | 稳定 |
选择排序 | O(n2) | O(n2) | O(n2) | O(1) | 不稳定 |
冒泡排序 | O(n2) | O(n) | O(n2) | O(1) | 稳定 |
希尔排序 | O(n1.5) | O(n) | O(n2) | O(1) | 不稳定 |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 |
快速排序 | O(nlogn) | O(nlogn) | O(n^2) | O(logn) | 不稳定 |
堆排序 | 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(n^2) | O(n+k) | 稳定 |
基数排序 | O(n*k) | O(n*k) | O(n*k) | O(n+k) | 稳定 |
快乐的做法
class Solution
{
public:
vector<int> sortArray(vector<int>& nums)
{
sort(nums.begin(),nums.end());
return nums;
}
};
直接插入排序
- 稳定性:稳定
- 时间复杂度:O(n2)
- 空间复杂度:O(1)
- 算法步骤
- 从第一个元素开始,该元素认为已经被排序;
- 取下一个元素,在已排序的序列中进行二分查找
- 根据下标
l
和r
找到下标,r
的右边一个就是应该插入的位置 - 将新元素插入到
r
后面 - 重复步骤
2
~4
代码(首刷看二分解析,更快)
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
int n = nums.size();
for(int i = 1; i < n; i++) {
if(nums[i] >= nums[i-1]) // 两元素递增则直接插入
continue;
int l = 0, r = i-1;
// 二分查找,时间复杂度logn
while(l <= r) {
int mid = (r-l)/2 + l;
if(nums[mid] > nums[i]) { // r右侧元素均大于 nums[i],即 r 及其左侧元素均小于等于nums[i]
r = mid - 1;
} else {
l = mid + 1;
}
}
int tmp = nums[i];
for(int j = i-1; j >= r + 1; j--) {
nums[j+1] = nums[j];
}
nums[r+1] = tmp;
}
return nums;
}
};
该算法在LC上会超时,无法通过。
选择排序
- 稳定性:不稳定
- 时间复杂度:O(n2)
- 空间复杂度:O(1)
- 算法步骤
- 第一层循环:
[0,n-1]
- 第二层循环:
[i+1,n]
- 循环遍历找到最小值
ind
,将i
和ind
交换
- 第一层循环:
代码(首刷自解)
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
int n = nums.size();
for(int i = 0; i < n-1; i++) {
int ind = i;
for(int j = i+1; j < n; j++) {
if(nums[j] < nums[ind]) {
ind = j;
}
}
swap(nums[i], nums[ind]);
}
return nums;
}
};
该算法在LC上会超时,无法通过。
冒泡排序
- 稳定性:稳定
- 时间复杂度:O(n2)
- 空间复杂度:O(1)
- 算法步骤
- 第一层循环
[0,n-1]
- 第二层循环
[0,n-1-i]
- 两两比较交换
- 第一层循环
代码(首刷自解)
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
int n = nums.size();
for(int i = 0; i < n-1; i++) {
bool flag = false;
for(int j = 0; j < n-i-1; j++) {
if(nums[j] > nums[j+1]) {
swap(nums[j], nums[j+1]);
flag = true;
}
}
if(!flag)
break;
}
return nums;
}
};
该算法在LC上会超时,无法通过。
希尔排序
- 稳定性:不稳定
- 时间复杂度:O(n1.5)
- 空间复杂度:O(1)
- 算法步骤:
- 选择一个增量序列
T1,T2,… ,Tk
,其中Ti>Tj,Tk=1,i>j; - 每趟排序,根据对应的增量
Ti
,将待排序列分割成若干子序列,分别对各子序列进行直接插入排序; - 按增量序列个数
k
,对序列进行k
趟排序。
- 选择一个增量序列
代码(首刷看解析)
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
int n = nums.size();
for(int gap = n/2; gap >= 1; gap /= 2) {
for(int i = gap; i < n; i++) {
shellSort(nums, gap, i);
}
}
return nums;
}
void shellSort(vector<int>& nums, int gap, int i) {
int j = i-gap;
int tmp = nums[i];
for(; j >= 0 && tmp < nums[j]; j -= gap) {
nums[j+gap] = nums[j];
}
nums[j+gap] = tmp;
}
};
第一个通过的排序,记得将判断条件放入for中,节省时间
归并排序
- 稳定性:稳定
- 时间复杂度:O(nlogn)
- 空间复杂度:O(n)
- 算法步骤
- 把长度为n的输入序列分成两个长度为n/2的子序列;
- 对这两个子序列分别采用归并排序(递归);
- 将两个排序好的子序列合并成一个最终的排序序列(注意下标)。
代码(首刷调试看解析)
class Solution {
public:
vector<int> tmp; // 辅助数组
vector<int> sortArray(vector<int>& nums) {
tmp.resize(nums.size(), 0); // 初始化
MergeSort(nums, 0, nums.size()-1);
return nums;
}
void MergeSort(vector<int>& nums, int l, int r) {
if(l >= r)
return; // 出递归
int mid = (r-l)/2+l;
MergeSort(nums, l, mid);
MergeSort(nums, mid+1, r);
// 下面是归并操作
int i = l, j = mid+1;
int k = 0;
while(i <= mid && j <= r) {
if(nums[i] <= nums[j]) {
tmp[k++] = nums[i++];
} else {
tmp[k++] = nums[j++];
}
}
while(i <= mid) {
tmp[k++] = nums[i++];
}
while(j <= r) {
tmp[k++] = nums[j++];
}
for(int m = 0; m < r-l+1; m++) {
nums[m+l] = tmp[m];
}
}
};
代码(二刷看解析)
class Solution {
public:
vector<int> tmp;
vector<int> sortArray(vector<int>& nums) {
tmp.resize(nums.size());
mergeSort(nums, 0, nums.size() - 1);
return nums;
}
void mergeSort(vector<int>& nums, int l, int r) {
if(l >= r)
return;
int mid = (l+r)/2;
mergeSort(nums, l, mid);
mergeSort(nums, mid+1, r);
int i = l, j = mid+1;
int k = 0;
while(i <= mid && j <= r) {
if(nums[i] < nums[j]) {
tmp[k++] = nums[i++];
} else {
tmp[k++] = nums[j++];
}
}
while(i <= mid) tmp[k++] = nums[i++];
while(j <= r) tmp[k++] = nums[j++];
for(int m = 0; m < r-l+1; m++) {
nums[l+m] = tmp[m];
}
}
};
代码(9.1 三刷自解)
class Solution {
public:
vector<int> tmp;
vector<int> sortArray(vector<int>& nums) {
tmp.resize(nums.size());
mergeSort(nums, 0, nums.size()-1);
return nums;
}
void mergeSort(vector<int>& nums, int left, int right) {
if(left >= right)
return;
int mid = (left+right)/2;
mergeSort(nums, left, mid);
mergeSort(nums, mid+1, right);
int i = left, j = mid+1;
int k = 0;
while(i <= mid && j <= right) {
if(nums[i] < nums[j])
tmp[k++] = nums[i++];
else tmp[k++] = nums[j++];
}
while(i <= mid)
tmp[k++] = nums[i++];
while(j <= right)
tmp[k++] = nums[j++];
for(int m = 0; m < k; m++)
nums[left+m] = tmp[m];
}
};
代码(9.21 四刷自解)
class Solution {
public:
vector<int> tmp;
vector<int> sortArray(vector<int>& nums) {
tmp = vector<int>(nums.size());
mergeSort(nums, 0, nums.size()-1);
return nums;
}
void mergeSort(vector<int>& nums, int l, int r) {
if(l >= r)
return;
int mid = (l+r)/2;
mergeSort(nums, l, mid);
mergeSort(nums, mid+1, r);
int i = l, j = mid+1;
int k = 0;
while(i <= mid && j <= r) {
if(nums[i] < nums[j]) {
tmp[k++] = nums[i++];
} else {
tmp[k++] = nums[j++];
}
}
while(i <= mid) {
tmp[k++] = nums[i++];
}
while(j <= r) {
tmp[k++] = nums[j++];
}
for(i = 0; i < k; i++) {
nums[l+i] = tmp[i];
}
}
};
快速排序
- 稳定性:不稳定
- 最好时间复杂度:O(nlogn)
- 最差时间复杂度:O(n2)
- 空间复杂度:O(logn)
- 算法步骤(左右指针法)
- 挑选一个
pivot
; - 每次移动指针再判断和
pivot
的大小,两两交换 - 递归
recursive
:按照[left,r]
和[r+1,right]
递归
- 挑选一个
快速排序最好情况下:O(nlogn) ,最坏情况下退化为选择排序(例如每次选择的都是最大值,之后遍历左边的 n -1 序列):O(n2)
代码(首刷看解析)
rand() % (b-a+1)+ a : a~b 之间的一个随机整数。
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
QuickSort(nums, 0, nums.size()-1);
return nums;
}
void QuickSort(vector<int>& nums, int left, int right) {
if(left >= right)
return;
int pivot = nums[rand()%(right-left+1)+left];
int l = left-1, r = right+1; // 左右指针初始指向边界外侧
while(l < r) {
while(nums[++l] < pivot); // 每次先移动指针,再做判断,这样不会陷入循环
while(nums[--r] > pivot);
if(l < r) swap(nums[l], nums[r]);
}
QuickSort(nums, left, r);
QuickSort(nums, r+1, right);
}
};
代码(二刷看解析)
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
QuickSort(nums, 0, nums.size() - 1);
return nums;
}
void QuickSort(vector<int>& nums, int left, int right) {
if(left >= right)
return; // 返回条件
int pivot = nums[rand()%(right - left + 1) + left];
int l = left - 1, r = right + 1;
while(l < r) {
while(nums[++l] < pivot);
while(nums[--r] > pivot);
if(l < r) swap(nums[l], nums[r]);
}
QuickSort(nums, left, r);
QuickSort(nums, r+1, right);
}
};
代码(8.31 三刷自解)
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
quickSort(nums, 0, nums.size()-1);
return nums;
}
void quickSort(vector<int>& nums, int left, int right) {
if(left >= right)
return;
int pivot = nums[rand()%(right-left+1)+left];
int l = left-1, r = right+1;
while(l < r) {
while(nums[++l] < pivot);
while(nums[--r] > pivot);
if(l < r)
swap(nums[l], nums[r]);
}
quickSort(nums, left, r);
quickSort(nums, r+1, right);
}
};
代码(9.21 四刷自解)
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
quickSort(nums, 0, nums.size()-1);
return nums;
}
void quickSort(vector<int>& nums, int l, int r) {
if(l >= r)
return;
int pivot = nums[rand()%(r-l)+l];
int left = l-1, right = r+1;
while(left < right) {
while(nums[++left] < pivot);
while(nums[--right] > pivot);
if(left < right)
swap(nums[left], nums[right]);
}
quickSort(nums, l, right);
quickSort(nums, right+1, r);
}
};
堆排序
- 稳定性:不稳定
- 时间复杂度:O(nlogn)
- 空间复杂度:O(1)
- 算法步骤
- 建立堆
- 把堆首(最大值)和堆尾互换
- 把堆的尺寸减一,重新构建堆,目的是把新的数组顶端数据调整到相应位置
- 重复步骤2和3,直到堆的尺寸为1
代码(首刷调试看解析)
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
buildHeap(nums);
int n = nums.size();
for(int i = n-1; i > 0; i--) {
swap(nums[0], nums[i]);
heapAdjust(nums, 0, i);
}
return nums;
}
void buildHeap(vector<int>& nums) {
int n = nums.size();
for(int i = (n-1)/2; i >= 0; i--) { // 从下往上建立
heapAdjust(nums, i, n);
}
}
void heapAdjust(vector<int>& nums, int i, int n) { // 调整为从上往下
while(i*2+1 < n) { // n为个数,当还有儿子的时候
int child = i*2+1;
if(child+1 < n && nums[child+1] > nums[child])
child++;
if(nums[child] > nums[i]) {
swap(nums[child], nums[i]);
i = child;
} else {
break;
}
}
}
};
代码(二刷调试看解析)
三个注意点:
heapAdjust
的参数需要指明长度,因为最后面的都是已经排好序的- 一开始构建堆的时候,for需要
i>=0
- 堆排序时
heapAdjust
的参数:长度需要-1
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
buildHeap(nums);
for(int i = nums.size() - 1; i; i--) {
swap(nums[0], nums[i]);
heapAdjust(nums, 0, i-1);
}
return nums;
}
void buildHeap(vector<int>& nums) {
int n = nums.size() - 1;
for(int i = n/2; i >= 0; i--) {
heapAdjust(nums, i, n);
}
}
void heapAdjust(vector<int>& nums, int k, int len) {
// int n = nums.size() - 1;
for(int i = k*2+1; i <= len; i = i*2+1) {
if(i+1 <= len && nums[i+1] > nums[i]) i++;
if(nums[i] > nums[k]) {
swap(nums[k], nums[i]);
k = i;
} else {
break;
}
}
}
};
代码(9.1 三刷自解)
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
buildHeap(nums);
int n = nums.size();
for(int i = 0; i < n; i++) {
swap(nums[0], nums[n-i-1]);
heapAdjust(nums, 0, n-i-2);
}
return nums;
}
void buildHeap(vector<int>& nums) {
int n = nums.size();
for(int i = (n-1)/2; i >= 0; i--) {
heapAdjust(nums, i, n-1);
}
}
void heapAdjust(vector<int>& nums, int k, int n) {
for(int i = k*2+1; i <= n; i = i*2+1) {
if(i+1 <= n && nums[i+1] > nums[i])
i++;
if(nums[k] < nums[i]) {
swap(nums[k], nums[i]);
k = i;
} else {
break;
}
}
}
};
代码(9.21 四刷自解)
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
int n = nums.size()-1;
biuldHeap(nums);
for(int i = n; i; i--) {
swap(nums[i], nums[0]);
heapAdjust(nums, i-1, 0);
}
return nums;
}
void biuldHeap(vector<int>& nums) {
int n = nums.size()-1;
for(int i = (n-1)/2; i >= 0; i--)
heapAdjust(nums, n, i);
}
void heapAdjust(vector<int>& nums, int n, int k) {
for(int i = k*2+1; i <= n; i = i*2+1) {
if(i+1 <= n && nums[i+1] > nums[i])
i++;
if(nums[k] < nums[i]) {
swap(nums[k], nums[i]);
k = i;
}
}
}
};
计数排序
时间复杂度:O(n+k)
空间复杂度:O(k)
稳定性:稳定
先假设 20 个数列为:{9, 3, 5, 4, 9, 1, 2, 7, 8,1,3, 6, 5, 3, 4, 0, 10, 9, 7, 9}。
让我们先遍历这个无序的随机数组,找出最大值为 10 和最小值为 0。这样我们对应的计数范围将是 0 ~ 10。然后每一个整数按照其值对号入座,对应数组下标的元素进行加1操作。
eg:大小写都包含的字符串转成全小写且排好序
void countingSort(std::vector<int>& arr) {
int maxVal = *max_element(arr.begin(), arr.end()); // 获取最大值
// 创建计数数组并初始化为0
std::vector<int> count(maxVal + 1, 0);
// 计算每个元素的出现次数
for (int val : arr) {
count[val]++;
}
// 重新生成排序后的数组
int index = 0;
for (int i = 0; i <= maxVal; i++) {
for (int j = 0; j < count[i]; j++) {
arr[index++] = i;
}
}
}