目录
算法分类
常见排序算法可以分为两大类:
1、比较类排序
通过比较来决定元素间相对次序,由于时间复杂度不能突破O(n * log n),
也称非线性时间比较类排序。
2、非比较类排序
不通过比较来决定元素间的相对次序,可以突破基于比较类排序的时间下界,
以线性时间运行,也称为线性时间非比较类排序。
1、冒泡排序(稳定)
1.1、思路描述
每一趟只能将一个数归位:每次循环比较前后两个元素大小,如果前者大于后者,则两者进行交换。结果是每次循环中最大的元素替换到末尾,经过 n - 1趟最终得到一个有序集合。
1.2、代码实现
//实现从小到大的排序
void bubble_sort(vector<int>& vec){
for(int i = 0; i < vec.size() - 1; ++i){
bool flag = false;
for(int j = 0; j < n - 1 - i; ++j){
if(vec[j] > vec[j + 1]){
int t = vec[j];
vec[j] = vec[j + 1];
vec[j + 1] = t;
flag = true;
}
}
if(!flag)
break;
}
}
1.3、时间空间复杂度
最好情况,数组已经是排好序的状态,时间复杂度为O(n);
最坏情况,数组是从大到小的情况,时间复杂度为O(n^2);
平均情况,数组是无序的,时间复杂度为O(n^2);
空间复杂度为O(1)。
2、快速排序(不稳定)
2.1、思路描述
在一个无序数组中取一个数key,每一趟排序实现:key左边的数都小于它,右边的数都大于它;然后左右两个区间重复这个过程,直到各个区间只有一个数。
2.2、代码实现
void quick_sort(vector<int>& vec, int left, int right){
if(left > right)
return ;
int i = left, j = right;
int temp = vec[left];
while(i != j){
while(vec[j] >= temp && i < j){
j--;
}
while(vec[i] <= temp && i < j){
i++;
}
if(i < j){
swap(vec[i], vec[j]);
}
}
swap(vec[left], vec[i]);
quick_sort(vec, left, i - 1);
quick_sort(vec, i + 1, right);
}
2.3、时间空间复杂度
最好情况,每次把数组都平分为两个部分,时间复杂度为O(n*log n);
最坏情况,每次只有左半边或右半边,时间复杂度为O(n^2);
平均情况,时间复杂度为O(n*log n);
空间复杂度为O(n * log n)。
3、插入排序(稳定)
3.1、思路描述
从第一个元素开始,该元素可以认为已经被排序;取出下一个元素,在已经排序的元素序列中从后往前扫描;如果新元素小于扫描到的元素,则将该元素移动到前一个位置(重复该步骤直到找到正确位置)。重复前面的步骤。
3.2、代码
void insert_sort(vector<int>& vec){
for(int i = 0; i < vec.size(); ++i){
int temp = vec[i];
int j = i;
while(vec[j - 1] > vec[j] && j > 0){
vec[j] = vec[j - 1];
j--;
}
vec[j] = temp;
}
}
3.3、时间空间复杂度
最好情况,数组是正序的,时间复杂度是O(n);
最坏情况,数组是倒序的,时间复杂度是O(n^2);
平均情况,时间复杂度为O(n ^ 2);
空间复杂度为O(1).
4、希尔排序(不稳定)
4.1、思路描述
根据间隔d,把间隔为d的元素都划分到一个组内,先让组内有序,刚开始 d = n / 2,接着d = n / 4,让d一直缩小,当d = 1时,此时数组中任意间隔为1的元素都有序,此时数组就是有序的。
4.2、代码
void shell_sort(vector<int>& vec) {
int n = vec.size();
for (int d = n / 2; d > 0; d /= 2) {
for (int i = d; i < n; ++i) {
int temp = vec[i];
int j = i - d;
while (j >= 0 && temp < vec[j]) {
vec[j + d] = vec[j];
j -= d;
}
vec[j + d] = temp;
}
}
}
4.3、时间空间复杂度
最好情况,时间复杂度为O(n);
最坏情况,时间复杂度为O(n ^ 2);
平均情况,时间复杂度为O(n ^ 1.3)
空间复杂度为O(1).
5、选择排序(不稳定)
5.1、思路描述
扫描整个数组,找到最小元素,将这个元素与第一个元素进行交换;之后从第二个位置开始扫描整个数组,找到最小元素,将这个元素与第二个元素进行交换;持续找出最小元素直到n - 1 个元素。即每一趟在n - i + 1(i = 1, 2, 3, ... , n - 1)个元素中选择最小的元素,并将其作为有序序列中第i 个元素。
5.2、代码
void select_sort(vector<int>& vec){
for(int i = 0; i < vec.size() - 1; ++i){
int minIndex = i;
for(int j = i + 1; j < vec.size(); ++j){
minIndex = vec[minIndex] < vec[j] ? minIndex : j;
}
int t = vec[i];
vec[i] = vec[minIndex];
vec[minIndex] = t;
}
}
5.3、时间空间复杂度
最好情况,最坏情况,每轮筛选出最小值都需要O(n),一共迭代 n - 1 次,
因此,时间复杂度都为O(n ^ 2);
空间复杂度为O(1).
6、堆排序(不稳定)
6.1、思路描述
堆的特点就是堆顶元素是一个最值,大顶堆堆顶是最大值,小顶堆堆顶是最小值。堆排序就是把堆顶的元素与最后一个元素交换,交换之后破坏了堆的特性,我们再把堆中剩余的元素 构成一个新的大顶堆,然后把堆顶元素与倒数第二个元素交换,一直迭代下去,等元素只有一个 的时候,数组就是有序的。
6.2、代码
void heap_adjust(vector<int>& vec, int start, int end){
int temp = vec[start];
for(int i = start * 2 + 1; i <= end; i = i * 2 + 1){
if(i < end && vec[i] < vec[i + 1]){
i++;
}
if(vec[i] > temp){
vec[start] = vec[i];
start = i;
}else{
break;
}
}
vec[start] = temp;
}
void heap_sort(vector<int>& vec){
int n = vec.size();
//建立大根堆,从后往前依次调整
for(int i = (n - 1 - 1) / 2; i >= 0; i--){
heap_adjust(vec, i, n - 1);
}
//每次将根和待排序最后一次交换,然后再调整
int temp;
for(int i = 0; i < n - 1; i++){
temp = vec[0];
vec[0] = vec[n - i - 1];
vec[n - i - 1] = temp;
heap_adjust(vec, 0, n - 1 - i - 1)
}
}
6.3、时间空间复杂度
最好情况,最坏情况下,时间复杂度都是O(n log n);
空间复杂度为O(1).
7、归并排序(稳定)
7.1、思路描述
分治思想,将大问题拆解成小问题。将一个大的数组拆分成几个小的数组(排序),然后一点点的合并。
7.2、代码
void merge_sort(vector<int>& vec, int left, int right){
if(left >= right)
return ;
int mid = (right - left) / 2 + left;
merge_sort(vec, left, mid);
merge_sort(vec, mid + 1, right);
Merge(vec, left, mid, right);
}
7.3、时间空间复杂度
时间复杂度:O(n * log n)
空间复杂度:O(n)
8、计数排序(稳定)
8.1、思路描述
根据待排序数组中最大元素和最小元素的差值范围,申请额外空间;遍历待排序,将每个元素出现的次数记录到元素值对应的额外空间内;对额外空间内数据进行计算,得出每一个元素的正确位置;将待排序数组每一个元素移动到计算出得到的正确位置上。
8.2、代码
void count_sort(vector<int>& vec){
int max = vec[0];
for(auto &v : vec){
if(v > max)
max = v;
}
vector<int>arr(max + 1);
for(int i = 0; i < vec.size(); ++i){
arr[vec[i]]++;
}
int j = 0;
for(int i = 0; i < arr.size(); ++i){
for(int k = 0; k < arr[i]; ++i){
vec[j++] = i;
}
}
}
8.3、时间空间复杂度
时间复杂度:O(n + k)
空间复杂度:O(k)
9、桶排序(稳定)
9.1、思路描述
设置一个定量的数组当作空桶,遍历输入数组数据,并把数据一个一个放到对应的桶里面;对每个不是空的桶进行排序;从不是空桶里把排好序的数据拼接起来。
9.2、代码
void BucketSort(vector<int> &vec) {
int n = vec.size();
// 得到数列的最大最小值
int max = vec[0], min = vec[0];
for(int i = 1; i < n; ++i) {
if(vec[i] > max)
max = vec[i];
if (vec[i] < min)
min = vec[i];
}
// 计算桶的数量并初始化
int bucketNum = (max - min) / n + 1;
vector<int> temp;
vector<vector<int>> bucket;
for (int i = 0; i < bucketNum; ++i)
bucket.push_back(temp);
// 将每个元素放入桶
for (int i = 0; i < n; ++i) {
// 减去最小值,处理后均为非负数
int pos = (vec[i] - min) / len;
bucket[pos].push_back(vec[i]);
}
// 对每个桶进行排序,此处可选择不同排序方法
for (int i = 0; i < bucket.size(); ++i)
sort(bucket[i].begin(), bucket[i].end());
// 将桶中的元素赋值到原序列
int index = 0;
for (int i = 0; i < bucketNum; ++i)
for(int j = 0; j < bucket[i].size(); ++j)
vec[index++] = bucket[i][j];
}
9.3、时间空间复杂度
最坏情况,时间复杂度为O(n ^ 2);
最好情况,时间复杂度为O(n );
平均情况,时间复杂度为O(n + k);
空间复杂度:O(k)
10、基数排序(稳定)
10.1、思路描述
先以个位数的大小对数据进行排序,接着以十位数的大小来多数进行排序,排到最后,就是一组有序的元素了。在以某位数进行排序的时候,是用“桶”来排序的。
10.2、代码
void RadixSort(vector<int> &vec) {
int n = vec.size();
// 得到数列的最大值
int max = vec[0];
for (int i = 1; i < n; ++i) {
if(vec[i] > max)
max = vec[i];
}
// 计算最大值是几位数
int digits = 1;
while (max / 10 > 0) {
digits++;
max /= 10;
}
// 创建10个桶
vector<int> temp;
vector<vector<int>> bucket;
for (int i = 0; i < 10; ++i) {
bucket.push_back(temp);
}
// 进行每一趟的排序,从个位数开始排
for (int i = 1; i <= digits; i++) {
for (int j = 0; j < n; j++) {
// 获取每个数最后第 i 位对应桶的位置
int radio = (num[j] / (int)pow(10,i-1)) % 10;
// 放进对应的桶里
bucket[radio].push_back(num[j]);
}
// 合并放回原数组
int k = 0;
for (int j = 0; j < 10; j++) {
for (int& t : bucket[j]) {
vec[k++] = t;
}
//合并之后清空桶
bucket[j].clear();
}
}
}
10.3、时间空间复杂度
时间复杂度:O(k * n)
空间复杂度:O(k + n)
排序算法总结
排序算法 | 最好情况 | 最坏情况 | 平均时间复杂度 | 空间复杂度 | 稳定性 |
冒泡排序 | O(n ) | O(n ^ 2) | O(n ^ 2) | O(1) | 稳定 |
快速排序 | O(n log n) | O(n ^ 2) | O(n log n) | O(n log n) | 不稳定 |
插入排序 | O(n ) | O(n ^ 2) | O(n ^ 2) | O(1) | 稳定 |
希尔排序 | O(n) | O(n ^ 2) | O(n ^ 1.3) | O(1) | 不稳定 |
选择排序 | O(n ^ 2) | O(n ^ 2) | O(n ^ 2) | O(1) | 不稳定 |
堆排序 | O(n log n) | O(n log n) | O(n log n) | O(1) | 不稳定 |
归并排序 | O(n log n) | O(n log n) | O(n log n) | O(n) | 稳定 |
计数排序 | O(n + k) | O(n + k) | O(n + k) | O(k) | 稳定 |
桶排序 | O(n ) | O(n ^ 2) | O(n + k) | O(k) | 稳定 |
基数排序 | O(n * k) | O(n * k) | O(n * k) | O(n + k) | 稳定 |