数据结构 - 冒泡排序法详解
2017-01-24 23:36
169 查看
数据结构 - 冒泡排序法
排序算法的学习意义
当然, 现在大部分的高级语言都提供了封装好的排序方法, 例如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)了
相关文章推荐
- Android JNI 使用的数据结构JNINativeMethod详解
- 数据结构-排序: 交换排序(冒泡排序法)
- Android JNI 使用的数据结构JNINativeMethod详解
- Android JNI 使用的数据结构JNINativeMethod详解
- Android JNI 使用的数据结构JNINativeMethod详解
- 数据结构学习之_冒泡排序法
- 转 mysql 数据结构详解
- lwip学习笔记之几个重要的数据结构详解
- Android JNI 使用的数据结构JNINativeMethod详解 ||建立Android SDK下的JNI、JAVA应用完整步骤---Android JAVA调用C++代码
- 数据结构-排序: 交换排序(冒泡排序法)
- Android JNI 使用的数据结构JNINativeMethod详解(转)
- 数据结构----关键路径详解(Java)
- Android JNI 使用的数据结构JNINativeMethod详解
- 【数据结构】详解Linux内核之双向循环链表
- 数据结构-排序: 交换排序(冒泡排序法)
- XgCalendar 代码导读和Demo详解(1)参数说明和数据结构
- Android JNI 使用的数据结构JNINativeMethod详解
- Android JNI 使用的数据结构JNINativeMethod详解
- 数据结构--折半查找法 详解
- 解析从源码分析常见的基于Array的数据结构动态扩容机制的详解