冒泡排序
思想: 两两比较,大的往后移,每轮确定一个最大值。(一个一个交换)
对于数组a[ ],每次确定操作就是从左往右扫描,如果a[ i ]>a[ i+1 ],就执行 swap(a[ i ],a[ i+1 ])将两项交换,然后再往右检查,这样可以找出最大的并将其丢到最右边。
- 第一次确定操作是将
a[1]~a[n]中最大的放到a[n]; - 第二次确定操作是将
a[1]~a[n-1]中最大的放到a[n-1]。 - ...
复杂度:O(n²)
模板:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 9;
int a[N];
int main()
{
int n;cin >>n;
for(int i = 1;i <= n; ++i)cin >> a[i];
//i表示当前要确定的位置,从右往左
for(int i = n;i >= 1; -- i)
{
//j从左往右扫,最后一个j+1就到i了
for(int j = 1;j <= i - 1; ++ j)
{
if(a[j] > a[j + 1])swap(a[j], a[j + 1]);
}
}
//输出
for(int i = 1;i <= n; ++ i)cout << a[i] << " \n"[i == n];
return 0;
}
外层循环
i:从n递减到1,表示当前要确定最大值的位置(即右边界)。内层循环
j:从1遍历到i-1,比较相邻元素并交换,将当前最大值 "冒泡" 到i位置。输出部分 :
" \n"[i == n]表示最后一个元素后输出换行,其余元素后输出空格。
选择排序
思想 :每轮找出最小值,直接交换到前面;或者每轮找出最大值,直接交换到末尾。(直接选)
对于数组a[ ],每次确定操作(假设当前要确定的是i位置)就是从左往右扫描,计算出最大元素的下标 mid_id,最后执行一次 swap(a[ max_id ],a[ i ]) 将两项交换即可。
复杂度:O(n²)
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 9;
int a[N];
int main()
{
int n;cin >>n;
for(int i = 1;i <= n; ++i)cin >> a[i];
//i表示当前要确定的位置
for(int i = n;i >= 1; -- i)
{
int max_id = 1;//初始化为1
//j从左往右扫求出max_id
for(int j = 1;j <= i; ++ j)
{
if(a[j] > a[max_id])max_id = j;
}
swap(a[max_id], a[i]);
}
//输出
for(int i = 1;i <= n; ++ i)cout << a[i] << " \n"[i == n];
return 0;
}
对比冒泡:冒泡是相邻交换 "冒泡",选择是先找最值再一次交换。
循环细节 :外层i从n递减到1,内层j遍历[1,i]找最大值下标。
插入排序
思想 :将待排序的元素逐个插入到已排序序列的合适位置中。
复杂度:O(n²)
模板:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 9;
int a[N];
int main()
{
int n; cin >> n;
for(int i = 1; i <= n; ++i) cin >> a[i];
// i表示当前要确定的位置(一个数字默认有序)
for(int i = 2;i <= n; ++ i)
{
//此时[1, i - 1]已经为有序的数组,j表示要插入的位置
int val = a[i], j;
//将val和a[j - 1]比较,如果val小,就将a[j - 1]往后移动一格,给val留出位置
for(j = i;j > 1 && val < a[j - 1]; -- j)a[j] = a[j - 1];
//当循环跳出时,j = 1或val >= a[j],且原本的a[j]已经往后移动,此时的j为给val腾出的位置
a[j] = val;
}
for(int i = 1; i <= n; ++i) cout << a[i] << " ";
return 0;
}
快速排序
思想 :选一个基准数,数组中小于基准的都要放左边,大于基准的放右边,再递归两边。
通过不断地将数组分成两个子数组,递归地对子数组进行排序,最终得到一个有序的数组。这个过程通过选择合适的基准和分区操作来实现。不需要格外的空间。
复杂度:O(n log n)
模板:
快速排序的递归主体QuickSort()
// 快速排序的递归主体QuickSort()。
// 传入参数要排序的数组和区间的左右端点。
void QuickSort(int a[], int l, int r)
{
// l=r表示单个元素区间,l>r表示空区间,它们之间是相互独立的
if(l < r)
{
// Partition函数会将数组a[l]~a[r]这个区间中某个基准数字放到正确的位置并将这个位置返回。
int mid = Partition(a, l, r);
// 在确定了mid的位置之后,可以保证a[l]~a[mid-1]都 < a[mid] < a[mid+1]~a[r],只需要将左右两边分别向下递归地排序即可。
QuickSort(a, l, mid - 1);
QuickSort(a, mid + 1, r);
}
}
分区函数 partition()
// 分区函数:将基准放到正确位置,返回基准下标
// 传入数组以及想要排序的区间范围(默认升序)
int partition(int a[], int l, int r)
{
// 先设置a[r]为基准
int pivot = a[r];
// 设左指针i,右指针j,分别从l, r开始往中间走
int i = l, j = r;
while(i < j)
{
// 左指针i找比pivot大的
while(i < j && a[i] <= pivot) i++;
// 循环出来后有i >= j 或 找到在左区间但是比pivot大的(说明找到了需要交换的位置)
while(i < j && a[j] >= pivot) j--;
// 循环出来后有i >= j 或 找到在右区间但是比pivot小的(说明找到了需要交换的位置)
// 如果i < j说明没相遇,就把左右区间的这两个数交换;相遇了说明左右两边分区的其他元素都分配好了,剩下这个是大于pivot的数,将这个数跟基准数交换,基准数就被放到正确位置上了
if(i < j) swap(a[i], a[j]);
else swap(a[i], a[r]);
}
return i;//返回基准下标
}
归并排序
思想 :先把数组拆成两半,分别递归排好,再将两边按大小合并到新数组中,最后把新数组复制回原数组。
先把无序数组从中间不断递归拆分成更小的子数组,直到每个子数组只剩一个元素(单个元素天然有序),然后从最底层开始逐层往上合并------ 合并时用双指针分别遍历两个有序子数组,每次取出较小的元素按顺序放入临时数组,等两个子数组全部合并完成后,再把有序结果复制回原数组,直到整个数组最终合并为一个完整的有序数组,整个过程稳定且时间复杂度为 O (n log n)
复杂度:O(n log n)
模板:
归并排序的递归主体MergeSort()
// 归并排序的递归主体MergeSort()。
// 传入要排序的数组和区间的左右端点。
void MergeSort(int a[], int l, int r)
{
// 递归结束条件:当区间大小为1时,直接返回即可。
if(l == r)return;
// 找到中间位置(默认向下取整)
int mid = (l + r) / 2;
//递归排序
MergeSort(a, l, mid);
MergeSort(a, mid + 1, r);
// 排序完成后a[l, mid]和a[mid + 1, r]都是分别有序的
// 将a[l, r]两部分一个个地放入到b[l, r]
int pl = l, pr = mid + 1, pb = l;
while(pl <= mid || pr <= r)
{
if(pl > mid)
{
// 左半边已经放完,剩下的b全放右边的
b[pb ++] = a[pr ++];
}
else if(pr > r)
{
// 右半边已经放完,剩下的b全放左边的
b[pb ++] = a[pl ++];
}
else
{
// 两边都还有元素,取个小的放到b里
if(a[pl] < a[pr])b[pb ++] = a[pl ++];
else b[pb ++] = a[pr ++];
}
}
// 完成后复制回去
for(int i = l;i <= r; ++ i)a[i] = b[i];
}
桶排序
思想: 把数字按大小分到不同 "桶" 里,每个桶内部再简单排一下,最后按桶顺序倒出来,就是有序数组。适合数据量大但是值域小(<=1e6)的数据。
复杂度: O(n)
模板:
每个桶对应一个数值
#include <bits/stdc++.h>
using namespace std;
const int MAX = 500010; // 数据最大值(根据题目改)
int cnt[MAX]; // 桶数组:记录每个数出现次数
int main()
{
int n;cin >> n;
// 1. 统计每个数字出现次数
for (int i = 1; i <= n; i++) {
int x;cin >> x;
cnt[x]++;
}
// 2. 从小到大输出(排序完成)
for (int i = 0; i < MAX; i++) {
while (cnt[i]--) { // 出现几次就输出几次,没出现不会输出
cout << i << " ";
}
}
return 0;
}
每个桶对应一段区间,复杂度与每个桶内排序方法有关,不推荐。