冒泡排序

14 篇文章 0 订阅
4 篇文章 0 订阅

冒泡排序的思路很简单——从头至尾遍历数组元素,若前一项大于(或小于)后一项,则交换相邻两项。单次遍历整个数组可将某一个元素排列到正确位置,因此需要遍历元素数量n次。在代码中体现也就是内外两层循环,内层循环负责遍历中两两元素的交换操作,外层负责遍历次数控制。

首先看版本1:

/**
 * 冒泡排序效率最低写法,没有任何优化步骤
 * @param arrayToSort
 */
public static void sortAlpha(int[] arrayToSort) {
    for (int i = 0; i < arrayToSort.length; i++) {
        for (int j = 0; j < (arrayToSort.length - 1); j++) {
            int tmp;
            if (arrayToSort[j] > arrayToSort[j + 1]) {
                tmp = arrayToSort[j];
                arrayToSort[j] = arrayToSort[j+1];
                arrayToSort[j+1] = tmp;
            } 
        }
    }
}

很明显的两层循环,这里按从小到大排序,因此弱发现后一元素比当前元素大,则交换两个元素的位置,直到比较到最后一个元素为止。因为是jj+1比较,为避免数组越界,内层for循环需以arrayToSort.length - 1做边界。

这个版本中没有做任何优化,因为遍历n次后,数组中后n个元素实际已经是排好序的了,因此不需要再进行比较了。所以内层循环还可以进一步优化,得到版本2:

/**
 * 冒泡排序优化版本1,减少内层循环遍历个数,已经排序好的元素无需遍历。
 * @param arrayToSort
 */
public static void sortBeta(int[] arrayToSort) {
    for (int i = 0; i < arrayToSort.length; i++) {
        for (int j = 0; j < (arrayToSort.length - 1 - i); j ++) {
            int tmp;
            if (arrayToSort[j] > arrayToSort[j + 1]) {
                tmp = arrayToSort[j];
                arrayToSort[j] = arrayToSort[j+1];
                arrayToSort[j+1] = tmp;
            }
        }
    }
}

由于遍历n次后,后n项已经排好序了,因此内层循环上界再减去遍历次数即可,即arrayToSort.length - 1 - i这样每次内层遍历便可减少n次比较操作,提升了效率。

虽然这时内层循环已经优化操作次数,但如果给一个部分有序的数列,如

{1, 2, 3, 4, 8, 7, 6, 5}


遍历前四次后,数组实际已经排好序了,这时就不需要再进行比较操作了,因此除了前四次排序,仅在需要一次遍历就可检验出数组有序,也就是公共遍历5次数组就可完成排序了。在当前的逻辑下,会导致出现3次无意义的遍历。
解决这个问题的方案也很简单,若数组已经有序,则不会出现交换操作。因此仅需设定一个标志变量,当有交换时置成需要排序(检验)的状态,当所有元素已经有序,检验过程没有交换操作,那么再下次遍历数组直接退出即可,便是版本3:

/**
 * 冒泡排序优化版2,减少内部排序遍历个数,并且添加了数据是否有序的检验,
 * 若数组已经有序,无需再进行遍历,直接退出。
 * @param arrayToSort
 */
public static void sortGamma(int[] arrayToSort) {
    boolean needSort = true;
    for (int i = 0; i < arrayToSort.length; i++) {
        if (!needSort) {
            break;
        }
        for (int j = 0; j < (arrayToSort.length - 1 - i); j ++) {
            needSort = false;
            int tmp;
            if (arrayToSort[j] > arrayToSort[j + 1]) {
                tmp = arrayToSort[j];
                arrayToSort[j] = arrayToSort[j+1];
                arrayToSort[j+1] = tmp;
                needSort = true;
            }
        }
    }
}

在每次进入内部循环时重置标志变量,若进行了交换操作则相应改变,进入内层循环前进行判断,若不需要再遍历,直接退出。

接下来写一些测试代码,来验证三种方案的效率。生成两个数组:一个完全逆序,长度为9999;另一个完全正序,长度同样为9999(不用再排序了),然后分别使用三种排序,统计排序时间,以下是一种方法的调用:

public static void main(String[] args) {
    final int len = 9999;
    int test[] = new int[len];
    for (int i = 0; i < test.length; i++) {
        test[i] = len - 1 - i;
    }

    int testNoNeedSort[] = new int[len];
    for (int i = 0; i < testNoNeedSort.length; i ++) {
        testNoNeedSort[i] = i;
    }

    long startTimeA = System.currentTimeMillis();
    BubbleSort.sortGamma(test);
    long endTimeA = System.currentTimeMillis();
    System.out.println("Time use A:" + (endTimeA - startTimeA));

    long startTimeB = System.currentTimeMillis();
    BubbleSort.sortGamma(testNoNeedSort);
    long endTimeB = System.currentTimeMillis();
    System.out.println("Time use A:" + (endTimeB - startTimeB));

}

运行三种排序,统计到的时间:

版本逆序(最差情况)ms正序(最好情况)ms
版本110766
版本26242
版本3601

最好情况下由于仅需比较,不需要交换,因此三种版本使用时间均比最差情况短,但由于第三种增加了交换情况的判断,仅需遍历一遍就可完成。
最差情况下由于版本2,版本3对内层排序进行了操作数优化,因此显著优于完全没有优化的版本1。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值