假设所有的排序都是从小到大进行的
问题:给定n个元素,输出这n个元素从小到大的顺序(第一行读入一个n,接下来读入n个元素,每个元素不超过100)(n < 100)
样例输入:
6 21 25 49 28 16 8
样例输出:
8 16 21 25 28 49
稳定性描述: 假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中, r [ i ] = r [ j ] r[i]=r[j] r[i]=r[j],且 r [ i ] r[i] r[i]在 r [ j ] r[j] r[j]之前,而在排序后的序列中, r [ i ] r[i] r[i]仍在 r [ j ] r[j] r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
将有序序列末的后一个元素插入进有序序列的过程,例如 2 3 5 7 4 2\,3\,5\,7\,4 23574,前 4 4 4个元素有序,将 4 4 4插入到正确的位置,方法是不断用当前最后一个和前一个比较,如果顺序不对就交换,直到不发生交换为止,说明全部都有序了,过程如下图所示
代码
#include <cstdio>
#define N 105
int a[N], n;
void Insert_Sort () {
for (int i = 2; i <= n; i++) {
int temp = a[i];
for (int j = i; j >= 1 && a[j] < a[j - 1]; j --) {
a[j] = a[j - 1];
a[j - 1] = temp;
}
}
}
int main () {
scanf ("%d", &n);
for (int i = 1; i <= n; i++)
scanf ("%d", &a[i]);
Insert_Sort ();
for (int i = 1; i <= n; i++)
printf ("%d ", a[i]);
return 0;
}
假设对序列 9 1 2 5 7 4 8 6 3 5 9\,1\,2\,5\,7\,4\,8\,6\,3\,5 9125748635进行希尔排序,首先分组增量设置为长度的一半,然后一直减半,直到为 1 1 1,过程中对每一组进行直接插入排序,使得每一组都是有序的。如下图
代码
#include <cstdio>
#define N 105
int a[N], n;
void Shell_Sort () {
for (int gap = n / 2; gap >= 1; gap /= 2) { //gap表示增量
for (int i = gap; i <= n; i++) {
int temp = a[i];
for (int j = i; j >= gap && a[j] < a[j - gap]; j -= gap) { //对每一个分组进行排序
a[j] = a[j - gap];
a[j - gap] = temp;
}
}
}
}
int main () {
scanf ("%d", &n);
for (int i = 1; i <= n; i++)
scanf ("%d", &a[i]);
Shell_Sort ();
for (int i = 1; i <= n; i++)
printf ("%d ", a[i]);
return 0;
}
代码
#include <cstdio>
#define N 105
void swap (int &x, int &y) {
int t = x;
x = y;
y = t;
}
int a[N];
int main () {
int n, mn, loc;
scanf ("%d", &n);
for (int i = 1; i <= n; i++)
scanf ("%d", &a[i]);
for (int i = 1; i < n; i++) {
mn = a[i], loc = i;
for (int j = i; j <= n; j++)
if (a[j] < mn) {
mn = a[j];
loc = j;
}
swap (a[loc], a[i]);
}
for (int i = 1; i <= n; i++)
printf ("%d ", a[i]);
return 0;
}
代码
#include <cstdio>
#define N 105
int a[N];
void swap (int &x, int &y) {
int t = x;
x = y;
y = t;
}
int main () {
int n;
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 - i; j++) {
if (a[j] > a[j+1])
swap (a[j], a[j+1]);
}
}
for (int i = 1; i <= n; i++)
printf ("%d ", a[i]);
return 0;
}
可简单改进 (思路就在在某一轮比较中,如果相邻的两个元素没有发生交换,则说明顺序已经对了,不需要再继续排序下去)
#include <cstdio>
#define N 105
int a[N];
void swap (int &x, int &y) {
int t = x;
x = y;
y = t;
}
int main () {
int n;
bool flag;
scanf ("%d", &n);
for (int i = 1; i <= n; i++)
scanf ("%d", &a[i]);
for (int i = n; i > 1; i--) {
flag = true;
for (int j = 1; j < i; j++) {
if (a[j] > a[j+1]) {
swap (a[j], a[j+1]);
flag = false;
}
}
if (flag) break;
}
for (int i = 1; i <= n; i ++)
printf("%d ", a[i]);
return 0;
}
以中间的数作为基准数为例,指针 i , j i,j i,j分别从 l e f t , r i g h t left,right left,right开始,为了保证基准数的左边小于它,右边大于它,那么应该在左边找到一个不小于基准数的数,在右边找到一个不大于基准数的数,然后交换这两个数。直到,两个指针相遇。然后从 i , j i,j i,j指针停留的位置,一分为二,进行下一轮的调整(可以从图中发现,实际上快速排序就是在给每一次的基准数找到自己正确的位置)。下图以 1 5 2 3 4 8 1\,5\,2\,3\,4\,8 152348为例,来进行快速排序,下图的橘色为每次的基准数,每次先从右边开始找,然后是左边。如图所示
代码
#include <cstdio>
#define N 105
void swap (int &x, int &y) {
int t = x;
x = y;
y = t;
}
void Quick_Sort1 (int a[], int left, int right) { //选取第一个点作为基准元素
if (left >= right) return; // 一个元素肯定是有序的
int i = left, j = right, key = a[left];
while (i < j) {
while (i < j && a[j] >= key) j --; //必须先从右边开始找
while (i < j && a[i] <= key) i ++;
if (i < j) swap (a[i], a[j]);
}
swap (a[left], a[i]);
Quick_Sort (a, i + 1, right);
Quick_Sort (a, left, i - 1);
}
void Quick_Sort2 (int a[], int left, int right) { //选取中点作为基准元素
if (left >= right) return ;
int i = left, j = right, key = a[(left + right) >> 1];
while (i < j) {
while (a[j] > key) j --;
while (a[i] < key) i ++;
if (i <= j) {
swap (a[i], a[j]);
i ++, j --;
}
}
Quick_Sort (a, left, j);
Quick_Sort (a, i, right);
}
int main () {
int n, a[N] = {0};
scanf ("%d", &n);
for (int i = 1; i <= n; i++)
scanf ("%d", &a[i]);
Quick_Sort2 (a, 1, n);
for (int i = 1; i <= n; i++)
printf("%d ", a[i]);
return 0;
}
代码
#include <cstdio>
#define N 105
int bucket[N];
int main () {
int n, number;
scanf ("%d", &n);
for (int i = 1; i <= n; i++) {
scanf ("%d", &number);
bucket[number] ++;
}
for (int i = 0; i < N; i++) {
if (bucket[i])
for (int j = 1; j <= bucket[i]; j++)
printf("%d ", i);
}
return 0;
}
下面通过两张图片说明这个过程,例如 有一个初始序列, 2 8 4 6 3 7 2 \,8\, 4\, 6\, 3\, 7 284637,对它进行归并排序,首先将这个序列不断二分,直到子序列的元素个数为 1 1 1( 1 1 1个元素肯定是有序的)。
然后进行归并
不断将归并的序列变成有序,最终的到一个有序的序列 2 3 4 6 7 8 2\,3\,4\,6\,7\,8 234678
那么归并的操作是怎么进行的呢?如何将两个有序的序列合并成一个有序的序列呢?来看下面这张图,假设现在有两个有序子序列分别是 2 4 8 2\,4\,8 248和 3 6 7 3\,6\,7 367(它们都存在于数组 a a a中),用两个指针 i j i\,j ij分别指向这两个序列的开头,然后进行比较,找到小的那个,将它放进辅助数组中,在这个例子中 a [ i ] < a [ j ] a[i] < a[j] a[i]<a[j]因此指针 i i i向后移动一位继续比较。然后比较 4 3 4\,3 43的大小,并继续移动指针,当其中一个指针移动到子序列的末尾就停止移动,然后检查是否 i j i\,j ij都移动到了末尾,如果没有移动到末尾,就将后面的元素依次放进辅助数组中。这样辅助数组中就存放了两个有序子序列合并后的结果了。
#include <cstdio>
#define N 105
int a[N], b[N]; //数组B为辅助数组
void merge_sort (int l, int r) {
if (l >= r) return ;
int mid = (l + r) >> 1;
merge_sort (l, mid); //不断分解
merge_sort (mid + 1, r);
int i = l, j = mid + 1, k = l;
while (i <= mid && j <= r && k <= r) {
if (a[i] <= a[j]) b[k++] = a[i++];
else b[k++] = a[j++];
}
while (i <= mid) b[k++] = a[i++];
while (j <= r) b[k++] = a[j++];
for (int q = l; q <= r; q ++) //将辅助数组中的有序序列赋值回去
a[q] = b[q];
}
int main () {
int n;
scanf ("%d", &n);
for (int i = 1; i <= n; i++)
scanf ("%d", &a[i]);
merge_sort (1, n);
for (int i = 1; i <= n; i++)
printf ("%d ", a[i]);
return 0;
}
由于归并排序的特性,可以衍生出利用归并排序来找逆序对的方法。所谓逆序对就是一对元素,大的在前,小的在后,也就是序列中 a i > a j a_i>a_j ai>aj且 i < j i
那么怎么找逆序对呢?不妨这样想一想,归并排序在进行合并的时候。指针 i i i是指向左边序列,指针 j j j是指向右边的序列,并且这两个子序列都是从小到大有序增加的,那么如果出现了这样一种情况,即 a [ i ] > a [ j ] a[i] > a[j] a[i]>a[j],那么这个时候肯定会出现逆序对,会出现多少逆序对呢?注意此时的子序列是递增的,那么左边子序列在 i i i右边的肯定比 a [ i ] a[i] a[i]大,这个时候逆序对的个数,就是从 i i i开始到左边子序列结束的个数,用一个累加器加在一起即可。
#include <cstdio>
#define N 500005
#define in(a) a = read ()
int read () {
int x = 0, f = 1; char c = getchar ();
while (c < '0' || c > '9') {if (c == '-') f = - 1; c = getchar ();}
while (c >= '0' && c <= '9') {x = (x << 3) + (x << 1) + c - 48; c = getchar ();}
return f * x;
}
int a[N], b[N];
int ans;
void Merge_Sort (int left, int right) {
if (left >= right) return ;
int mid = (left + right) >> 1;
Merge_Sort (left, mid);
Merge_Sort (mid + 1, right);
int i = left, j = mid + 1, k = left;
while (i <= mid && j <= right) {
if (a[i] <= a[j]) b[k++] = a[i++];
else {
ans += mid - i + 1; //将所有a[j]前面比它大的数找出来
b[k++] = a[j++];
}
}
while (i <= mid) b[k++] = a[i++];
while (j <= right) b[k++] = a[j++];
for (int q = left; q <= right; q ++) a[q] = b[q];
}
int main () {
int n;
in (n);
for (int i = 1; i <= n; i++)
in (a[i]);
Merge_Sort (1, n);
printf ("%d", ans);
return 0;
}