javascript常用的几种排序算法

246 阅读6分钟

冒泡排序

概述

冒泡排序就是把最大(或最小)往后调,相邻的2个值进行比较,如果符合设定条件则进行位置对调。

image.gif

思路

将数组的每个值拿出来与下一个值做对比,如果符合设定的条件,则将下一个值保存到临时变量中,然后2个值位置对调。

具体实现解析

[3,1,2,5,0]排序为正序为例。

  1. 声明第一个for循环,作用确保数组中的每个值与下一个值比对过,如下:

    循环得到的结果:

  • 第1次: [1,2,3,0,5]
  • 第2次: [1,2,0,3,5]
  • 第3次: [1,0,2,3,5]
  • 第4次: [0,1,2,3,5]
  1. 声明第二个for循环,作用就是每次数组从第一个值开始与下个值做比对,符合条件则对调位置。

    第一次循环得到的结果:

  • 第1次: [1,3,2,5,0] 3和1做对比,3大于1符合条件,进行对调位置
  • 第2次: [1,2,3,5,0] 2和3做对比,3大于2符合条件,进行对调位置
  • 第3次: [1,2,3,5,0] 5和3做比对,3小于5不符合条件,所以没有对调位置
  • 第4次: [1,2,3,0,5] 5和0做比对,5大于0符合条件,进行对调位置
  1. 小优化点,第二个循环len-i目的就是避免不必要的计算。比如:
    声明的第1个for循环,第一次遍历得到的结果是[1, 2, 0, 3, 5],最后1个值已经是最大,遍历第二遍的时候就不进行对比。

代码如下:

console.log('正序:', sort([3,1,2,5,0], 'asc'))
console.log('倒序:', sort([3,1,2,5,0], 'desc'))

function sort(arr, dir){
    const len = arr.length -1
    for(let i = 0; i < len; i++){
        for(let j = 0; j < len-i; j++){
            const nextIndex = j + 1
            const isPass = (dir === 'desc' && arr[j] < arr[nextIndex]) 
                || (dir === 'asc' && arr[j] > arr[nextIndex])
            if(isPass){
                const temp = arr[nextIndex]
                arr[nextIndex] = arr[j]
                arr[j] = temp
            }
        }
    }
    return arr
}

选择排序

概述

选择排序就是选出最小(或最大)的一个值,存放在数组的起始位置,然后再从剩余的中寻找到最小(大)值,然后放到已排序的值末尾。

image.png

思路

将数组中值与标记值(最小或最大的值)做对比,如果符合设定条件,则将当前数值的下标赋值给标记值(最小或最大的值)markIndex,等内循环结束找到最小(或最大)的值的下标之后再进行位置对调。

具体实现解析

一、声明2个变量

  1. markIndex 用于保存最小(最大)的值索引(标记索引)

  2. temp 用于数值 位置对调时临时保存数值 二、遍历,声明第一个for循环

  3. markIndex设置为i, 把数组第一值标记为最小(最大)。

  4. 声明第二个for循环,用于找出最小(最大)的值。j=i+1的作用是 遍历的值与标记的值做比对时(排除自身)。

  5. 符合设定的条件则将值的索引标识为 标记索引。

  6. 等声明的第一个for,每次循环结束之后,将起始(已排序的值)位置的值保存到temp变量中,标记为最小(最大)的值放到起始(已排序的值)位置,完成位置对调。

    声明的第一个for,每次遍历得到的结果,如下:

    例子:需要排序的数组 [2,1,3,5,0,9,10],结果为正序:

    • 第1次 [2,1,3,5,0,9,10] => [0, 1, 3, 5, 2, 9, 10] 0和2位置进行了对调
    • 第2次 [0, 1, 3, 5, 2, 9, 10] => [0, 1, 3, 5, 2, 9, 10] 这次没有变化,因为已排序的值末尾位置的数值比右侧的数值都小
    • 第3次 [0, 1, 3, 5, 2, 9, 10] => [0, 1, 2, 5, 3, 9, 10] 2和3位置进行了对调
    • 第4次 [0, 1, 2, 5, 3, 9, 10] => [0, 1, 2, 3, 5, 9, 10] 3和5位置进行了对调
    • 第5次 [0, 1, 2, 3, 5, 9, 10] => [0, 1, 2, 3, 5, 9, 10] 这次没有变化,因为顺序已经是从小到大达到了预想的结果
    • 第6次 [0, 1, 2, 3, 5, 9, 10] ...

代码如下:

console.log('正序:', sort([2,1,3,5,0,9,10]))

function sort(arr){
    let markIndex, temp
    const len = arr.length
    for(let i = 0; i < len - 1; i++){
        markIndex = i
        for(let j = i + 1; j < len; j++){
            if(arr[j] < arr[markIndex]){
                markIndex = j
            }
        }
        temp = arr[i]
        arr[i] = arr[markIndex]
        arr[markIndex] = temp
    }
    return arr
}

插入排序

思路

比较前后2个值,如果符合条件则进行位置对调并且依次往前比较。

具体实现解析

[5,2,0,6,8,9]为例,从小到大的插入排序整个过程如下所示:

第1轮: [5,2,0,6,8,9] => [2,5,0,6,8,9] 2与5对比,2比5小符合条件,交换位置并依次往前比较

  • [2,5,0,6,8,9] 5与2对比,5比2大不符合条件,无需交换位置

第2轮: [2,5,0,6,8,9] => [2,0,5,6,8,9] 0与5对比,0比5小符合条件,交换位置再并依次往前比较

  • [2,0,5,6,8,9]=> [0,2,5,6,8,9] 0与2对比,0比2小符合条件,交换位置(前面已没有值,进入第3轮) 第3轮: [0,2,5,6,8,9] 6与5对比,6比5大不符合条件,无需交换。

...

代码如下:

const arr = [5,2,0,6,8,9]

console.log('正序:', sort(arr))

function sort(arr){
    const len = arr.length
    
    for(let i = 0; i < len; i++){
        let index = i + 1 

        // 依次比较前后2个值,如果符合条件则进行位置对调
        while(index >= 0 && arr[index] < arr[index - 1]){
            const temp = arr[index]
            arr[index] = arr[index - 1]
            arr[index - 1] = temp
            // 依次往前比较
            index --
        }
        console.log(arr)
    }
    return arr
}

希尔排序

概述

希尔排序,也称为“递减增量排序”算法,是插入排序的一种更高效的改进版本。

直接插入排序和希尔排序的比较:

  1. 直接插入排序是稳定的,而希尔排序是不稳定的(相等数据可能互换位置)。
  2. 直接插入排序更适合于原始记录基本有序的集合。
  3. 希尔排序的比较次数和移动次数都要比直接插入排序少,当N越大时,效果越明显。
  4. 在希尔排序中,增量序列(间隔)gap的取法必须满足:最后一个步长必须是1。
  5. 直接插入排序也适用于链式存储结构,希尔排序不适用于链式结构。

思路

先将整个待排序的记录序列分割成若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。

具体实现解析

序列:[8,9,1,7,2,3,4,6,0],设置间隔分组:gap = Math.floor(数组长度/2)

c825f3d2dcc60f8d9c4b97bebe8ba22.png 代码如下:

function shellSort(arr){
    const len = arr.length
    // 获取间隔(步长)
    let gap = Math.floor(len / 2)

    // 循环gap间隔,逐步缩小为1
    for(gap; gap > 0; gap = Math.floor(gap / 2)){

        // 对当前分组执行直接插入排序
        for(let i = gap; i< len; i++){
            let temp = arr[i], j
            for(j=i-gap; j >=0 && arr[j] > temp; j-=gap){
                arr[j+gap] = arr[j]
            }
            arr[j+gap] = temp
        }
    }
    return arr
}


console.log(shellSort([8,9,1,7,2,3,4,6,0]))

快速排序

概述

快速排序是对冒泡排序的一种改进,采用的是分治策略(一般与递归结合使用),以减少排序过程中的比较次数。

思路

选择一个基准数,通过一轮排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小。然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以达到全部数据变成有序。

  1. 从数列中挑出一个基准值。
  2. 将所有比基准值小的摆放在基准前面,所有比基准值大的摆在基准的后面(相同的数可以到任一边),在这个分区退出之后,该基准就处于数列的中间位置。
  3. 递归地把基准值前面的子数列和基准值后面的子数列进行排序。 代码如下:
/**
 * 排序
 * @param { Array } arr
 * @param { Number } left 左边界
 * @param { Number } right 右边界
 */
function sort(arr, left, right){
    let len = arr.length
    let partitionIndex //基准

    left = left? left : 0
    right = right ? right : len - 1

    // 结束条件
    if(right > left){
        partitionIndex = partition(arr, left, right)
        sort(arr, left, partitionIndex - 1)
        sort(arr, partitionIndex + 1, right)
    }
   
    return arr
}

/**
 * 分组
 */
function partition(arr,left,right){
    let pivot = left //基准
    let index = pivot + 1
    for (let i = index; i <= right; i++) {
        if(arr[i] < arr[pivot]){
            swap(arr, i, index)
            index ++
        }
    }

    swap(arr, pivot, index - 1)

    return index - 1
}
/**
 * 交换
 */
function swap(arr, i, j){
    const temp = arr[i]
    arr[i] = arr[j]
    arr[j] = temp
}

console.log(sort([5,1,0,3,4,6]))