您的位置:首页 > 理论基础 > 数据结构算法

数据结构 - 冒泡排序法详解

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)了
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  数据结构 c语言