7种常用简单排序算法

7种简单排序算法小结

假设所有的排序都是从小到大进行的

问题:给定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]之前,则称这种排序算法是稳定的;否则称为不稳定的。

基于插入的排序

直接插入排序

  • 数列的前半部分为有序,后部分为无需。一开始有序序列只有第一个,默认是有序的,然后将新的一个元素在原来的有序序列中找到正确的位置,然后插入进去。(需要注意的是,将新的元素插入进有序序列的时候,有些类似于冒泡的过程,如果待插入的元素小于有序序列的最大元素,那么将这两个的大小交换一下,再往前进行比较)(数据规模小并且越有序越高效)
  • 时间复杂度: O ( n 2 ) O(n^2) O(n2)
  • 稳定性:稳定

将有序序列末的后一个元素插入进有序序列的过程,例如 2   3   5   7   4 2\,3\,5\,7\,4 23574,前 4 4 4个元素有序,将 4 4 4插入到正确的位置,方法是不断用当前最后一个和前一个比较,如果顺序不对就交换,直到不发生交换为止,说明全部都有序了,过程如下图所示

7种常用简单排序算法_第1张图片

代码

#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;
}

希尔排序

  • 希尔排序是插入排序的一种。也叫做缩小增量排序,将原来的序列,按照一定的增量进行分组,然后对每个分组进行直接插入排序。接着减少增量,再次分组排序, 直到增量为 1 1 1的时候,整个序列被分为 1 1 1组,排序完成。
  • 时间复杂度: O ( n l o g   2 n ) O(nlog\,2n) O(nlog2n)(和增量的选择有关)
  • 稳定性:不稳定
  • 没有快速排序快,但是实现简单,在最坏情况下和平均情况下效率相差不大,可以多使用,如果性能不行再用快排

假设对序列 9   1   2   5   7   4   8   6   3   5 9\,1\,2\,5\,7\,4\,8\,6\,3\,5 9125748635进行希尔排序,首先分组增量设置为长度的一半,然后一直减半,直到为 1 1 1,过程中对每一组进行直接插入排序,使得每一组都是有序的。如下图

7种常用简单排序算法_第2张图片

代码

#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;
}

基于选择的排序

直接选择排序

  • 从头到尾遍历所有的元素,找到最小的元素,并且与首位置元素进行交换,接着从第二个元素开始遍历,找到最小的元素,并且与第二个位置的元素进行交换, n n n个元素则进行 n − 1 n-1 n1轮的比较交换,每一轮确定一个最小的元素放在前面。
  • 时间复杂度: O ( N 2 ) O(N^2) O(N2)
  • 稳定性:不稳定

代码

#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;
}

基于交换的排序

冒泡排序

  • 每一轮依次比较相邻的两个元素,如果大的在前小的在后,则交换这两个元素,这样每一轮都能确定一个未排序的元素中最大的元素,总共 n n n个元素进行 n − 1 n-1 n1轮的比较
  • 时间复杂度: O ( N 2 ) O(N^2) O(N2)
  • 稳定性:稳定

代码

#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;
}

快速排序

  • 首先找到一个基准数,将基准数左右两边的数字进行交换,使得左边的元素全部比它小,右边的元素全都比它大,然后将左右两边分而治之,同样找基准数,然后交换,不断进行下去,直到子序列只剩下一个元素。每一轮快速排序,总是能将基准数放在正确的位置上。而每次交换比较是跳跃式的,总的比较次数就比冒泡排序少,最坏情况下,仍然是相邻的两个元素进行了交换(基准数的选取很大程度上影响算法的速度,一般选择 序列的第一个元素,或者中间的元素作为基准数)。
  • 平均时间复杂度: O ( N l o g ( N ) ) O(Nlog(N)) O(Nlog(N))
  • 稳定性:不稳定

中间的数作为基准数为例,指针 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为例,来进行快速排序,下图的橘色为每次的基准数,每次先从右边开始找,然后是左边。如图所示

7种常用简单排序算法_第3张图片

代码

#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;
}

桶排序

  • 找出待排序元素的最大值 m a x max max,开一个数组大小为 m a x + 1 max+1 max+1,遍历待排序数列,将数列的值最为数组的下标,数组元素自增 1 1 1位。最后从数组的开头进行遍历,如果数组的值不为 0 0 0,则进行输出下标,输出的次数为数组的值的大小。
  • 时间复杂度: O ( N + C ) O(N+C) O(N+C)
  • 稳定性:稳定排序

代码

#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;
}

归并排序

  • 归并排序是建立在归并操作上的一种有效排序算法。核心思想在于将两个有序的数列合并成为一个大的有序数列,通过递归的方式层层合并。先将数组不断二分,然后在进行有序序列的合并
  • 时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
  • 稳定性:稳定

下面通过两张图片说明这个过程,例如 有一个初始序列, 2   8   4   6   3   7 2 \,8\, 4\, 6\, 3\, 7 284637,对它进行归并排序,首先将这个序列不断二分,直到子序列的元素个数为 1 1 1( 1 1 1个元素肯定是有序的)。

7种常用简单排序算法_第4张图片

然后进行归并

7种常用简单排序算法_第5张图片

不断将归并的序列变成有序,最终的到一个有序的序列 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都移动到了末尾,如果没有移动到末尾,就将后面的元素依次放进辅助数组中。这样辅助数组中就存放了两个有序子序列合并后的结果了。

7种常用简单排序算法_第6张图片 需要注意的是,辅助数组中只是暂时存放当前归并的两个子序列的结果,需要将这个辅助数组对应的位置复制到原数组中。
#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 ii<j的有序对。例如序列 4   3   6   2 4\,3\,6\,2 4362,其中就有 4 4 4个逆序对。分别是 4   3 、 4   2 、 3   2 、 6   2 4\,3、4\,2、3\,2、6\,2 43423262

那么怎么找逆序对呢?不妨这样想一想,归并排序在进行合并的时候。指针 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;
}

你可能感兴趣的:(数学)