怎样理解冒泡排序

一.什么是冒泡排序

1.不断比较两个相邻元素,将大的往后或者往前排,就像水里的泡泡一样,越往后越接近水面泡泡体积越大,由此得名.

用图来说明:

冒泡排序

2.用java代码实现冒泡排序


    // ============================= 第0版 开始 ======================
    @Test
    public void sort01() {
        int []arr = {1, 2, 5, 6, 22, 23, 50, 43, 65, 78, 88};
        System.out.println("原始数组:" + Arrays.toString(arr) + ",数组长度" +arr.length);
        System.out.println("第0版 冒泡排序");
        firstBubbleSort0(arr);
        System.out.println("排序后数组" + Arrays.toString(arr));
    }
    

    /**
     * 普通版 第0版冒泡
     * @param array
     */
    private void firstBubbleSort0(int[] array) {
        int ifTimes = 0;
        int swapTimes = 0;
        for (int i = 0; i < array.length; i++) {
            for (int j = 0; j < array.length -1; j++) {
                // if次数
                ifTimes ++;

                if (array[j] > array[j+1]) {
                    int temp;
                    temp = array[j];
                    array[j] = array[j+1];
                    array[j+1] = temp;
                    swapTimes ++ ;
                }
            }
            System.out.println("第" + i+ "次后结果为:" + Arrays.toString(array));
        }
        System.out.println("if判断次数ifTimes = " + ifTimes);
        System.out.println("发生交换次数swapTimes = " + swapTimes);

    }
    // ============================= 第0版 结束 ======================

原始数组:[1, 2, 5, 6, 22, 23, 50, 43, 65, 78, 88]数组长度11
第0版 冒泡排序
第0次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第1次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第2次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第3次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第4次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第5次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第6次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第7次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第8次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第9次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第10次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
if判断次数ifTimes = 110
发生交换次数swapTimes = 1
排序后数组[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]

可以看出冒泡排序的空间复杂度为O(1),时间负责度为O(n^2)

3.对于每轮排出来的最后(大)元素,其实可以不参与接下来的排序

一起来看看代码实现(在内部循环中每次遍历终点 array.length - i,i为外层循环 ) 


    // ============================= 第一版 开始 ======================
    @Test
    public void testUnSortArray() {
        int []arr = {43, 22, 6, 5, 23, 2, 50, 65, 78, 88, 1};
        System.out.println("原始数组:" + Arrays.toString(arr));
        firstBubbleSort(arr);
        System.out.println("排序后数组" + Arrays.toString(arr));
    }


    /**
     * 普通版 第一版冒泡
     * @param array
     */
    private void firstBubbleSort(int[] array) {
        int ifTimes = 0;
        int swapTimes = 0;
        for (int i = 0; i < array.length; i++) {
            for (int j = 0; j < array.length -i -1; j++) {
                // if次数
                ifTimes ++;

                if (array[j] > array[j+1]) {
                    int temp;
                    temp = array[j];
                    array[j] = array[j+1];
                    array[j+1] = temp;
                    swapTimes ++ ;
                }
            }
            System.out.println("第" + i+ "次后结果为:" + Arrays.toString(array));
        }
        System.out.println("ifTimes = " + ifTimes);
        System.out.println("swapTimes = " + swapTimes);

    }

打印结果:

原始数组:[43, 22, 6, 5, 23, 2, 50, 65, 78, 88, 1]
第0次后结果为:[22, 6, 5, 23, 2, 43, 50, 65, 78, 1, 88]
第1次后结果为:[6, 5, 22, 2, 23, 43, 50, 65, 1, 78, 88]
第2次后结果为:[5, 6, 2, 22, 23, 43, 50, 1, 65, 78, 88]
第3次后结果为:[5, 2, 6, 22, 23, 43, 1, 50, 65, 78, 88]
第4次后结果为:[2, 5, 6, 22, 23, 1, 43, 50, 65, 78, 88]
第5次后结果为:[2, 5, 6, 22, 1, 23, 43, 50, 65, 78, 88]
第6次后结果为:[2, 5, 6, 1, 22, 23, 43, 50, 65, 78, 88]
第7次后结果为:[2, 5, 1, 6, 22, 23, 43, 50, 65, 78, 88]
第8次后结果为:[2, 1, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第9次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第10次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
if判断次数ifTimes = 55
发生交换次数swapTimes = 22
排序后数组[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]

时间复杂度为O(n*(n-1)/2)

冒泡排序就算完成了,好了你已经完全掌握了冒泡排序.(如果你真这样想,那你就天真了,文章也应该结束了).

二.冒泡排序优化

1.如果原来的数组接近完全排序

假如数组:

1, 2, 5, 6, 22, 23, 50, 43, 65, 78, 88
当第一轮排序完成后,得到结果

1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88

2.如果还是使用原始的冒泡排序(第一版)

原始数组:[1, 2, 5, 6, 22, 23, 50, 43, 65, 78, 88]
第一版 冒泡排序
第0次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第1次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第2次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第3次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第4次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第5次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第6次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第7次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第8次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第9次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
第10次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]
if判断次数ifTimes = 55
发生交换次数swapTimes = 1
排序后数组[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]

我们发现第0次遍历后,数组已经是完全排序.后面的遍历其实没有必要.

3.来了来了,第二版代码

每次内部循环完之后,判断数组是否已经是有序状态

     // ============================= 第二版 ======================
    @Test
    public void sort22() {
        int []arr = {1, 2, 5, 6, 22, 23, 50, 43, 65, 78, 88};
        System.out.println("原始数组:" + Arrays.toString(arr));
        System.out.println("第二版 冒泡排序(如果数组已经有序,结束排序)");
        sortArrayWithAllArrayIsSorted(arr);
        System.out.println("排序后数组" + Arrays.toString(arr));
    }

    /**
     * 对已经排序的  可以减少开销, 当数组已经是排序状态后 不会继续进行
     * 判断目标是 整个 数组是否已经排序好
     * @param array
     */
    private void sortArrayWithAllArrayIsSorted(int[] array) {
        int ifTimes = 0;
        int swapTimes = 0;

        for (int i = 0; i < array.length; i++) {
            // 是否已经排序好了(是否发生互换 否定)
            boolean isSorted = true;
            for (int j = 0; j < array.length - 1 - i ; j++) {
                // if次数
                ifTimes ++;

                if (array[j] > array[j+1]) {
                    int temp;
                    temp = array[j];
                    array[j] = array[j+1];
                    array[j+1] = temp;
                    isSorted = false;
                    swapTimes ++ ;
                }
            }
            System.out.println("第" + i+ "次后结果为:" + Arrays.toString(array) + ",isSorted=" + isSorted);
            // 如果没有互换 说明已经排序完成
            if (isSorted) {
                break;
            }
        }
        System.out.println("if判断次数ifTimes = " + ifTimes);
        System.out.println("发生交换次数swapTimes = " + swapTimes);
    }


    // ============================= 第二版 结束 ======================

3.1 使用该代码排序接近排序的数组

原始数组:[1, 2, 5, 6, 22, 23, 50, 43, 65, 78, 88]
第二版 冒泡排序(如果数组已经有序,结束排序)
第0次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88],isSorted=false
第1次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88],isSorted=true
if判断次数ifTimes = 19
发生交换次数swapTimes = 1
排序后数组[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]

这种接近完全排序的数组,在这种情况下优势明显.

3.2 如果数组不是接近完全排序

使用第二版 冒泡排序,排序下列数组

原始数组:[2, 5, 6, 1, 22, 23, 43, 50, 65, 78, 88]
第0次后结果为:[2, 5, 1, 6, 22, 23, 43, 50, 65, 78, 88],isSorted=false
第1次后结果为:[2, 1, 5, 6, 22, 23, 43, 50, 65, 78, 88],isSorted=false
第2次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88],isSorted=false
第3次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88],isSorted=true
if判断次数ifTimes = 34
发生交换次数swapTimes = 3
[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]

相比较于第一版,还是减少了很多遍历比较次数.

4.如果数组部分有序,只需要遍历无序部分.

观察3.2中例子, 数组中 22, 23, 43, 50, 65, 78, 88 部分不需要排序,那么遍历的时候能不能把这部分排除呢?

所以咱们第三版排序,在第二版的基础上考虑部分有序,遍历到无序 和 有序的分界处就可以了.

上菜:

    @Test
    public void sort32() {
        int []arr = {2, 5, 6, 1, 22, 23, 43, 50, 65, 78, 88};
        System.out.println("原始数组:" + Arrays.toString(arr));
        System.out.println("第三版 冒泡排序(遍历到无序和有序的边界)");
        sortArrayWithPartyArrayIsSorted(arr);
        System.out.println("排序后数组" + Arrays.toString(arr));
    }

    /**
     * 遍历到无序和有序的边界
     * 判断目标是 部分(前半无序,后半有序 会减少if) 
     * @param array
     */
    private void sortArrayWithPartyArrayIsSorted(int[] array) {

        int ifTimes = 0;
        int swapTimes = 0;

        // 最后一次发生交换的地方
        int lastSwapIndex = 0;

        // 无序部分长度, 每次比较只需要比到这里为止
        int unSortBorder = array.length -1;

        for (int i = 0; i < array.length; i++) {
            // 是否已经排序好了(是否发生互换 否定)
            boolean isSorted = true;
            for (int j = 0; j < unSortBorder; j++) {
                // for次数
                ifTimes ++;

                if (array[j] > array[j+1]) {
                    int temp;
                    temp = array[j];
                    array[j] = array[j+1];
                    array[j+1] = temp;

                    isSorted = false;
                    // 当本轮for完成 j 到 array.length 的位置元素已经排序,下轮不需要遍历
                    lastSwapIndex = j;

                    swapTimes ++ ;
                }
            }
            System.out.println("第" + i+ "次后结果为:" + Arrays.toString(array) + ",isSorted=" + isSorted);
            // 如果没有互换 说明已经排序完成
            // 更新边界
            unSortBorder = lastSwapIndex;

            if (isSorted) {
                break;
            }

        }
        System.out.println("if判断次数ifTimes = " + ifTimes);
        System.out.println("发生交换次数swapTimes = " + swapTimes);
    }
    // ============================= 第三版 结束 ======================

原始数组:[2, 5, 6, 1, 22, 23, 43, 50, 65, 78, 88]
第三版 冒泡排序(遍历到无序和有序的边界)
第0次后结果为:[2, 5, 1, 6, 22, 23, 43, 50, 65, 78, 88],isSorted=false
第1次后结果为:[2, 1, 5, 6, 22, 23, 43, 50, 65, 78, 88],isSorted=false
第2次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88],isSorted=false
第3次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88],isSorted=true
if判断次数ifTimes = 13
发生交换次数swapTimes = 3
排序后数组[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88]

和例子3.2中同样的数组,减少了21次if判断.

5.刚刚我们只考虑了数组的一边是否有边界(有序无序边界),我们能不能考虑数组两边是否有边界呢

思路:i是偶数从左到右冒大泡, 如果i是基数 从右到左冒小泡.

    // 第四版(考虑双向边界)
    @Test
    public void sort43() {
        int []arr = {2, 5, 6, 22, 23, 43, 50, 65, 78, 88, 1};
        sortArrayWithDoublePartyArrayIsSorted(arr);
        System.out.println(Arrays.toString(arr));
    }

    /**
     * 对已经排序的  可以减少开销, 当数组已经是排序状态后 不会继续进行
     * 判断目标是 双边界(从两边观察 是否有已经排好序的部分) 数组是否已经排序好
     * @param array
     */
    private void sortArrayWithDoublePartyArrayIsSorted(int[] array) {
        int temp;
        int ifTimes = 0;
        int swapTimes = 0;

        // 最后一次发生交换的地方
        int lastLeftSwapIndex = 0;
        int lastRightSwapIndex = array.length -1;

        for (int i = 0; i < array.length; i++) {
            // 是否已经排序好了(是否发生互换 否定)
            boolean isSorted = true;

            // 每次从第个开始判断if
            int beginBorder = lastLeftSwapIndex;
            // 无序部分长度, 每次比较只需要比到这里为止
            int endBorder = lastRightSwapIndex;

            if(i % 2 == 0) {
                for (int j = beginBorder; j < endBorder; j++) {
                    // if次数
                    ifTimes ++;
                    //
                    if (array[j] > array[j+1]) {
                        temp = array[j];
                        array[j] = array[j+1];
                        array[j+1] = temp;

                        isSorted = false;
                        // 当本轮for完成 j 到 array.lenght 的位置元素已经排序
                        lastRightSwapIndex = j;

                        // test 代码
                        swapTimes ++ ;
                    }
                }
            } else {
                for (int j = endBorder - 1; j >= 0; j--) {
                    // if次数
                    ifTimes++;
                    //
                    if (array[j] > array[j + 1]) {
                        temp = array[j];
                        array[j] = array[j + 1];
                        array[j + 1] = temp;

                        isSorted = false;
                        // 当本轮for完成 endBorder - j 到 endBorder 的位置元素已排序排序
                        lastLeftSwapIndex = j;

                        // test 代码
                        swapTimes++;
                    }
                }
            }
            System.out.println("第" + i+ "次后结果为:" + Arrays.toString(array) + ",isSorted=" + isSorted);

            // 如果没有互换 说明已经排序完成
            if (isSorted) {
                break;
            }
        }
        System.out.println("if判断次数ifTimes = " + ifTimes);
        System.out.println("发生交换次数swapTimes = " + swapTimes);
    }

以数组:[ 2, 5, 6, 22, 23, 43, 50, 65, 78, 88, 1 ] 为例

第三版结果:

原始数组:[2, 5, 6, 22, 23, 43, 50, 65, 78, 88, 1]
第三版 冒泡排序(遍历到无序和有序的边界)
第0次后结果为:[2, 5, 6, 22, 23, 43, 50, 65, 78, 1, 88],isSorted=false
第1次后结果为:[2, 5, 6, 22, 23, 43, 50, 65, 1, 78, 88],isSorted=false
第2次后结果为:[2, 5, 6, 22, 23, 43, 50, 1, 65, 78, 88],isSorted=false
第3次后结果为:[2, 5, 6, 22, 23, 43, 1, 50, 65, 78, 88],isSorted=false
第4次后结果为:[2, 5, 6, 22, 23, 1, 43, 50, 65, 78, 88],isSorted=false
第5次后结果为:[2, 5, 6, 22, 1, 23, 43, 50, 65, 78, 88],isSorted=false
第6次后结果为:[2, 5, 6, 1, 22, 23, 43, 50, 65, 78, 88],isSorted=false
第7次后结果为:[2, 5, 1, 6, 22, 23, 43, 50, 65, 78, 88],isSorted=false
第8次后结果为:[2, 1, 5, 6, 22, 23, 43, 50, 65, 78, 88],isSorted=false
第9次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88],isSorted=false
第10次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88],isSorted=true
if判断次数ifTimes = 55
发生交换次数swapTimes = 10

第四版结果:

原始数组:[2, 5, 6, 22, 23, 43, 50, 65, 78, 88, 1]
第四版 冒泡排序(考虑双向边界)
第0次后结果为:[2, 5, 6, 22, 23, 43, 50, 65, 78, 1, 88],isSorted=false
第1次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88],isSorted=false
第2次后结果为:[1, 2, 5, 6, 22, 23, 43, 50, 65, 78, 88],isSorted=true
if判断次数ifTimes = 28
发生交换次数swapTimes = 10

第四版结果明显优于第三版结果,但是第四版缺点就是代码量复杂.

git代码:https://github.com/foxiaotao/java_interview/tree/master/src/sort/bubble

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值