冒泡排序算法以及它的优化方案

Posted sysocjs

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了冒泡排序算法以及它的优化方案相关的知识,希望对你有一定的参考价值。

一、什么是冒泡排序?

冒泡排序(Bubble Sort)是一种最为基础的交换排序,相信学过C语言的,都接触过这种排序算法。

这篇文章重点应该放在优化上面。

二、冒泡排序的实现思想:

将数组里面相邻的元素两两比较,根据大小来交换元素位置,举个栗子:

这里有一个数组array[4, 6, 5, 8, 9, 3, 2, 1, 7]

首先4和6比较,4小于6,位置不变,接下来6和5比较,6大于5,所以6和5的位置对调,数组变成[4, 5, 6, 8, 9, 3, 2,1, 7],由于6和5位置对调,接着是6和8比较,6小于8,所以位置不变,如此类推,第一轮排序后,数组变成[4,  5, 6, 8, 3, 2, 1, 7, 9],第二轮又从第一个元素4开始比较,但是最终比较的元素不是9而是7,因为第一轮比较,已经是确定将最大的元素放到了最后的位置,所以没有必要与最后的元素进行比较,这一轮最终结果为[4, 5, 6, 3, 2, 1, 7, 8, 9],

如此类推,完成全部排序总共需要array.length x( array.length-1)/2次比较(这个是等差数列计算出来的,有兴趣的可以自己算一下)。因为每一轮都要全部比较,所以最原始的冒泡排序叫做稳定排序。

根据这种原始思想,可以得到冒泡排序的原始版

public void sortArray(int[] array){
    int temp;
    for(int i=0; i<array.length; i++){
        for(int j=0; j<array.length-i-1; j++){
            if(array[j] > array[j+1]){
                temp = array[j];
                array[j] = array[j+1];
                array[j+1] = temp;
            }
        }
    }
}

我们把每一轮结果罗列出来时,

第三轮结果:[4, 5,  3,  2, 1, 6, 7, 8, 9]

第四轮结果:[4, 3,  2, 1, 5,  6, 7, 8, 9]

第五轮结果:[3, 2, 1, 4,  5,  6, 7, 8, 9]

第六轮结果:[2, 1, 3, 4,  5,  6, 7, 8, 9]

第七轮结果[1, 2, 3, 4,  5,  6, 7, 8, 9]

第八轮结果:[1, 2, 3, 4,  5,  6, 7, 8, 9]

从结果可以看出,程序做了些“无用功”,为了避免程序做这些“无用功”,要对基础版本程序作出一些修改,

优化第一版:

 

public void sortArray(int[] array){
    int temp;
    for(int i=0; i<array.length; i++){
        boolean isSorted = true;
        for(int j=0; j<array.length-i-1; j++){
            if(array[j] > array[j+1]){
                temp = array[j];
                array[j] = array[j+1];
                array[j+1] = temp;
                isSorted = false;
            }
        }
        if(isSorted){
            break;
        }
    }
}

 

程序定义了一个boolean类型的isSorted变量,用来判断往后的循环当中,数组是否已经是有序的,每一轮循环都会设置其值为true,当有元素对调位置时,就将isSorted的值设置为false,表示该数组还不是有序数组。每一轮都要判断isSorted的值,如果判断当前一轮操作没有元素有位置调换,那么可以提前结束所有的循环。当然,本次栗子中用到的数组还是需要进行8轮循环,因为,第7轮的时候isSorted的值会被设置为false,到了第八轮才是true,读者可以自行举例别的数组检验。

还是拿回每一轮运行结果出来:

第三轮结果:[4, 5,  3,  2, 1, 6, 7, 8, 9]

第四轮结果:[4, 3,  2, 1, 5,  6, 7, 8, 9]

第五轮结果:[3, 2, 1, 4,  5,  6, 7, 8, 9]

第六轮结果:[2, 1, 3, 4,  5,  6, 7, 8, 9]

第七轮结果[1, 2, 3, 4,  5,  6, 7, 8, 9]

第八轮结果:[1, 2, 3, 4,  5,  6, 7, 8, 9]

这里讲解得详细一点,以第三轮结果,在第四轮运行操作中,4和5比较,4<5,不调换位置,5和3比较,5>3,位置对调,数组变成[4, 3, 5,  2, 1, 6, 7, 8, 9],5和2比较,5<2,位置对调,变成[4, 3, 2, 5, 1, 6, 7, 8, 9],5和1比较,5>1,位置对调,变成[4, 3, 2, 1, 5, 6, 7, 8, 9]。后面就是5和6比较,6和7比较,7和8比较,8和9比较,但是这四次比较对数组排序都没有任何“贡献”,同理,在第五轮循环操作中,没有“贡献”的操作会增加一次,这是不希望出现的。

这里要介绍一个概念——有序区,有序区指数组有序的区域,这里只数组末尾的有序元素组成的区域,在极端的情况,如[9, 8, 7, 6, 5, 4, 3, 2, 1],按照从小到大顺序排序,每一轮排序,有序区只增加一位元素,但更多的情况有序区元素是大于循环轮次,当有序区元素等于数组长度时,可以认为这个数组已经排序完成,所以下面给出第二次优化,

优化第二版:

public void sortArray(int[] array){
    int border = array.length-1;
    int lastIndex = 0;
    int temp;
    for(int i=0; i<array.length; i++){
        boolean isSorted = true;
        for(int j=0; j<border; j++){
            if(array[j] > array[j+1]){
                temp = array[j];
                array[j] = array[j+1];
                array[j+1] = temp;
                
                lastIndex = j;
                isSorted = false;
            }
        }
        border = lastIndex;
        if(isSorted){
            break;
        }
    }
}

这一版新增了两个int类型变量,一个是border,表示无序项的边界,同时也是每一轮循环的次数设定值,另一个是lastIndex,用来记录最后元素需要交换的下标值,进行一轮循环后,将这个值赋值给border,作为下一轮循环的次数。每一轮循环,当有元素需要调换位置时,记录j的位置,当前轮次循环结束,就将lastIndex赋值给border,最为新一轮循环操作的边界。

以第五轮结果为栗子,[3, 2, 1, 4,  5,  6, 7, 8, 9]

在进行第六轮循环操作时,3和2比较,3>2,位置对调,变成[2, 3, 1, 4,  5,  6, 7, 8, 9],此时lastIndex = j = 0,3和1比较,3>1,位置对调,变成[2, 1, 3, 4,  5,  6, 7, 8, 9],此时lastIndex = j = 1,3和4比较,3<4,位置不变,如此类推,本轮循环结束时,lastIndex = 1,那么此时border = 1,在第七轮循环里面,只需要进行1次比较就可以结束第七轮循环。

但是,优化第二版仍不是最优方案,上面的两种优化方案只是减少每轮的操作次数,还有一种可以直接减少循环的轮数,那就是鸡尾酒算法排序,这个留到下一篇更新。

 


以上是关于冒泡排序算法以及它的优化方案的主要内容,如果未能解决你的问题,请参考以下文章

冒泡排序,算法以及优化

冒泡排序以及冒牌排序优化算法

冒泡排序到快速排序做的那些优化

鸡尾酒算法排序

常见排序算法的优化

我知道你会冒泡排序,但是你会优化冒泡排序吗?