1.STL sort
任何排序算法的时间复杂度不会低于。
sort函数不会保证相等的元素保持原来的次序,如果有这样的需求,要用stable_sort(耗时比sort长)。
将元素按降序排列的方法:sort(v.begin(),v.end(),greater
2.冒泡排序
输入:n,n个整数
输出:按升序排列的n个整数
从第一个数开始,每个数和后一个数比,如果它比后一个数大,则交换。这样循环一遍就会把最大的数移动到最后面,依次类推即可完成排序。
#include
using namespace std;
int a[100];
int main() {
int n;
cin >> n;
int t;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n - i; j++) {
if (a[j] > a[j + 1]) {
t = a[j];
a[j] = a[j + 1];
a[j + 1] = t;
}
}
}
for (int i = 1; i <= n; i++) cout << a[i] << " ";
return 0;
}
时间复杂度:
3.选择排序
在未排序序列中找到最小元,和第一个元素交换,直到所有元素都已排序。
#include
using namespace std;
int n;
int a[100];
int min;
int t;
int main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) {
min = i;
for (int j = i + 1; j <= n; j++) {
if (a[j] < a[min]) min = j;
}
if (min != i) {
t = a[min];
a[min] = a[i];
a[i] = t;
}
}
for (int i = 1; i <= n; i++) cout << a[i] << ' ';
return 0;
}
时间复杂度:
4.插入排序
将未排序序列的每一个元素同已经排好序的序列的每一个元素比较,并把它们插入到适当的位置。
#include
using namespace std;
int n;
int a[100];
int key;
int j;
int main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 2; i <= n; i++) {
key = a[i];
for (j = i - 1; j >= 1 && key < a[j]; j--) a[j + 1] = a[j];
a[j + 1] = key;
}//假设1-i-1的元素已经排好了,将key插到比它小的元素后面
for (int i = 1; i <= n; i++) cout << a[i] << ' ';
return 0;
}
时间复杂度:
5.希尔排序
增量序列:,其中的递增序列。
分别对原序列每隔个元素做插入排列,使其每隔个元素提取出来的子序列升序,直到对每个相邻元素都做插入排列。
希尔增量:从开始,每次的间隔是前一次的一半,时间复杂度:。
#include
using namespace std;
int main() {
int n;
int a[100];
int key;
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int gap = n / 2; gap >= 1; gap /= 2) { //间隔从n/2到1
for (int i = gap + 1; i <= n; i++) { //前gap个元素相当于插入排序的第一个元素
key = a[i];
int j;
for (j = i - gap; j >= 1 && a[j] > key; j -= gap) a[j + gap] = a[j];
a[j + gap] = key;
}
}
for (int i = 1; i <= n; i++) cout << a[i] << ' ';
return 0;
}
Hibbard增量:,时间复杂度:
#include
#include
using namespace std;
int main() {
int n;
int a[100];
int key;
int gap;
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
int maxk = log2(n + 1);
for (int k = maxk; k >= 1;k--) {
gap = pow(2, k) - 1;
for (int i = gap + 1; i <= n; i++) {
key = a[i];
int j;
for (j = i - gap; j >= 1 && a[j] > key; j -= gap) a[j + gap] = a[j];
a[j + gap] = key;
}
}
for (int i = 1; i <= n; i++) cout << a[i] << ' ';
return 0;
}
6.堆排序
由第6章知,建立一个堆所用时间为,N次查找最小元所需时间,故堆排序所需时间为。
但是,堆作为数组,要占O(N)的空间,所以空间复杂度比原来大一倍。
priority_queue实现:
#include
#include
using namespace std;
priority_queue,greater > q;
int main() {
int n;
int key;
int a[100];
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
q.push(a[i]);
}
for (int i = 1; i <= n; i++) {
key = q.top();
q.pop();
a[i] = key;
}
for (int i = 1; i <= n; i++) cout << a[i] << ' ';
return 0;
}
STL heap实现:
make_heap(begin,end,cmp):将数组或者向量做成堆(默认为大根堆)。
pop_heap(begin,end,cmp):将第一个元素和最后一个元素互换,并将[begin,end-1)的元素做成一个新堆。
push_heap(begin,end,cmp):要求[begin,end-1)是一个堆,把end-1加到堆里。
#include
#include
using namespace std;
int a[100];
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++) cin >> a[i];
make_heap(a, a + n, greater());
for (int i = 0; i < n; i++) {
cout << a[0] << ' ';
pop_heap(a, a + n - i, greater());
}
return 0;
}
7.归并排序
对于一个长度为N的序列,递归地将前N/2的元素和后N/2的元素分别排序,再合并起来。
只需要考虑合并两个已经排序的表:和。
分别设置两个指针,一个指向a表,一个指向b表,如果a指针指向的元素更小,则把它填入c表,并向后移动一个元素,直到一个数组的元素被使用完,将另一个数组的剩余元素复制到c表即可。
该算法最多进行次比较,故时间复杂度为。
#include
#include
using namespace std;
int a[100];
int res[100];
void merge(int lstart, int rstart, int rend) { //将左半部分和右半部分合并
int lend = rstart - 1;
int temp = lstart; //相当于指针c
int num = rend - lstart + 1; //元素数
while (lstart <= lend && rstart <= rend) {
if (a[lstart] <= a[rstart])
res[temp++] = a[lstart++];
else
res[temp++] = a[rstart++];
}
while (lstart <= lend) res[temp++] = a[lstart++];
while (rstart <= rend) res[temp++] = a[rstart++];
for (int i = 0; i < num; i++, rend--) {//rend始终没动过,从rend向前数num个数
a[rend] = res[rend];
}
}
void mergesort(int left, int right) {
if (left == right) return;
int center = (left + right) / 2;
mergesort(left, center);
mergesort(center + 1, right);
merge(left, center + 1, right);
}
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++) cin >> a[i];
mergesort(0, n - 1);
for (int i = 0; i < n; i++) cout << a[i] << ' ';
return 0;
}
时间复杂度方程:
用迭代法解得,故时间复杂度为。
空间复杂度为。
归并排序比较的次数少,复制的次数多。
8.快速排序
从序列中挑出一个枢纽元a,将比a小的数放在a的前面,比a大的数放在a的后面,递归地排序a前面的数和a后面的数。
枢纽元的选取:如果选取第一个或最后一个元素作为枢纽元,若序列是反序的,则所有的数都会被放到a的一边,显然不行。好的选取是求第一个元素,中间元素和最后一个元素的中值作为枢纽元。
分割策略:先把枢纽元和最后一个元素交换,使它离开要分割的序列。设两个指针i,j分别从第一个元素和倒数第二个元素开始,当i在j的左边时,将i右移,移过那些比枢纽元小的元素,直到i所指的元素比枢纽元大;同时将j左移,直到j所指的元素比枢纽元小,交换i和j所指的元素。如果i或者j所指向的元素与枢纽元相等,直接交换。直到i和j相邻时过程终止。最后交换j所指向的元素和枢纽元。
可以证明,快排的最坏时间复杂度为,平均时间复杂度为,当N较小的时候,快排不如插入排序好。由于快排的递归性质,一定会处理N较小的情况,所以可以加一个特判:当N小于10时,用插入排序。(见书,这里如果不这样做的话,会出现未知bug)
#include
#include
using namespace std;
int a[100010];
int n;
int choose(int left, int right) { //确保最左边元素比中间元素小,中间元素比最右边元素小
int center = (left + right) / 2;
if (a[center] < a[left]) swap(a[left], a[center]);
if (a[right] < a[left]) swap(a[left], a[right]);
if (a[right] < a[center]) swap(a[center], a[right]);
swap(a[center], a[right - 1]); //把枢纽元放在right-1,因为right比它大
return a[right - 1];
}
void quicksort(int left, int right) {
if (left + 10 <= right) {
int pivot = choose(left, right);
int i = left, j = right - 1;
while (1) {
if (i == n - 1 || j == 0) break;
while (a[++i] < pivot);
while (pivot < a[--j]);
if (i < j) swap(a[i], a[j]);
else break;
}
swap(a[i], a[right - 1]);
quicksort(left, i - 1);
quicksort(i + 1, right);
}
else {
for (int i = left + 1; i <= right; i++) {
int j;
int key = a[i];
for (j = i - 1; j >= left && a[j] > key; j--) a[j + 1] = a[j];
a[j + 1] = key;
}
}
}
int main() {
cin >> n;
for (int i = 0; i < n; i++) cin >> a[i];
quicksort(0, n - 1);
for (int i = 0; i < n; i++) cout << a[i] << ' ';
return 0;
}
洛谷上的一个简化版(直接拿中间元素作为枢纽元):
#include
#include
using namespace std;
int a[100010];
int n;
void quicksort(int left, int right) {
if (left >= right) return;
int mid = a[(left + right) / 2];
int i = left - 1, j = right + 1;
while (1) {
while (a[++i] < mid);
while (a[--j] > mid);
if (i <= j) swap(a[i], a[j]);
else break;
}
quicksort(left, j);
quicksort(i, right);
}
int main() {
cin >> n;
for (int i = 0; i < n; i++) cin >> a[i];
quicksort(0, n - 1);
for (int i = 0; i < n; i++) cout << a[i] << ' ';
return 0;
}
9.桶排序
设N的最大值为M,开一个大小为M的数组a[M](初始化为0),读入i就a[i]++,输出的时候有几个就输出几次。
#include
using namespace std;
int a[1000001];
int main() {
int n;
int temp;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> temp;
a[temp]++;
}
for (int i = 1; i <= 1000001; i++) {
while (a[i]--) {
cout << i << ' ';
}
}
return 0;
}
时间复杂度:
空间复杂度:
10.补充:快速选择
查找第k个数的最优算法,若k<=j,则答案必在前j个数内;否则,在后面的数内。
#include
using namespace std;
int a[100010];
int n;
int k;
int quickselect(int left, int right) {
if (left == right) return a[left];
int mid = a[(right + left) / 2];
int i = left - 1, j = right + 1;
while (1) {
while (a[++i] < mid);
while (a[--j] > mid);
if (i <= j) swap(a[i], a[j]);
else break;
}
if (k <= j) quickselect(left, j);
else if (k > j) quickselect(i, right);
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
cin >> k;
cout<
最坏时间复杂度:
平均时间复杂度: