桶排序
桶排序在需要排序的数字比较小的的时候非常好用的算法,它的时间复杂度达到了 Θ ( n ) \Theta(n) Θ(n)
基本思路就是把每一个数字放到一个以他为下标的数组中,然后输出时如果一个桶中有数就输出,输出的次数就是桶里放的数的个数。
#include<cstdio>
const int maxn = 120000;
int a[maxn];
int capa[5000000];
int main()
{
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
capa[a[i]] ++; //如果输入的是3 , 3为下标的桶 , 即a[3] 的值+1
}
for (int i = 0; i <= 500000; i++)
if (capa[i]) //这个桶的值大于0 即如果这个桶里有数字
for (int j = 1; j <= capa[i]; j++) //capa[i] 的值代表i这个数出现了几次,用循环输出
printf("%d ", i);
return 0;
}
冒泡排序
冒泡排序近似与最朴素的排序算法
每次选定一个数,就与前面的数比较,如果发现这个数更小,就交换。
#include<cstdio>
int a[100000] , n;
int main()
{
scanf("%d", &n);
for(int i = 1 ; i <= n ; i ++)
scanf("%d", &a[i]);
for (int i = 1; i <= n; i++)
for(int j = 1 ; j <= n ; j ++)
if (a[i] < a[j])
{
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
for (int i = 1; i <= n; i++)
printf("%d ", a[i]);
return 0;
}
选择排序
选择排序是遍历序列多次,每一次找到序列中最小的值放在最前面,再对剩余序列进行同样操作。复杂度为
Θ
(
(
1
+
n
)
∗
n
2
)
=
Θ
(
n
2
)
\Theta(\frac{(1+n)*n}{2})=\Theta(n^2)
Θ(2(1+n)∗n)=Θ(n2) 但是比朴素的冒泡排序快。
#include<iostream>
#include<cstdio>
using namespace std;
const int maxn = 233333;
int n , a[maxn];
inline int read()
{
int x = 0 , f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') { x = x * 10 + ch - 48; ch = getchar(); }
return x * f;
}
int main()
{
n = read();
for (int i = 1; i <= n; i++) a[i] = read();
for (int i = 1; i <= n; i++) //对从i号元素开始的序列进行操作
{
int mx = 1e9 + 1; //最小值
int pt = 0; //最小值的位置
for (int j = i; j <= n; j++) //从i号元素开始往后找
if (a[j] < mx) //如果i号后面的j号元素更小
{
mx = a[j]; //更新最小值
pt = j; //更新最小值的位置
}
swap(a[i] , a[pt]); //交换最小值和i号元素
}
for (int i = 1; i <= n; i++) printf("%d " , a[i]);
}
插入排序
插入排序的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
#include<cstdio>
const int maxn = 233333;
int n;
int a[maxn];
int main()
{
scanf("%d" , &n);
for (int i = 1; i <= n; i++) scanf("%d" , &a[i]);
for (int i = 2; i <= n; i++)
{
int x = a[i];
if (x >= a[i - 1]) continue; //如果这个数大于前面的数,说明从这个数往前的序列是有序的
//直接进行下一次循环
for (int j = i - 1; j >= 0; j--)//如果这个数小于前面的数
{
if (x < a[j]) a[j + 1] = a[j]; //如果这个数小于前面的数,前面的数需要给其让位,往后移一格
else//往前走的过程中终于比前面的数大了,由于前面的数给其让位,直接将数填到这个位置即可
{
a[j + 1] = x;
break;
}
}
}
for (int i = 1; i <= n; i++) printf("%d " , a[i]);
}
快速排序
快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
1、从数列中挑出一个元素,称为 “基准”(pivot);
2、重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
3、递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
快速排序的时间复杂度最好情况是
Θ
(
n
l
o
n
g
n
)
\Theta(nlongn)
Θ(nlongn)但是有些情况可以被卡到
Θ
(
n
2
)
\Theta(n^2)
Θ(n2)
所以说快速排序并不稳定
这里的基准数是随机选择的
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 300000;
int n;
int a[maxn];
inline int read()
{
int x = 0 , f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') { x = x * 10 + ch - 48; ch = getchar(); }
return x * f;
}
inline void quicksort(int l , int r)
{
if (l >= r) return;
int x;
while (1) x = rand() % (r - l + 1) + l; //随机选择基准数
swap(a[x] , a[l]); //将基准数放到序列最前面
x = a[l];
int lp = l , rp = r;
while (lp < rp)
{
while (a[rp] >= x && lp < rp) rp--; //右指针如果碰不到比基准数小的数就一直跑
//如果碰到了就停止
while (a[lp] <= x && lp < rp) lp++; //左指针如果碰不到比基准数大的数就一直跑
//如果碰到了就停止
swap(a[lp] , a[rp]);//交换左右指针的值
}
swap(a[l] , a[rp]);//交换基准数和右指针,保证了基准数左边的数全部不大于他,右边的数全不小于他
quicksort(l , rp - 1);
quicksort(rp + 1 , r); //继续快速排序基准数两边的无序区间
}
int main()
{
n = read();
for (int i = 1; i <= n; i++) a[i] = read();
quicksort(1 , n);
for (int i = 1; i <= n; i++) printf("%d " , a[i]);
}
归并排序
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
1、把长度为n的输入序列分成两个长度为n/2的子序列;
2、对这两个子序列分别采用归并排序;
3、将两个排序好的子序列合并成一个最终的排序序列。
#include <iostream>
#include<cstdio>
using namespace std;
inline int read()
{
int x = 0 , f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') { x = x * 10 + ch - 48; ch = getchar(); }
return x * f;
}
const int maxn = 300001;
int n;
int a[maxn] , tmp[maxn];
inline void merge(int l , int len) //我的写法是传入区间的左端点和长度
{
if (len == 1) return;
int llen = len / 2; //处理左子区间的长度
merge(l , llen);
merge(l + llen , len - llen);
int num = 0;
int lp = l , rp = l + llen; //处理左子区间指针和右子区间指针
while (num < len)
{
if (rp == l + len && lp < l + llen || lp <= l + llen - 1 && a[lp] <= a[rp]) tmp[++num] = a[lp++];
if (lp == l + llen && rp < l + len || rp <= l + len - 1 && a[rp] <= a[lp]) tmp[++num] = a[rp++];
}
//并的时候针对两种情况
///一是某一边的指针已经到头,另一边的指针还没到头,把另一边指针的值加到临时数组里,同时它的指针前移
//另一个是两边指针都没到头,这边指针的值小于那一边的值,把这边指针的值放到临沭数组里,同时它的指针前移
//排完之后临时数组是有序的
//将临时数组里的值重新赋给数组a 此时在区间[l , l + len]中,a数组是有序的
for (int i = 1; i <= num; i++) a[l + i - 1] = tmp[i];
}
int main()
{
n = read();
for (int i = 1; i <= n; i++) a[i] = read();
merge(1 , n);
for (int i = 1; i <= n; i++) printf("%d " , a[i]);
}