目录
前言
排序是数据处理的基本操作之一。在计算机科学里面,排序是算法的重要地基,排序后的数据更易于处理和查找。 在本篇文章中,我将会简要的介绍一下十大排序算法,通过学习排序算法,我们可以得到思路的扩充,算法效率的优化,算法的稳定性,以及在不同场景的合适选择。
一、冒泡排序(Bubble Sort)
冒泡排序又称为沉淀排序,进行n次操作,每次将”最大“(”最小“)的数放到最底部,然后对剩余的无序数组继续冒泡,直到最后变成有序数组,冒泡排序是稳定排序。冒泡排序小步骤优化便是,设置一个标志,用来判断剩余序列是否为有序,是的话便可直接退出,不是的话继续进行冒泡操作。
1.原理:
- 重复遍历序列,依次比较相邻的两个元素,如果顺序颠倒则进行交换。
- 每次遍历会将最大(最小)的元素”冒泡“到序列的末尾。
2.效率:
- 时间复杂度:最好情况为O(n),即该序列已经是有序的。最坏情况是O(n²) 。平均为O(n²)。
- 空间复杂度:O(1)。
3.适用场景:
- 数据量小,且部分有序的序列
4.代码实现:
void Bubble_Sort(std::vector<int> &vec){
int n = vec.size();
//设置一个布尔函数判断剩余序列是否有序
bool swapped;
//i < n -1 是因为判断条件是将当前与下一个进行比较,因此用不到vec[n - 1]来比较
for(int i = 0 ; i < n - 1 ; i ++){
swapped = false;
//该循环j < n - i - 1是因为已经将最大的放到末尾之后便不用在与之比较了
for(int j = 0 ; j < n - i - 1 ; j ++){
if(vec[j] > vec[j + 1])
std::swap(vec[j] , vec[j + 1]) , swapped = true;
}
if(swapped) break;
}
}
//std::vector<int> &vec 是传入一个以vector<int>为类型的数组
二、选择排序(Selection Sort)
选择排序即为暴力的排序方法,该排序最简单、直观,易于理解,对于初学者来说是了解排序的良好教程。学习选择排序就要知道排序的目的是什么?通过字面意思便也可以知道,排序便是将n个杂乱无章的数放到它们应该待在的位置。言简意赅,选择排序便是对一个长为n的数组遍历两遍,它没有”冒泡“排序”聪明“,可以说是”死脑筋“,但是它简单易写,且不占用额外空间。对于稳定性来说,如果要求稳定排序的话,其比较规则就需要进行改变,等下在代码实现里面会体现出来。
1.原理:
- 每次从未排序部分选择最大(小)的元素,放到已排序部分的末尾。
2.效率:
- 时间复杂度:最好、最坏、平均情况均为O(n²)。
- 空间复杂度:O(1)。
3.适用场景:
- 数据量小,且内存有限的情况。
4.代码实现:
void Selection_Sort(std::vector<int>& vec){
int n = vec.size();
//i < n -1 是因为判断条件是将当前与之后剩下的进行比较,因此用不到vec[n - 1]来比较
for (int i = 0; i < n - 1; i ++){
//本次排序从小到大排
int ma = i;
//暴力排序,直接遍历之后的即可,遇到符合条件的让ma坐标等于其即可
for (int j = i + 1; j < n; j ++)
//如果是不稳定排序,其条件是vec[ma] < vec[j]
if (vec[ma] <= vec[j]) ma = j;
std::swap(arr[i], arr[ma]);
}
}
三、插入排序(Insertion Sort)
插入排序是一种”动态的算法“,在一个有序数列上依次增加数据,当增添一个数据x之后,把他插入有序数列里面中合适的位置,使得数列依旧有序。在初始的时候数列为空数列,在不断输入数据之后,使其一直保持有序性,即为插入排序。因此该数列是稳定的,且任何时候都是有序的。
1.原理:
-
将未排序部分的元素逐个插入到已排序部分的正确位置。
2.效率:
- 时间复杂度:最好情况为O(n),即该序列已经是有序的。最坏情况是O(n²) 。平均为O(n²)。
- 空间复杂度:O(1)。
3.适用场景:
- 数据量小,且部分有序的情况。
4.代码实现:
void Insertion_Sort(std::vector<int>& vec) {
int n = vec.size();
for (int i = 1; i < n; i++){
//key 保存下当前数值
int key = vec[i];
//令 j = i - 1 从后往前查找,这样方便后移
int j = i - 1;
while (j >= 0 && vec[j] > key) {
//如果符合条件就将该数后移,不符合即为找到插入位置了
vec[j + 1] = vec[j];
j--;
}
//直接在该位置赋值即可实现插入操作
vec[j + 1] = key;
}
}
/*本文采用的是暴力查找,也可以使用二分查找,找到合适位置进行插入,
但是,但是,但是,最重要的是关于位置后移之后腾位置这一步,如果使用
连续存储便要全部后移,这样也会增加时间复杂度,最好是使用链表进行存储。
*/
四、归并排序(Merge Sort)
归并排序是分治法的运用之一,对于归并排序,我将其步骤分为三部曲,分别是分解、递归和合并,并且归并排序是稳定排序,对于初学者,可以通过画图来模拟全过程,更容易理解这一排序。
1.原理:
- 分解:把初始序列分成两个长度相同的左右子序列。
- 递归:利用递归的方法将分解后的序列继续分解,直到当前子序列只剩下一个数
- 合并:也可以称为”分解的回溯“,不断将最末端的子序列合并,并且从小到大(从大到小)排序,使得合并后的序列便是有序的。
2.效率:
- 时间复杂度:最好、最坏、平均情况均为O(n log n)。
- 空间复杂度:O(1)。
3.适用场景:
- 大规模数据,尤其是需要稳定排序的时候,条件优于快排。
4.代码实现:
void Merge(std::vector<int>& vec, int left, int mid, int right) {
//创建一个temp容器作为中间过渡
std::vector<int> temp(right - left + 1);
int i = left, j = mid + 1, k = 0;
//排序,将小的先放进去,实现从小到大
while (i <= mid && j <= right) {
if (vec[i] <= vec[j]) temp[k++] = vec[i++];
else temp[k++] = vec[j++];
}
//分别遍历剩余的两个序列,将其余剩下的纳入temp之后
while (i <= mid) temp[k++] = vec[i++];
while (j <= right) temp[k++] = vec[j++];
//将排好序的序列重新放回vec中
for (int p = 0; p < k; p++)
vec[left + p] = temp[p];
}
void Merge_Sort(std::vector<int>& vec, int left, int right) {
//找中间点
if (left >= right) return;
int mid = left + (right - left) / 2;
//分解
Merge_Sort(vec, left, mid);
Merge_Sort(vec, mid + 1, right);
//合并
Merge(vec, left, mid, right);
}
五、快速排序(Quick Sort)
快速排序也同样是基于分治法,选择一个基准元素,将序列分成两部分,左边小于基准,右边大于基准,然后递归排序。一般来说,基准选择第一个,中间,或者最后。利用双指针的方法,先将基准的数取出放入临时存储数空间(t) 里面,然后从基准右边找到第一个比基准小的数,放到基准的位置,然后从基准的左边找到第一个比基准大的数,放到刚取数的那个位置,以此类推,进行递归,最后先以同样的方式进行第二轮遍历,直到全部排完序,并且这是一个不稳定排序。
1.原理:
- 选基准,先挑选基准右边第一个比基准小的数,放入基准的位置,然后挑选基准左边第一个比基准大的数,放入基准右边被取出数的位置。
- 递归重复,直到排完序。
2.效率:
- 时间复杂度:最好情况为O(n log n) , 最坏情况是O(n²) 。平均为O(n log n)。
- 空间复杂度:O(n log n)。
3.适用场景:
-
大规模数据,尤其是对稳定性没有要求的场景。
4.代码实现:
void Quick_Sort(std::vector<int> & vec , int l , int r){
//本次快排选择中间为基准
int i = l , j = r , mid;
//位运算,可以直接实现除二的功效
mid = l + r >> 1;
int key = vec[mid];
//双指针算法
while(i <= j){
//左边找到第一个比key大的数
while(vec[i] < key) i++;
//右边找到第一个比key小的数
while(vec[j] > key) j--;
//如果满足i > j即为已经排好序,如果没有则必然有需要交换的位置,则进行交换
if(i <= j){
std::swap(vec[i] , vec[j]);
++i;
--j;
}
}
//递归进入下一段,左边一段和右边一段,直到子序列已经排好序
if(j > l) Quick_Sort(l , j);
if(i < r) Quick_Sort(i , r);
}
总结:
本片文章为十大排序算法——选择合适的排序(上),主要分享了冒泡排序,选择排序,插入排序,归并排序和快速排序的基本实现原理,对它们的功能实现进行了详细的分析,并且讲述了其代码实现和使用场景,希望大家能给予鼓励和支持,预计在明天晚上将继续分享十大排序算法——选择合适的排序(下)。如果看到这里了,那就点个赞支持一下,创作不易,谢谢!