排序算法的重要性
排序算法对于大家来说应该都不陌生。计算机相关专业的同学入门的第一门课程C语言课程中,老师通常都会讲解冒泡排序。软件开发当中用到排序算法的场景挺多,比如一个年级学生的成绩按照总分大小进行排名次,再比如我们网购的时候通常会按照销量,按照价格进行排序等。学好排序算法不管是考试、笔试|面试、工作都可以用的上。面试找工作的时候,排序算法也是高频出现的考题之一,比如现场手撕冒泡排序,快速排序的原理等。 因此排序算法也是比较重要的需要好好掌握的一类算法。
算法的学习最重要的是理解实现原理
冒泡排序算法通常是新手接触排序算法时,最先接触到的算法。所以雷老师在这里也以冒泡排序算法为例,讲透冒泡排序,以及排序算法当中比较重要的一些概念。如果你在学习冒泡排序的时候,学习的时候感觉是懂了,一到自己写的时候写不出来,这就是一知半解,没有彻底的搞懂。我们在学习算法的时候不需要去死记硬背代码,重要的是要理解原理,思路清楚了,代码自然就有了。如果是死记硬背的代码,时间长了不用,很容易忘记。只有理解了实现原理,才能活学活用,变成自己的技能。假如我们没有理解算法 或者理解的不透彻可能看书之后或者听老师讲解之后觉得懂了,到自己写的时候却写不出来,或者写出来了,过段时间不用又忘了。 我们以C语言为例来讲解冒泡排序。
冒泡排序算法原理
void bubble_sort (int arr[], int len) {}
冒泡排序只会操作相邻的两个数,对相邻的两个数进行比较,看时候满足大小关系要求。如果不满足就让这两个数互换位置。一次冒泡之后至少会让一个数移动到它应该在的位置。n个数,我们只需要冒泡n-1次即可完成排序。
举个栗子,我们对一下数列9 1 5 8 3 7进行从小到大冒泡排序
第一次冒泡
首先比较9和1,9 比1大,交换位置
1 9 5 8 3 7
继续比较9和5,9比5大,交换位置
1 5 9 8 3 7
继续比较9和8,9比8大,交换位置
1 5 8 9 3 7
继续比较9和3,9比3大,交换位置
1 5 8 3 9 7
继续比较9和7,9比7大,交换位置
1 5 8 3 7 9
在经过一轮冒泡之后数字9已经到了正确的位置,接下来,在除了9之外的剩余5个数中继续冒泡
第二次冒泡
首先比较1和5,1比5小,不做处理,继续比较5和8,5比8小,不做处理,继续比较8和3,8比3小,交换位置
1 5 3 8 7 9
继续比较8和7,8比7大,交换位置
1 5 3 7 8 9
经过第二次冒泡之后,数字8到了正确的位置,接下来,在除了数字8,9之外的剩余4个数中继续冒泡
第三次冒泡
首先比较1和5,1比5小不做处理,继续比较5和3,5比3大,交换位置
1 3 5 7 8 9
比较5和7,5比7小,不做处理
1 3 5 7 8 9
经过第三次冒泡数字7到了正确的位置,接下来在除了数字7,8,9之外的剩余3个数中继续冒泡
第四次冒泡
比较1和3,1比3小不做处理,比较3和5,3比5小不做处理
第四次冒泡之后数字5到了正确的位置,接下来在剩余的两个数中继续冒泡
1 3 5 7 8 9
第五次冒泡
1和3比较,1比3小,不做处理
1 3 5 7 8 9
此时数列已经是从小到大一次排列了。
将以上步骤用代码表示如下:
#include <stdio.h>
void bubble_sort (int arr[], int len) {
int i, j, temp;
for (i = 0; i < len - 1; i++) {
for(j = 0; j < len - i -1; j++) {
// 相邻两个数比较
if( arr[j] > arr[j+1] ) {
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
int main(){
int arr[] = { 9, 1, 5, 8, 3, 7};
int len = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, len);
int i;
for (i = 0; i < len; i++)
printf("%d ", arr[i]);
printf("\n arr size is %d, arr item size is %d", sizeof(arr), sizeof(arr[0]));
// return 1;
}
初次接触冒泡排序的同学,难点可能是在里层冒泡次数j < len - 1 - i这里了,这行代码简单说就是,每次冒泡比较n个数,我们需要比较len -1次,由于我们冒泡一次就会有一个数在应该在的位置,冒泡i次,就有i个数在应该在的位置。这些已经在正确位置的数就不需要参与比较了,所以是 j < len -1 - i。以上就是冒泡排序算法的完整代码。
冒泡排序的优化
当某次冒泡已经没有数据交换时,说明数据已经是排好序的了,不用再继续冒泡了。优化版的冒泡排序代码如下:
void bubble_sort2 (int arr[], int len) {
int i, j, temp;
for (i = 0; i < len - 1; i++) {
int flag = 0;
for(j = 0; j < len - i -1; j++) {
// 相邻两个数比较
if( arr[j] > arr[j+1] ) {
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
flag = 1;
}
}
if (!flag) {
break;
}
}
}
排序的稳定性
排序的稳定性是说,如果待排序的序列中存在值相等的元素,经过排序之后,相等的元素之间原有的先后顺序不变。废话不多说,我们来举个栗子,假如待排序的序列22, 34, 3, 32, 82, 55 82 中有两个82,在排序之后,这两个82的先后顺序保持不变,那么这个排序算法就是稳定的排序算法,如果这两个82位置发生了变化,那么这个排序算法就是不稳定的排序算法。我们在选在排序算法的时候一定要注意排序算法的稳定性。
在我们的示例中,待排序的是一组数字,这组数字,值相等的数字谁在前,谁在后好像没什么影响。但是我们在实际的软件开发当中处理的数据可不是数字,而是实际业务中的数据。比如我们对一个年级的学生按照成绩大小进行排序。如果存在成绩相等的学生,则按照班级进行排序。那么我们可以先按照班级进行一次排序,这次排序之后,学生列表是按照班级一班,二班,三班这样的顺序进行排序。之后我们在按照成绩进行一次稳定的排序,即可做到,按照成绩排序,成绩相等的则班级号小的再前。假如我们按照成绩排序的算法是不稳定的,则有可能出现一班和二班有两个同学成绩相等,排序后,成绩相等的这两个同学,二班的同学排再一班的前边。
姓名 | 班级 | 成绩 |
李同学 | 一班 | 97 |
张同学 | 二班 | 86 |
高同学 | 二班 | 97 |
孙同学 | 一班 | 86 |
外部排序和内部排序
内排序是在排序的过程中,待排序的所有记录全部放在内存中。比如我们上边的冒泡排序,排序数组中的数列。数组中的元素全部是在内存中的,这就是内部排序。外部排序是指排序的数据量大,不能同时放到内存中进行排序,整个排序过程需要内存和硬盘之间多次交换数据才能进行。比如我们内存4G,要排序的数据有8G,那我们就不能将这8G的数据一次性读入内存进行排序。对于这种情况,通常是将文件进行分割,排序分割后的数据,然后进行归并。比如我们将8G的数据文件分割成4份,每份就是2GB,然后我们将这2GB的数据一次读入内存,进行排序。重复4次,我们就有了4个内部有序的文件。然后对这4个文件再进行一次归并排序。归并排序雷老师后边会讲。