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

数据结构 - 冒泡排序法

排序算法的学习意义

当然, 现在大部分的高级语言都提供了封装好的排序方法, 例如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(i1)=1+2+3+...+n1=n(n1)2=n22n2 次的比较次数 和 相对应级数的位置交换次数。

时间复杂度只看最高阶的次数。

所以就是O( n2 )了

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nvd11

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值