基本排序算法的梳理(JavaScript)

205 阅读3分钟

本文为学习排序算法时的自我思路梳理,代码以简单易懂为主。

几个概念

  • 稳定:如果 a 原本在 b 前面,且 a = b,经过排序后,a 仍然在 b 的前面。
  • 不稳定:如果 a 原本在 b 前面,且 a = b,经过排序后,a 可能会出现在 b 的后面。
  • 时间复杂度:一个算法执行所耗费的时间,用 O 表示。
  • 空间复杂度: 运行完一个程序所需内存的大小。
  • 内排序:所有排序操作都在内存中完成。
  • 外排序:数据存放在磁盘中,排序需要通过磁盘和内存的数据传输进行。

几种经典排序算法的对比

1639119421(1).jpg

选择排序

1.基本思想

在未排序序列中找到最小(大)元素,存放到未排序序列的起始位置。在所有的完全依靠交换去移动元素的排序方法中,选择排序属于非常好的一种。

2. JS 代码实现(循环)

function minIndex(array) {
    let index = 0
    for (let i = 1; i < array.length; i++) {
        array[i] < array[index] ? (index = i) : index
    }
    return index
}

function swap(array, i, j) {
    let temp = array[i]
    array[i] = array[j]
    array[j] = temp
}

function sort(array) {
    for(let i = 0; i < array.length-1; i++) {
        let index = minIndex(array.slice(i)) + i
        if(index !== i) {swap(array, index, i)}
    }
    return array
}

快速排序

1. 基本思想

快速排序(Quicksort)是对冒泡排序的一种改进,借用了分治的思想,由C. A. R. Hoare在1962年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

2. JS 代码实现(递归):

function quickSort(array) {
    if(array.length <= 1) {
        return array
    }

    // 确定以数组中的哪个位置基准
    let pivotIndex = Math.floor(array.length / 2)
    let pivot = array.splice(pivotIndex, 1)[0] // 此时的数组已经是剔除了作为基准的数之后的数组了

    // 准备好基准位置左边和右边的空位
    let leftArea = []
    let rightArea = []

    // 对此时数组里的每一项,判断它如果比基准小,就放到左边,否则放到基准右边
    for (let i = 0; i < array.length; i++) {
        array[i] < pivot ? leftArea.push(array[i]) : rightArea.push(array[i])
    }

    return quickSort(leftArea).concat([pivot], quickSort(rightArea))
}

归并排序

1. 基本思想

归并排序是建立在归并操作上的一种有效的排序算法,1945年由约翰·冯·诺伊曼首次提出。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用,且各层分治递归可以同时进行。

归并排序算法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。

2. JS 代码实现(递归)

function mergeSort(array) {
    let len = array.length
    if(len === 1) {return array}  // 只有一个值的数组,即为有序的

    // 数组长度大于1时,将数组分为左右两部分
    let left = array.slice(0, Math.floor(len/2))
    let right = array.slice(Math.floor(len/2))

    // 对左右两部分的数组,分别各自再进行 mergeSort(),然后将左右两有序数组进行 merge 合并
    return merge(mergeSort(left), mergeSort(right))  // 只管合并两个已经排好序的数组
}

// merge 接受两个 有序数组 作为参数
    let merge = (arr1, arr2) => {
        // 当其中一个数组为空数组时,则直接返回另一个数组
        if(arr1.length === 0) {return arr2}
        if(arr2.length === 0) {return arr1}

        // 比较两个有序数组的第一项,取出较小的值后,再对剩余的两个数组继续进行 merge(), 该递归会一直进行到直到其中一个数组为空
        return arr1[0] < arr2[0] ? [arr1[0]].concat( merge(arr1.slice(1), arr2) ) : [arr2[0]].concat( merge(arr2.slice(1), arr1) )
    }

计数排序

1. 基本思想

计数排序本质就是统计不同元素出现的次数,然后将元素依次从小到大放置,每个元素看统计的次数,就紧挨着放置几个同样的元素。 这是通过空间换时间的方式。

2. JS 代码实现

// 对正整数组
function countSort(array) {
    let hashTable = {}, max = 0, result = []

    // 遍历数组
    for(let i = 0; i < array.length; i++) { 
        // 存下数组中的最大值
        array[i] > max ? max = array[i] : max

        // 如果哈希表中没有记录该值,则将该值作为 key,其 value 记为 1;
        // 如果哈希表中已有该值,则将其 value + 1
        if (!(array[i] in hashTable)){
            hashTable[array[i]] = 1
        } else {
            hashTable[array[i]] += 1
        }
    }

    // 遍历哈希表
    for (let j = 0; j <= max; j++) {
        if (j in hashTable) {
            for (let k = 0; k < hashTable[j]; k++) {
                result.push(j)
            }
        }
    }

    return result
}