数据结构 - 冒泡排序法
排序算法的学习意义
当然, 现在大部分的高级语言都提供了封装好的排序方法, 例如java的 Colletcions.sort().
但是, 排序算法的实现仍然是出现在很多笔试题当中的。 理解排序算法的实现, 有利与你的编程思维的进步。 还是那句, 没学习好数据结构的程序员, 只能算一个码农。当然, 如果你的方向是业务分析(BA)或者软件测试(QA), 另说。
一般排序的写法
广州的德资公司RIB(建筑软件开发,.net方向), 有一笔试题 , 有1个10个数字组成的数组 {4, 1, 5, 8, 0, 3, 7, 9, 6, 2}, 要求写1个冒泡排序的的方法, 令数组里的元素从小到达重写排列。
咋一看, 还不简单吗。
很多人都是这样写的。
int sort(){
int arr[10] = {4, 1, 5, 8, 0, 3, 7, 9, 6, 2};
genericSort(arr, 10);
return 0;
}
int genericSort(int * arr, int len){
int i, j;
for (i = 0; i < len - 1; i++) {
for (j = i+1; j < len; j++){
if (arr[j] < arr[i]){
swap(arr, i, j);
}
}
}
return 0;
}
int swap(int * arr, int i, int j){
int iTmp = arr[i];
arr[i] = arr[j];
arr[j] = iTmp;
}
首先, 我直接说了, 上面的算法不能算是冒泡排序法, 我们暂且给它起个名字叫双重循环法吧。 它的时间复杂度是O(
n2
)
它的算法理解不难, 无非是1个双重循环。
在第二层循环中, 让1个元素去跟其他在它之后的元素逐个对比, 一旦发现比它小的, 就交换位置。
也就是说每执行一次外层循环, 就把最小的元素放在前列
让我们加入一些debug信息, 然后打印出来(刚交换位置后的数字前带*)
step 1: changing: *1, *4, 5, 8, 0, 3, 7, 9, 6, 2
step 4: changing: *0, 4, 5, 8, *1, 3, 7, 9, 6, 2
step 12: changing: 0, *1, 5, 8, *4, 3, 7, 9, 6, 2
step 19: changing: 0, 1, *4, 8, *5, 3, 7, 9, 6, 2
step 20: changing: 0, 1, *3, 8, 5, *4, 7, 9, 6, 2
step 24: changing: 0, 1, *2, 8, 5, 4, 7, 9, 6, *3
step 25: changing: 0, 1, 2, *5, *8, 4, 7, 9, 6, 3
step 26: changing: 0, 1, 2, *4, 8, *5, 7, 9, 6, 3
step 30: changing: 0, 1, 2,*3, 8, 5, 7, 9, 6, *4
step 31: changing: 0, 1, 2, 3, *5, *8, 7, 9, 6, 4
step 35: changing: 0, 1, 2, 3, *4, 8, 7, 9, 6, *5
step 36: changing: 0, 1, 2, 3, 4, *7, *8, 9, 6, 5
step 38: changing: 0, 1, 2, 3, 4, *6, 8, 9, *7, 5
step 39: changing: 0, 1, 2, 3, 4, *5, 8, 9, 7, *6
step 41: changing: 0, 1, 2, 3, 4, 5, *7, 9, *8, 6
step 42: changing: 0, 1, 2, 3, 4, 5, *6, 9, 8, *7
step 43: changing: 0, 1, 2, 3, 4, 5, 6, *8, *9, 7
step 44: changing: 0, 1, 2, 3, 4, 5, 6, *7, 9, *8
step 45: changing: 0, 1, 2, 3, 4, 5, 6, 7, *8, *9
change count: 19
compare count: 45
首先, 可以看出两层循环中, 总共比较了45次 = 9 + 8 + 7 + 6 …..
位置交换了19次
在第4次比较后, 最小的数字0被放到里第0个位置。
第12次比较后,, 第二小的数字1被放到第1个位置。
…
但是我们貌似也见到了1些不合理的地方, 例如在第24次比较时, 数字3由第2个位置搬到了第9个位置.. 反而比它应该存在的位置还靠后。
下面我们来看看冒泡排序法。
冒泡排序法的写法
冒泡排序最显著的特点, 就是按位置循环, 相邻两个元素比较,如果大小不合理,则交换元素。
请原谅博主那点可怜的表达能力…, 还是让我们用代码来交流吧:
int bubbleSort(int * arr, int len){
int i, j;
for (i = 0; i < len - 1; i++) {
for (j = len -1; j > i; j--) {
if (arr[j-1] > arr[j]) {
swap(arr, j, j-1);
}
}
}
}
首先, 外层循环的写法没变, 但是里层的循环是由后往前比的。
这个算法的时间复杂度也明显是O(
n2
)呀。效率怎么样呢, 让我们看看debug信息:
step 1: changing: 4, 1, 5, 8, 0, 3, 7, 9, *2, *6
step 2: changing: 4, 1, 5, 8, 0, 3, 7, *2, *9, 6
step 3: changing: 4, 1, 5, 8, 0, 3, *2, *7, 9, 6
step 4: changing: 4, 1, 5, 8, 0, *2, *3, 7, 9, 6
step 6: changing: 4, 1, 5, *0, *8, 2, 3, 7, 9, 6
step 7: changing: 4, 1, *0, *5, 8, 2, 3, 7, 9, 6
step 8: changing: 4, *0, *1, 5, 8, 2, 3, 7, 9, 6
step 9: changing: *0, *4, 1, 5, 8, 2, 3, 7, 9, 6
step 10: changing: 0, 4, 1, 5, 8, 2, 3, 7, *6, *9
step 11: changing: 0, 4, 1, 5, 8, 2, 3, *6, *7, 9
step 14: changing: 0, 4, 1, 5, *2, *8, 3, 6, 7, 9
step 15: changing: 0, 4, 1, *2, *5, 8, 3, 6, 7, 9
step 17: changing: 0, *1, *4, 2, 5, 8, 3, 6, 7, 9
step 21: changing: 0, 1, 4, 2, 5, *3, *8, 6, 7, 9
step 22: changing: 0, 1, 4, 2, *3, *5, 8, 6, 7, 9
step 24: changing: 0, 1, *2, *4, 3, 5, 8, 6, 7, 9
step 27: changing: 0, 1, 2, 4, 3, 5, *6, *8, 7, 9
step 30: changing: 0, 1, 2, *3, *4, 5, 6, 8, 7, 9
step 32: changing: 0, 1, 2, 3, 4, 5, 6, *7, *8, 9
change count: 19
compare count: 45
比较了45次, 交换了19次, 我艹, 不是跟上面的完全一样吗。
但仔细看看, 这个算每一次交换都是合理的交换, 数字容易不会向相反的方向被交换。 是, 当然这对效率没影响。
另外,下面的才是重点。
由于高效率的交换, 这个算法在32次比较后实际上就已经完全排好序了, 32步之后的比较都是无意义的。
而上面的双重循环法知道第45次比较后才完全拍好序。
也就是讲, 冒泡排序法其实有优化的空间。
为什么叫冒泡排序法
在优化之前, 我们先来搞清楚这个问题, 排序就排序, 跟冒泡有什么关系呢?
我们再仔细看看debug信息:
step 1: changing: 4, 1, 5, 8, 0, 3, 7, 9, *2, *6
step 2: changing: 4, 1, 5, 8, 0, 3, 7, *2, *9, 6
step 3: changing: 4, 1, 5, 8, 0, 3, *2, *7, 9, 6
step 4: changing: 4, 1, 5, 8, 0, 2, *3, 7, 9, 6
step 6: changing: 4, 1, 5, *0, *8, 2, 3, 7, 9, 6
step 7: changing: 4, 1, *0, *5, 8, 2, 3, 7, 9, 6
step 8: changing: 4, *0, *1, 5, 8, 2, 3, 7, 9, 6
step 9: changing: *0, *4, 1, 5, 8, 2, 3, 7, 9, 6
step 10: changing: 0, 4, 1, 5, 8, 2, 3, 7, *6, *9
step 11: changing: 0, 4, 1, 5, 8, 2, 3, *6, *7, 9
step 14: changing: 0, 4, 1, 5, *2, *8, 3, 6, 7, 9
step 15: changing: 0, 4, 1, *2, *5, 8, 3, 6, 7, 9
step 17: changing: 0, *1, *4, 2, 5, 8, 3, 6, 7, 9
step 21: changing: 0, 1, 4, 2, 5, *3, *8, 6, 7, 9
step 22: changing: 0, 1, 4, 2, *3, *5, 8, 6, 7, 9
step 24: changing: 0, 1, *2, *4, 3, 5, 8, 6, 7, 9
step 27: changing: 0, 1, 2, 4, 3, 5, *6, *8, 7, 9
step 30: changing: 0, 1, 2, *3, *4, 5, 6, 8, 7, 9
step 32: changing: 0, 1, 2, 3, 4, 5, 6, *7, *8, 9
change count: 19
compare count: 45
小数字是从后面一步一步地慢慢升到前部的。
就如气泡从容器底部慢慢升到顶部。
所以叫冒泡排序法~~!
冒泡排序的优化
优化思想就是令到算法能够判断数组被完全排序完毕, 一旦数组被完全排序完成, 不在进行后面的无意义比较。
那么如果判断数组被完全完成呢?
很简单, 在冒泡循环中, 如果执行一次外部循环里面, 没有发生任何的位置交换, 那就证明排序已经完成了。
注意, 这个方法不适用与上面的双重循环法, 因为它不是两两比较的。
优化后的代码:
int bubbleSort(int * arr, int len){
int i, j;
int chgFlag = 1;
for (i = 0; i < len - 1; i++) {
if (chgFlag == 0){
break;
}
chgFlag = 0;
for (j = len -1; j > i; j--) {
if (arr[j-1] > arr[j]) {
swap(arr, j, j-1);
chgFlag = 1;
}
}
}
}
价格flag判断一下就ok了
优化后的debug 信息:
change count: 19
compare count: 39
比较次数小了。。
冒泡排序法时间复杂度O( n2 )怎么算出来的
当然, 双重循环明显是O( n2 )嘛… 但我下面明显给的是具体数学公式:
首先最优的情况下 {0, 1, 2, 3, 4 , 5, 6, 7, 8, 9 .. n}
只需要执行n-1比较, 没有位置交换。
但是在最差的情况下(n, n -1 , n-2 … 0)
必须执行 :
∑ni=2(i−1)=1+2+3+...+n−1=n(n−1)2=n22−n2
次的比较次数 和 相对应级数的位置交换次数。
时间复杂度只看最高阶的次数。
所以就是O( n2 )了