zoukankan      html  css  js  c++  java
  • 数据结构 6 基础排序算法详解 冒泡排序、三层冒泡排序逐步优化方案详解

    前言

    说到前面,我们已经详解了几种数据结构、包括数组、链表、二叉树、B树、B+树等基本数据结构、当然,我们这节课也叫做数据结构与算法、肯定会包含算法的相关知识、因为在之前已经了解和学习过有关时间复杂度的相关内容。当然也是和算法密切相关的。时间复杂度和空间复杂度共同决定一个算法的好坏、本节,我们将学习有关数组元素排序的几种常用算法。以及使用图画的方式展示出来。为了我们更好的理解与使用。

    冒泡排序

    听这个名字可以大概猜到。就像湖底水草上面的水泡一样,从水底到水面的一个过程。就是排序的其中一轮。我们来画图理解一下:

    假设我们有这样一个长度的数组:需要将元素 顺序排列(从小到大)
    image.png

    指定一个本轮循环的元素jj+1 元素 进行比较。若大于则换位。

    image.png

    这里明显 49>38 调换位置。
    image.png

    指针位移,再次开始与 i+1 个元素比较;
    image.png

    65>49 很显然,不进行换位。下移一位。
    image.png

    97>65 则不进行换位。位移下一位。
    image.png

    发现这里需要进行换位操作。因为97>76
    image.png

    位移下一位。
    image.png

    比较后,进行换位操作。
    image.png

    位移下一位。。。。这里省略步骤 到最后一步我们会发现
    image.png

    当指针到达(len-1)位置的时候,其实已经比较完了。所以我们通过这一轮的冒泡排序。一个最大的元素已经被确认出来了。

    重复以上的过程,从第二个元素继续开始。再次重复这样的过程。实现排序。

    下次的终止位置。则只需要比较到上一次位置-1处即可。

    JAVA 代码示例。

    第一版

    int[] arrays = {49, 38, 65, 97, 76, 13, 27, 49};
    
    public static void sort1(int[] arrays) {
    
        int allChange = 0;
        for (int i = 0;i<arrays.length;i++) {
    
            for (int j = 0 ;j<arrays.length-i-1;j++) {
    
                if (arrays[j] > arrays[j+1]) {
    
                    int temp = arrays[j];
                    arrays[j] = arrays[j+1];
                    arrays[j+1] = temp;
    
                }
                allChange++;
            }
            System.out.println(Arrays.toString(arrays));
        }
    
        System.out.println("总比较次数:"+allChange);
    }
    

    第一版的冒泡排序通过两个FOR 循环的形式实现

    外层 for循环用于控制整体程序的运行次数。也就是数组的长度。
    内层 for用于与后面一位数比较大小。从而实现交换的逻辑。

    如此一来,第一版冒泡排序的时间复杂度为 O(N的平方)

    [38, 49, 65, 76, 13, 27, 49, 97]
    [38, 49, 65, 13, 27, 49, 76, 97]
    [38, 49, 13, 27, 49, 65, 76, 97]
    [38, 13, 27, 49, 49, 65, 76, 97]
    [13, 27, 38, 49, 49, 65, 76, 97]
    ----------------------------------注意这段分隔符的意义
    [13, 27, 38, 49, 49, 65, 76, 97]
    [13, 27, 38, 49, 49, 65, 76, 97]
    [13, 27, 38, 49, 49, 65, 76, 97]
    总比较次数:28
    

    第二版

    通过观察控制台的输出,我们发现,其实在循环进行到第5次的时候,其实数组已经变得井然有序了。但是我们的算法仍然兢兢业业的做4次无用功。所以这里就是一个需要优化的地方。

    通过加入一个isChange的变量。用来记录本次循环是否发生了变化。若没有发生元素的交换。则后面的循环直接跳出即可。

    int[] arrays = {49, 38, 65, 97, 76, 13, 27, 49};
    
    public static void sort2(int[] arrays) {
    
        int allChange = 0;
    
        for (int i = 0; i < arrays.length; i++) {
    
            boolean isChange = true;
    
            for (int j = 0; j < arrays.length - i - 1; j++) {
    
                if (arrays[j] > arrays[j + 1]) {
    
                    int temp = arrays[j];
                    arrays[j] = arrays[j + 1];
                    arrays[j + 1] = temp;
    
                    isChange = false;
                }
                allChange++;
            }
            System.out.println(Arrays.toString(arrays));
            if (isChange) {
                break;
            }
        }
        System.out.println("总比较次数:"+allChange);
    }
    

    通过控制台输出的内容我们不难发现。其实循环在第五次的时候,已经完成了元素的交换,但是为何又输出了一遍?

    [38, 49, 65, 76, 13, 27, 49, 97]
    [38, 49, 65, 13, 27, 49, 76, 97]
    [38, 49, 13, 27, 49, 65, 76, 97]
    [38, 13, 27, 49, 49, 65, 76, 97]
    [13, 27, 38, 49, 49, 65, 76, 97]
    --------------------------------
    [13, 27, 38, 49, 49, 65, 76, 97]
    

    其实在第四遍走完后,排序已经是完成了的。但是因为不知道下次时候会有元素的交换,所以在此走了一遍,因为没有交换元素,则在第五遍执行完毕后,跳出循环。

    再次优化,有序长度的界定

    何谓有序长度的界定呢?我们重新选择一个数组来打印一次,发现这里面需要优化的位置点。

    选定一个特殊数组:[3,4,2,1,5,6,7,8]

    通过短暂的观察,我们可以发现,5,6,7,8 这几位元素已经是处于一个有序的状态的。那么我们现有的算法,怎么去对于这些有序序列的比较。从而优化性能呢?我们先用这个数组来跑一遍

    [3, 2, 1, 4, 5, 6, 7, 8]
    [2, 1, 3, 4, 5, 6, 7, 8]
    [1, 2, 3, 4, 5, 6, 7, 8]
    ------------------------
    [1, 2, 3, 4, 5, 6, 7, 8]
    

    通过测试后发现,现有的算法对于数据的比较只需要三次即可排列好这个数组,但是我们会发现。在执行第一遍的时候,若遇到一些有序的元素。

    例如:第一遍已经产生的 [4, 5, 6, 7, 8] 算法还是在第二遍执行的时候,重复进行了比较。我们能不能想个办法。跳过这些重复的内容呢?

    我们可以控制内层比较的长度啊!!!

    想要跳过一些元素的比较,那么就需要得到这个有序内容的长度。从而控制第二层循环,不要比较这些有序长度即可。

    如何确定有序长度。

    当然是若发生比较的时候,

        public static void sort3(int[] array) {
    
            //用来记录最后一次发生变化的位置
            int lastChangeIndex = 0;
            //用来表示需要比较的长度。只需要比较到之前即可
            int sortLength = array.length - 1;
    
            int allChange = 0;
    
            for (int i = 0; i < array.length; i++) {
    
                boolean isChange = true;
    
                for (int j = 0;j< sortLength;j++) {
    
                    if (array[j] > array[j + 1]) {
    
                        int temp = array[j];
                        array[j] = array[j + 1];
                        array[j + 1] = temp;
    
                        isChange = false;
                        //记录发生变化的位置
                        lastChangeIndex = j;
                    }
                    //用于记录比较次数
                    allChange++;
                }
                sortLength = lastChangeIndex;
                System.out.println(Arrays.toString(array));
                //若无改变则跳出循环
                if (isChange) {
                    break;
                }
            }
            System.out.println("总比较次数:"+allChange);
        }
    

    最终我们得出,我们的算法只需要运行10次比较。即可排序完成。再来看看我们第二版本的算法次数

    --------------------- 第三版
    [3, 2, 1, 4, 5, 6, 7, 8]
    [2, 1, 3, 4, 5, 6, 7, 8]
    [1, 2, 3, 4, 5, 6, 7, 8]
    [1, 2, 3, 4, 5, 6, 7, 8]
    总比较次数:10
    
    ----------------------第二版
    [3, 2, 1, 4, 5, 6, 7, 8]
    [2, 1, 3, 4, 5, 6, 7, 8]
    [1, 2, 3, 4, 5, 6, 7, 8]
    [1, 2, 3, 4, 5, 6, 7, 8]
    总比较次数:22
    ----------------------第一版
    [3, 2, 1, 4, 5, 6, 7, 8]
    [2, 1, 3, 4, 5, 6, 7, 8]
    [1, 2, 3, 4, 5, 6, 7, 8]
    [1, 2, 3, 4, 5, 6, 7, 8]
    [1, 2, 3, 4, 5, 6, 7, 8]
    [1, 2, 3, 4, 5, 6, 7, 8]
    [1, 2, 3, 4, 5, 6, 7, 8]
    [1, 2, 3, 4, 5, 6, 7, 8]
    总比较次数:28
    

    小结

    通过一次一次的优化,使得我们的冒泡排序从最初的比较次数28,通过加入有无调换顺序的判断后 优化为22 再到记录有序序列的下标。变为更优化的10次。是算法优化的结果,也是我们在慢慢进步的效果。

    下节课我们将学习到一个更加优化的排序算法。鸡尾酒排序

  • 相关阅读:
    For each···in / For···in / For···of
    JavaScript object
    specific word count (index of )
    history of program
    js的回调函数
    promise
    js的事件流事件机制
    js的closures(闭包)
    baidu-map
    基于封装优点的类设计习惯
  • 原文地址:https://www.cnblogs.com/ChromeT/p/12763772.html
Copyright © 2011-2022 走看看