算法竞赛之排序算法初入门
关于排序的一些知识点
排序是计算机内经常进行的一种操作,其目的是将一组"无序"的记录序列调整为"有序"的记录序列。我们学习的排序是关于算法之中,对一些特定数据,按照一定的优先级顺序将数据及逆行合理化的排列,比如在最初学习C语言的时候,老师提到的冒泡排序、选择排序,这两个是基本的排序,原理也应该比较简单,其实最早接触到的就是两个数字比较大小,然后按升序或者降序排布的,这个应该是最早接触的排序了。
既然排序这么重要,我们就学习一下一些常见的排序算法吧,由于本人能力有限,所以有什么介绍不好的地方,欢迎评论并共同进步。
例子:3 2 1 5 6 0 1 2 3 7
-
冒泡排序(此次降序): 需要(n-1)轮的操作,最后得到一个单调数组,将当前数组里面的第一个元素与之后的元素进行比较,若是当前元素小于被比较的元素,则进行交换,由于每次都是最大的先固定下来,像水里的气泡,离水面越近越大,所以称之为冒泡排序。运行结果如下图所示:
代码:
#include <stdio.h> int main() { int array[10] = {3, 2, 1, 5, 6, 0, 1, 2, 3, 7}; int cnt = 0; for(int i = 0; i < 9; i++) { for(int j = i + 1; j < 10; j++) if(array[i] < array[j]) { int temp = array[i]; array[i] = array[j]; array[j] = temp; } printf("Case %d:\n", ++cnt); for(int j = 0; j < 10; j++) printf("%d ", array[j]); printf("\n"); } return 0; }
-
选择排序(降序):这个应该就是C语言之中最早接触到的排序算法了吧,对一个大小为n的数组,总共需要n轮排序之后,会得到一个单调数组,而在每一轮里面,每次在当前元素与被比较元素之间判断,若是被比较元素较大,则记录当前被比较元素的值以及下标,当前一轮操作结束之后,则将当前元素与记录的元素进行交换。
结果:
代码:
#include <stdio.h> int main() { int array[10] = {3, 2, 1, 5, 6, 0, 1, 2, 3, 7}; int cnt = 0; for(int i = 0; i < 9; i++) { int max_ = array[i], index = i; for(int j = i + 1; j < 10; j++) if(max_ < array[j]) { max_ = array[j]; index = j; } int temp = array[i]; array[i] = array[index]; array[index] = temp; printf("Case %d:\n", ++cnt); for(int j = 0; j < 10; j++) printf("%d ", array[j]); printf("\n"); } return 0; }
讲完这两个比较基本的排序算法之后,我们开始学习一些比较中级一点的排序方法吧。
例子:3 2 1 5 6 0 1 2 3 7
-
快速排序算法:(升序)
-
先从数列中取出一个数作为基准数。
-
分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
-
再对左右区间重复第二步,直到各区间只有一个数。
注:我们找的基准数一般都是数组的第一个元素的值。
-
-
这里手动打印出第一次排序的过程(红色为虚拟的)
原始数组:3 2 1 5 6 0 1 2 3 7
然后选取基准数:3,将数字存入临时变量之中,然后数组就多出来了一个位置,之后开始操作。
第一次:i = 0, j = 9, x = 3;
开始从右往左开始扫描到第一个小于3的数字,若是大等于的话,则j进行自减,然后第一次找到的是下标为7,值为2的数字,这时候的数组变为:
2 2 1 5 6 0 1 3 3 7
i j
i = 0, j = 7, x = 3;
然后从左到右扫描到第一个大等于3的数字,若是小于的话,则i进行自增,然后第一次找到的下标为1,值为2的数字,这时候的数组变为:
2 2 1 3 6 0 1 5 3 7
i j
i = 1, j = 7, x = 3
重复操作,接下来只打印满足时候,当前数组的状态,若是不满足i <j ,则当前轮结束;
2 2 1 1 6 0 3 5 3 7
i j
2 2 1 1 3 0 6 5 3 7
i j
2 2 1 1 0 3 6 5 3 7
ij
这时候,第一轮的结果结束,可以看到, 在基准数3的左边全部小于3,在基准数3的右边全部大等于3.所以被拆分成两个数组:
第一个是2 2 1 1 0,第二个是6 5 3 7.
当最后传进来的数组大小为1的时候,那么当前循环就结束操作。
具体的模拟过程这里就不进行打印了,看过第一轮的过程,应该会对快速排序有一个比较直观的感受,要是不是很懂的话,那么就多看几遍试试。
代码:
#include <stdio.h> void quick_sort(int* s, int l, int r) { if (l < r) { int i = l, j = r, x = s[l]; while (i < j) { while(i < j && s[j] >= x) j--; if(i < j) s[i++] = s[j]; while(i < j && s[i] < x) i++; if(i < j) s[j--] = s[i]; } s[i] = x; quick_sort(s, l, i - 1); quick_sort(s, i + 1, r); } } int main() { int s[10] = {3, 2, 1, 5, 6, 0, 1, 2, 3, 7}; quick_sort(s, 0, 9); for(int i = 0; i < 10; i++) printf("%d ", s[i]); return 0; }
-
堆排序:堆排序还是基于二叉树的感觉,
-
根据数组元素创建一棵完全二叉树
-
先构造初始堆,需要从最后一个非叶子节点调节,保证每次得到的堆都满足父亲节点的值大于左右孩子节点的值;
-
将每次操作得到的根节点元素与当前堆中的最后一个元素进行交换,然后重复步骤(b),直到需要操作的元素大小为1时候,则已经完成了堆排序
-
过程:
1.先创建一棵完全二叉树:
2.构造初始堆:
3.开始重新调整,获取满足结果的堆结果:
图1. 因为初始堆的根肯定是最大的,所以直接与最后一个元素进行交换
图2,进行重新排序之后的结果
图1 图2 图2结果
排序结束,所以最后得到的数组顺序为:0 1 1 2 2 3 3 5 6 7
代码:
#include <stdio.h> void Heapfy(int A[],int idx,int max) { int left=idx*2+1; int right=left+1; int largest=idx; if(left<max&&A[left]>A[idx]) largest=left; if(right<max&&A[largest]<A[right]) largest=right; if(largest!=idx){ int temp=A[largest]; A[largest]=A[idx]; A[idx]=temp; Heapfy(A,largest,max); } } void buildHeap(int A[],int ll){ int len=ll; for(int i=len/2-1;i>=0;--i) Heapfy(A,i,len); for(int i=len-1;i>=1;--i) { int temp=A[0]; A[0]=A[i]; A[i]=temp; Heapfy(A,0,i); } } int main() { int arr[10] = {3, 2, 1, 5, 6, 0, 1, 2, 3, 7}; int size = 10; buildHeap(arr, size); for(int i=0; i<size; i++) printf("%d ", arr[i]); return 0; }
3.归并排序:将数组进行拆分成小块的,保证每个块中的元素是有序的,然后将两两进行拼接,根据当前移动的数组所在的元素大小进行插入。
过程:
先将数组进行拆分成最小块:
然后将每个块内进行排序,两个块进行叠加的时候,用两个指针分别指向各自数组,然后在O(n)的时间复杂度就可以完成,然后拆分的时间是N(lg(n))
下面是进行叠加的过程:
代码:
#include<cstdio> using namespace std; int n,a[30000],b[30000],cnt; void merge(int x,int y,int z){ int i=x,j=y+1,k=0; while(i<=y&&j<=z){ if(a[i]<a[j]){ b[k]=a[i]; ++i; ++k; }else{ b[k]=a[j]; ++j; ++k; } } while(i<=y){ b[k]=a[i]; ++k; ++i; } while(j<=z){ b[k]=a[j]; ++k; ++j; } for(int t=0;t<k;++t){ a[x+t]=b[t]; } } void mergesort(){ for(int time=1;time<=4;++time){ for(int i=0;i<n;i+=(1<<time)){ int j; if(i+(1<<time)-1<n){ j=i+(1<<time)-1; }else{ j=n-1; } merge(i,i+(1<<time-1)-1,j); } } } int main(){ //freopen("mergeSort.in","r",stdin); //freopen("mergeSort.out","w",stdout); scanf("%d",&n); for(int i=0;i<n;++i){ scanf("%d",a+i); } mergesort(); for(int i=0;i<n;++i){ printf("%d",a[i]); if(i%10==9) putchar('\n'); else putchar(' '); } return 0; }
//(代码来源:斌神)
4.基数排序:根据当前位的数字,然后装入不同的数组,之前看一个例子,语言不知道怎么组织就是了(个人感觉基数排序应该是针对绝对值排序这种,即一个数组里面的元素应该是同号才对)
例子: 123 321 456 264 5329 21230 326
因为所有数组之中,最大的数字的长度为5,所以5次就可以得到一个单调数组。
第一轮(个位数)
0:21230
1:321
3:123
4:264
6:456 326
9:5329
结果: 21230 321 123 264 456 326 5329
第二轮(十位数)
2:321 123 326 5329
3:21230
5:456
6:264
结果:321 123 326 5329 21230 456 264
第三轮(百位数)
1:123
2:21230 264
3:321 326 5329
4:456
结果:123 21230 264 321 326 5329 456
第四轮(千位数)
0:123 264 321 326 456
1:21230
5:5329
结果:123 264 321 326 456 21230 5329
第五轮(万位数)
0: 123 264 321 326 456 5329
2: 21230
结果: 123 264 321 326 456 5329 21230
所以最后的结果是: 123 264 321 326 456 5329 21230
代码:
#include <stdio.h> #include <string.h> void ji_sort(int arr[], int ll, int n) { int s = 1; for(int t = 1; t <= ll; t++) { int num[10][15]; int len[10]; memset(num, 0, sizeof(num)); memset(len, 0, sizeof(len)); for(int i = 0; i < n; i++){ int temp = arr[i] / s % 10; num[temp][len[temp]++] = arr[i]; } int l = 0; printf("Case %d:\n", t); for(int i = 0; i < 10; i++) { if(len[i] != 0) printf("\n%d:", i); for(int j = 0; j < len[i]; j++) { arr[l++] = num[i][j]; printf(" %d", num[i][j]); } } printf("\n结果;"); for(int i = 0; i < n; i++) printf("%d ", arr[i]); printf("\n"); s *= 10; } } int main() { int array[15], n, l = 0; scanf("%d", &n); for(int i = 0; i < n; i++){ scanf("%d", &array[i]); int t = array[i], s = 0; while(t) { s++; t/=10; } if(s > l) l = s; } printf("%d\n", l); ji_sort(array, l, n); return 0; }
上面都是属于自己实现的算法,其实在c++里面已经对快速排序进行封装,提供了一个方法->sort()。还是建议平时做题的时候,直接使用系统提供的函数进行编写,不仅效率高,而且还不容易出错,当然了,这些算法平常时候还是要自己敲敲来熟悉一下。
原本是应该讲述qsort,但平常本人都是使用sort函数,而且也感觉会比qsort好用,如果想了解的话,这里仅仅是作为一个入门使用的,想多了解的话还是夺取看看博客之类的文章吧
Sort(arr, arr+n, cmp);
一般是三个参数,第一个参数代表着起始位置,第二个参数代表着结束为止,最后一个参数代表着排序规则。如果只写前面两个参数的话,那么就会采用默认的排序,即升序排序,若是遇到了自定义结构体的话,肯定是要写第三个参数,第三个参数的名称和你定义的函数体是要一致的。
大致的排序就说到这里,不懂的话可以群里直接问吧。