基础排序算法

冒泡

插入排序

选择排序

进阶排序算法


归并排序

时间复杂度就是 O(nlog(n))

思路分析

归并排序是对分治思想的典型应用,它按照如下的思路对分治思想“三步走”的框架进行了填充:

  • 分解子问题:将需要被排序的数组从中间分割为两半,然后再将分割出来的每个子数组各分割为两半,重复以上操作,直到单个子数组只有一个元素为止。
  • 求解每个子问题:从粒度最小的子数组开始,两两合并、确保每次合并出来的数组都是有序的。(这里的“子问题”指的就是对每个子数组进行排序)。
  • 合并子问题的解,得出大问题的解:当数组被合并至原有的规模时,就得到了一个完全排序的数组

    真实排序过程演示

    下面我们基于归并排序的思路,尝试对以下数组进行排序:
    image.png
    首先重复地分割数组,整个分割过程如下
    首次分割,将数组整个对半分:
    image.png
    二次分割,将分割出的左右两个子数组各自对半分:
    image.png
    三次分割,四个子数组各自对半分后,每个子数组内都只有一个元素了:
    image.png
    接下来开始尝试解决每个子问题。将规模为1的子数组两两合并为规模为2的子数组,合并时确保有序,我们会得到这样的结果:
    image.png
    继续将规模为2的按照有序原则合并为规模为4的子数组:
    image.png
    最后将规模为4的子数组合并为规模为8的数组:
    image.png
    整个数组就完全有序了。

编码实现

通过上面的讲解,我们可以总结出归并排序中的两个主要动作:

  • 分割
  • 合并

这两个动作是紧密关联的,分割是将大数组反复分解为一个一个的原子项,合并是将原子项反复地组装回原有的大数组。整个过程符合两个特征:

  1. 重复(令人想到递归或迭代)
  2. 有去有回(令人想到回溯,进而明确递归这条路)

因此,归并排序在实现上依托的就是递归思想。

  1. function mergeSort(arr) {
  2. const len = arr.length
  3. // 处理边界情况
  4. if(len <= 1) {
  5. return arr
  6. }
  7. // 计算分割点
  8. const mid = Math.floor(len / 2)
  9. // 递归分割左子数组,然后合并为有序数组
  10. const leftArr = mergeSort(arr.slice(0, mid))
  11. // 递归分割右子数组,然后合并为有序数组
  12. const rightArr = mergeSort(arr.slice(mid,len))
  13. // 合并左右两个有序数组
  14. arr = mergeArr(leftArr, rightArr)
  15. // 返回合并后的结果
  16. return arr
  17. }
  18. function mergeArr(arr1, arr2) {
  19. // 初始化两个指针,分别指向 arr1 和 arr2
  20. let i = 0, j = 0
  21. // 初始化结果数组
  22. const res = []
  23. // 缓存arr1的长度
  24. const len1 = arr1.length
  25. // 缓存arr2的长度
  26. const len2 = arr2.length
  27. // 合并两个子数组
  28. while(i < len1 && j < len2) {
  29. if(arr1[i] < arr2[j]) {
  30. res.push(arr1[i])
  31. i++
  32. } else {
  33. res.push(arr2[j])
  34. j++
  35. }
  36. }
  37. // 若其中一个子数组首先被合并完全,则直接拼接另一个子数组的剩余部分
  38. if(i<len1) {
  39. return res.concat(arr1.slice(i))
  40. } else {
  41. return res.concat(arr2.slice(j))
  42. }
  43. }

快排

快速排序在基本思想上和归并排序是一致的,仍然坚持“分而治之”的原则不动摇。区别在于,快速排序并不会把真的数组分割开来再合并到一个新数组中去,而是直接在原有的数组内部进行排序。

思路分析

快速排序会将原始的数组筛选成较小和较大的两个子数组,然后递归地排序两个子数组。
这个描述对初学者来说可能会比较抽象,我们直接通过真实排序的过程来理解它:

时间复杂度

  • 最好时间复杂度 O(nlog(n))
  • 最坏时间复杂度O(n^2)
  • 平均时间复杂度:O(nlog(n))

    编码实现

    ```javascript const quickSort = (arr, left = 0, right = arr.length - 1) => { if (arr.length > 1) { const lineIndex = partition(arr, left, right); if (left < lineIndex - 1) {
    1. quickSort(arr, left, lineIndex - 1);
    } if (right > lineIndex) {
    quickSort(arr, lineIndex, right);
    
    } } return arr; };

const partition = (arr, left, right) => { const pivotValue = arr[Math.floor(left + (right - left) / 2)]; let i = left; let j = right; while (i <= j) { while (arr[i] <= pivotValue) { i++; } while (arr[j] >= pivotValue) { j—; } if (i <= j) { [nums[i], nums[j]] = [nums[j], nums[i]]; i++; j—; } } return i; }; ```