zoukankan      html  css  js  c++  java
  • JS 这一次彻底理解冒泡排序

    壹 ❀ 引

    在面试环节中,算法总是逃不掉的一关,对于我这种非班科出生且大学不接触数学的人来说,逻辑思维方面确实较为欠缺,昨晚跟百度的同学聊到凌晨,自我感觉差距较大,受了不小打击,所以决心抓一抓算法,做做相关训练题,我可能没天赋,但做一点总比什么都不做要强,这篇文章从十大排序中的冒泡排序说起。

    贰 ❀ 冒泡排序基本概念

    在水中,空气的密度比水轻,所以水中的气泡会不断上浮,这是我们生活中所理解的冒泡。而冒泡排序的概念也是如此。

    对于一个数组,我们会比较相邻的两个元素,如果前者比后者大,则需要交换两者的位置,也就是较大的后沉,较小的往前浮,看个最简单的例子。

    let arr = [2, 1];
    //比较索引0,与索引1的两个元素,如果前者比后者大,交换两者位置。
    if (arr[0] > arr[1]) {
        //交换位置
        arr[0] = [arr[1], arr[1] = arr[0]][0]
    };
    arr; //[1,2]
    

    当然,这个例子特别简单,因为只包含2个元素,所以我们只需要遍历一次,且只比较一次即可得到最终排序结果。

    我们将数组升级,再加一项:

    let arr = [3,2,1];
    

    此时只比较一次很明显解决不了问题,不过数量不多,我们尝试拆分比较过程。

    • 第一次遍历
      • 第一次比较
        • 3比2大,交换位置,得到数组[2,3,1];
      • 第二次比较
        • 3比1大,再次交换位置,得到数组[2,1,3];

    此时第一次遍历结束,我们发现目前数组并没有排序完成,还需要再遍历一次:

    • 第二次遍历
      • 第一次比较
        • 2比1大,交换位置,得到数组[1,2,3]

    此时排序完成,为什么第二次只需要比较一次呢?这是因为第一次遍历一定会将数组当前最大项移到数组尾部,所以第二次比较没有意义。

    为了论证这一点,我们将数组再次升级,再多加一项,拆分步骤如下:

    let arr = [4,2,3,1];
    
    • 第一次遍历
      • 第一次比较
        • 4比2大,交换位置,得到数组[2,4,3,1];
      • 第二次比较
        • 4比3大,交换位置,得到数组[2,3,4,1];
      • 第三次比较
        • 4比1大,交换位置,得到数组[2,3,1,4],此时第一次遍历结束,看来还需要再次遍历。
    • 第二次遍历
      • 第一次比较
        • 2比3小,不需要交换位置,数组维持不变[2,3,1,4];
      • 第二次比较
        • 3比1大,交换位置,得到数组[2,1,3,4];

    你看,我们又不需要进行第三次比较,因为第一次遍历已经将最大的4移动到了数组末尾,所以第三次比较毫无意义。由于数组还没排序完毕,我们需要进行第三次遍历:

    • 第三次遍历
      • 第一次比较
        • 2比1大,交换位置,得到数组[1,2,3,4];

    此时我们已经得到了最终排序,所以不需要进行第二次比较与第三次比较。

    细心的同学可能已经发现了一个规律,针对同一个数组,每遍历一次,都会将当前最大的元素移动到数组末尾,且下次遍历的比较次数需要相比上次少一次。

    再说的直白点,还是上面的例子,第1遍历需要比较3次,确定了当前最大数4。第二次遍历只需要比较2次,确定了当前最大数3,第三次遍历只需要比较1次,确定了当前最大数2。

    我们综合上诉三个例子来做个总结:

    • 数组有2项时,需要遍历1次,比较1次。
    • 数组有3项时,需要遍历2次,第一次比较2次,第二次比较1次。
    • 数组有4项时,需要遍历3次,第一次比较3次,第二次比较2次,第三次比较1次。

    我们好像得到了一个结论,当数组有N项时,需要遍历N-1次,且每次遍历的比较次数为N - 当前遍历次数(也就是i+1,因为是从i=0开始,所以加个1)

    所以我们可以用2个for循环,外层用于定义遍历次数,内层用于定义比较次数。

    叁 ❀ 冒泡排序实现

    知道了这个规律,我们直接上代码:

    let arr = [3, 1, 5, 4, 7, 6, 0, 2];
    
    function bubbleSort(arr) {
        var length = arr.length,
            i = 0;
        // 遍历次数为length-1次
        for (; i < length - 1; i++) {
            // 当前遍历的比较次数为length - 当前遍历次数
            for (var j = 0; j < length - i - 1; j++) {
                if (arr[j] > arr[j + 1]) {
                    arr[j] = [arr[j + 1], arr[j + 1] = arr[j]][0];
                };
            };
        };
        return arr;
    };
    
    bubbleSort(arr); //[0, 1, 2, 3, 4, 5, 6, 7]
    

    百度百科给的代码示例外层使用了while循环,思路一样,代码如下:

    function bubbleSort(arr) {
        var i = arr.length, j;
        var tempExchangVal;
        while (i > 0) {
            for (j = 0; j < i - 1; j++) {
                if (arr[j] > arr[j + 1]) {
                    tempExchangVal = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = tempExchangVal;
                };
            };
            i--;
        };
        return arr;
    };
     
    var arr = [3, 2, 4, 9, 1, 5, 7, 6, 8];
    

    但是这段代码有个问题,前面我们知道当数组有N项,就需要遍历N-1次,很明显这段代码中使用while(i>0)会无意义的多执行一次,这里改成while(i>1)会好一点。

    那么关于数组冒泡排序就说到这里,本人笨拙,为了解释清楚冒泡的过程,只能采取找规律来加深理解,若文中有描述不当之处,还望指出,那么到这里本文结束。

    肆 ❀ 参考

    排序-冒泡排序
    三分钟彻底理解冒泡排序

  • 相关阅读:
    pyqt的setObjectName()/findChild()
    pyqt的多Button的点击事件的槽函数的区分发送signal的按钮。
    分布式存储
    QTableWidget的表头颜色设置
    QListView的子项的ViewMode
    QHeaderView的点击和双击事件
    LeetCode(63):不同路径 II
    LeetCode(62):不同路径
    LeetCode(61):旋转链表
    LeetCode(60): 第k个排列
  • 原文地址:https://www.cnblogs.com/echolun/p/12638903.html
Copyright © 2011-2022 走看看