排序
排序算法有很多很多种,其中最简单最适合入门的莫过于冒泡排序,所以我们从冒泡排序讲起,逐渐入门排序算法。
- 冒泡排序
冒泡排序的实现步骤十分简单,对于一个数组A,我们从第一个数开始遍历n次,遍历到的数如果比后面一个数大,便交换这个数和后面那个数。
比如一个数组4 2 4 5 1,从第一个数开始,发现4比2大,交换后2 4 4 5 1,遍历到5后发现5比1大,交换后2 4 4 1 5,重新开始从第一个元素开始遍历,
不断交换后2 4 1 4 5->2 1 4 4 5->1 2 4 4 5。
冒泡排序为什么是正确的,实际上每遍历交换一轮都会将不在指定位置的最大的数送到指定位置,比如说上面例子中的5,经过一轮循环后被送到了自己应该呆在的位置上。那么为什么能送到呢?因为不在指定位置的最大的数必然会比自己的后面的数要大,这时候就会进行交换,交换后指针后移又指向这个最大的数,这个数便一直后移到小于等于后一个数为止,这样就到达了指定位置。复杂度O(n²).
代码实现也非常简单:
#include<iostream> using namespace std; int n, a[1000005]; int main() { cin >> n; for (int i = 1; i <= n; i++)cin >> a[i]; for (int i = 1; i < n; i++) { for (int j = 1; j < n - 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] << " ";//输出 return 0; }
之后便是和冒泡排序差不多简单的插入排序。
- 插入排序
每次将一个待排序的记录按其关键字大小插入到前面已排好序的子序列中,直到全部记录插入完成。
#include<iostream> using namespace std; int n, a[1000005]; int main() { cin >> n; for (int i = 1; i <= n; i++)cin >> a[i]; for (int i = 2; i <= n; i++) { int cur = i, ans = a[i]; for (int j = 1; j < i; j++) { if (a[j] > a[i]) { cur = j; break; } } for (int j = i; j >= cur; j--) {//先将要插入位置后面的元素全部后移 a[j] = a[j - 1]; } a[cur] = ans; } for (int i = 1; i <= n; i++)cout << a[i] << " "; return 0; }
- 希尔排序
插入排序之后当然是基于插入排序的希尔排序。希尔排序︰先将待排序表分割成若干形如 L[i,i + d,i + 2d ...., i + kd]的“特殊”子表,对各个子表分别进行直接插入排序。缩小增量d,重复上述过程,直到d=1为止。
#include<iostream> using namespace std; int a[1000005], n; void Shellsort(int len) { int increment = len; while (increment > 1) { increment = increment / 3 + 1;//计算增量 for (int i = 1; i <= increment; i++) {//选择开始的元素 for (int j = i + increment; j <= len; j += increment) {//插入排序 int cur = a[j], ans = i; for (int h = j - increment; h >= 1; h -= increment) { if (a[h] <= cur) { ans = h + increment; break; } } for (int h = j; h >=ans&&h>=increment; h -= increment) a[h ] = a[h - increment]; a[ans] = cur; } } } } int main() { cin >> n; for (int i = 1; i <= n; i++)cin >> a[i]; Shellsort( n); for (int i = 1; i <= n; i++)cout << a[i] << " "; return 0; }
- 简单选择排序
简单选择排序算法和它的名字一样十分的简单,刚开始遍历n个数,找出n个数中最小的数,让它和数组中第一个数交换位置,之后从第二个数开始遍历n-1个数,找出n-1个数中最小的,让它和第2个数交换位置,一次类推进行n-1次后就能形成有序排列。这或许也是大家第一次接触排序自己想到的第一个解决方案。复杂度O(n²)。
#include<iostream> using namespace std; int n, a[1000005]; int main() { cin >> n; for (int i = 1; i <= n; i++)cin >> a[i]; for (int i = 1; i < n; i++) { int minn = 0x7fffffff, cur; for (int j = i; j <= n ; j++) { if (a[j] < minn) { minn = a[j]; cur = j; } } swap(a[i], a[cur]); } for (int i = 1; i <= n; i++)cout << a[i] << " "; return 0; }
- 快速排序
快速排序顾名思义特别的快,时间复杂度能达到O(nlogn)。主要做法是在一个数组中选出一个参照数,一般选第一个,然后用两个指针一个指向开头一个指向结尾,两个指针同时向中间逼近,当右边的指针遇到比参照数小的元素时停止,左边指针遇到比参照数大的元素时停止,交换两个元素位置,之后继续逼近和交换,直到两个指针指到同一个元素时停止,将指向的元素和参照数交换,这样就能实现参照数左边的数都是比参照数小的,参照数右边的数都是比参照数大的,之后依照同样的方法处理参照数的左半部分和右半部分,一直处理到要处理的序列只有一个元素时停止,这时候整个序列就是有序的了。以数组4 2 4 5 1来说,我们先选择第一个数4为参照数,对比右指针所指向的元素1发现比4小,之后依次对比移动左指针4 2 4 5,发现5比4大,交换左右指针所指元素5 ,1得到新序列4 2 4 1 5,之后继续移动右指针到1发现比4小,停止移动。移动左指针也到1,这时候左右指针指向同一元素便可以交换参照数和所指元素得到新序列1 2 4 4 5,以此类推继续处理左半部分1 2 4,右半部分只有一个元素不用处理。参照数为1,右指针一直移动到1,这时候到达边界停止移动。这时候左右指针相等,交换元素,1和自己交换不变。得1 2 4,以此类推处理右半部分2 4,便可得到有序序列。
#include<iostream> using namespace std; int a[1000005], n; void Qsort(int l,int r) { if (l >= r)return; int i = l, j = r; while (i < j) { while (i < j && a[j] >= a[l])j--; while (i < j && a[i] <= a[l])i++; swap(a[i], (i == j) ? a[l] : a[j]); } Qsort(l, i - 1); Qsort(i + 1, r); } int main() { cin >> n; for (int i = 1; i <= n; i++)cin >> a[i]; Qsort(1, n); for (int i = 1; i <= n; i++)cout << a[i] << " "; return 0; }
- 归并排序
了解归并排序算法之前让我们首先考虑两个有序的序列怎么合并成一个有序的序列。比如说序列1 2 5 8和序列3 6 7,想一想其实很简单让两个指针分别指向两个序列的第一个元素,之后比较两个指针指向的元素的大小,较小的元素加入一个新序列并且指针后移。那么对于一个无序的序列我们其实可以分而治之,先把序列分成一个一个的元素,一个元素当然是有序的,这样让两个有序的序列构成一个新的有序序列,新的有序序列又可以和其他两个元素构成的有序序列合并,以此类推最终就会构成一个完整的有序序列了。
#include<iostream> using namespace std; int a[1000005], n, b[1000005]; void Gsort(int l, int r) { if (l == r)return; int mid = (l + r) >> 1, tot = 1, i = l, j = mid + 1; Gsort(l, mid); Gsort(mid + 1, r); while (tot <= r - l + 1) { if ((j <= r) && (a[i] >= a[j] || i == mid + 1)) { b[tot] = a[j]; j++; tot++; } else if ((j == r + 1 || a[i] <= a[j]) && (i <= mid)) { b[tot] = a[i]; i++; tot++; } } for (int i = 1; i < tot; i++) { a[l + i - 1] = b[i]; } } int main() { cin >> n; for (int i = 1; i <= n; i++)cin >> a[i]; Gsort(1, n); for (int i = 1; i <= n; i++)cout << a[i] << " "; return 0; }
- 堆排序
要了解堆排序首先我们要了解一个概念,什么是堆?
若n个关键字序列L[ 1...n]满足下面某一条性质,则称为堆(Heap) :
若满足︰L(i)≥L(2i)且L(i)≥L(2i+1) (1 ≤ i <n/2 ) —―大根堆(大顶堆)
若满足︰L(i)≤L(2i)且L(i)≤L(2i+1)(1 ≤i <n/2 ) -―小根堆(小顶堆)
乍一看这个堆的定义很奇怪,为什么要拿第i个元素和第2i,2i+1个元素相比呢,其实这是把一个序列抽象成了一棵二叉树,这课二叉树按照每一层进行编号,第i个元素的左右孩子不就是第2i,2i+1个元素吗。所以大顶堆就是说对每个拥有左右子树的结点,结点的值都要大于自己的左右孩子的值。
实现堆排序既可以使用大顶堆也可以使用小顶堆,这里我们用大顶堆实现。首先我们将数列初始化为一个大顶堆,之后取堆的第一个元素和最后一个元素交换,因为大顶堆的第一个元素就是堆的根,是最大的数,可以翻放到最后。这时候再处理n-1个元素的堆,处理完之后再把第一个元素放到n-1的位置,因为这是第二大的元素。以此类推,堆排序就完成了。
#include<iostream> using namespace std; int a[1000005], n; void HeapAdjust(int k,int len) { for (int i = 2*k; i <= len; i *= 2) { if (a[i] < a[i + 1] && i < len) i++; if (a[i] > a[i / 2]) swap(a[i], a[i / 2]); } } void BuildHeapTree() { for (int i = n / 2; i > 0; i--) { HeapAdjust(i, n); } } void Heapsort() { BuildHeapTree(); swap(a[1], a[n]); for (int i = n - 1; i > 0; i--) { HeapAdjust(1, i); swap(a[1], a[i]); } } int main() { cin >> n; for (int i = 1; i <= n; i++)cin >> a[i]; Heapsort(); for (int i = 1; i <= n; i++)cout << a[i] << " "; return 0; }

浙公网安备 33010602011771号