数组常见的几种排序算法

秋招面试被问到数组排序算法,我只答的出快排,然后就没有然后了. 果然还是得吃点苦头才有动力学习,这几个排序见过无数次,但是一直看不进去 都是基于自己的理解写的笔记内容,希望对不懂的小伙伴有些帮助,欢迎指点问题

冒泡排序

数组分成两部分,每次在未排序的元素中通过一一比较并交换相邻的位置,将一个最大的或者最小的值移动到指定位置,重复进行这个步骤得到完整排序

平均时间复杂度为 O(n^2),最好是O(n)(已经排好序的情况),最差是O(n^2)(完全逆序)

稳定的排序算法 (稳定:存在相等元素时,它们之间的相对顺序不变)

缺点是它的交换次数较多,每一轮都可能进行多次交换。

js 复制代码
function bubbleSort(arr) {
  let len = arr.length
  for(let i = 0; i < len - 1; i++){
    for(let j = 0; j < len - i - 1; j++){
      if(arr[j] > arr[j + 1]){
        let temp = arr[j]
        arr[j] = arr[j + 1]
        arr[j + 1] = temp
      }
    }
  }
  return arr
}
// 示例
let arr = [5, 3, 8, 4, 2];
console.log(bubbleSort(arr)); // 输出 [2, 3, 4, 5, 8]

选择排序

数组分成两部分,每次在未排序的元素中得到一个最大值或者最小值,通过交换位置将这个放到已排序队列的末尾

平均时间复杂度为 O(n^2),最好是O(n)(已经排好序的情况),最差是O(n^2)(完全逆序)

不稳定

缺点是它的比较次数是固定的,无论数组是否已经部分有序,都需要进行 n(n-1)/2 次比较。

js 复制代码
function selectionSort(arr){
  let len = arr.length
​
  for(let i = 0; i < len - 1; i++){
    let min = i
    for(let j = i + 1; j < len; j++){
      if(arr[j] < arr[min]){
        min = j
      }
    }
    //交换位置
    let temp = arr[min]
    arr[min] = arr[i]
    arr[i] = temp
  }
  return arr
}
// 示例
var arr = [5, 3, 8, 4, 2];
console.log(selectionSort(arr)); // 输出 [2, 3, 4, 5, 8]

插入排序

将数组分成两部分,在未排序的元素中选择索引值最小的那一个,将它和已排序部分一一比较,插入到合适的位置

平均时间复杂度为 O(n^2),最好情况下(数组已经有序)的时间复杂度为 O(n),最坏情况下(数组完全逆序)的时间复杂度也为 O(n^2)。

稳定的排序算法

js 复制代码
function insertionSort(arr) {
  let len = arr.length
  for(let i = 1; i < len; i++){
    let cur = arr[i]
    let j = i - 1
    while(j >= 0 && arr[j] > cur){
      arr[j + 1] = arr[j]
      j--
    }
    arr[j + 1] = cur
  }
  return arr
}
var arr = [5, 3, 8, 2, 1];
console.log(insertionSort(arr)); // 输出 [1, 2, 3, 5, 8]

快排

选定一个基准值,将数组分为两部分,大于基准值和小于基准值,然后对这两部分的数组递归此步骤

平均时间复杂度为 O(nlogn),最坏情况下(数组已经有序或完全逆序)的时间复杂度为 O(n^2)。

不稳定的排序算法

js 复制代码
function quickSort (arr) {
    if(arr.length <= 1) {
        return arr
    }
    let pivotIndex = Math.floor(arr.length / 2);
    let pivot = arr.splice(pivotIndex, 1)[0];
    let left = []
    let right = []
​
    for(let i = 0; i < arr.length; i++){
        if(arr[i] < pivot) {
            left.push(arr[i])
        }
        else{
            right.push(arr[i])
        }
    }
    return quickSort(left).concat([pivot],quickSort(right))
}
console.log(quickSort([23,43,53,2,3,5332,34]));

归并排序

将数组分成两部分,每部分做到部分内排序,然后在合并两个数组,递归此步骤

时间复杂度始终为 O(nlogn),无论是在最好、最坏还是平均情况下。

稳定的排序算法

缺点是需要额外的空间来存储临时数组,因此空间复杂度为 O(n)。

js 复制代码
/*
 * @Author: xiangyue_li
 * @Date: 2023-07-16 13:38:03
 * @LastEditors: xiangyue_li
 */
function mergeSort(arr) {
  if (arr.length <= 1) {
    return arr;
  }
​
  //将数组分成两个子数组
  let mid = Math.floor(arr.length / 2);
  let left = arr.slice(0, mid);
  let right = arr.slice(mid);
​
  //递归调用归并排序算法
  return merge(mergeSort(left),mergeSort(right));
}
​
function merge(left,right){
  let result = []
  let i = 0,j = 0
​
  //比较两个子数组,元素较小的放入新数组
  while(i < left.length && j < right.length) {
    if(left[i] <= right[j]){
      result.push(left[i])
      i++
    }
    else{
      result.push(right[j])
      j++
    }
  }
​
  //将剩余元素放入新的数组中
  while(i < left.length){
    result.push(left[i])
    i++
  }
​
  while(j < right.length){
    result.push(right[j])
    j++
  }
  return result
}
var arr = [5, 3, 8, 2, 1];
console.log(mergeSort(arr)); // 输出 [1, 2, 3, 5, 8]

堆排序

构建大顶堆(大顶堆就是根节点是所有节点中最大的那一个)

构建好大顶堆之后,遍历数组,每次将索引为0(根节点)的元素与未排序数组的最后一位交换

然后堆的节点数减一,重新调整大顶堆,获得剩余数组中的最大值,重复上述步骤

时间复杂度为 O(nlogn),并且具有原地排序的特点,即不需要额外的空间来存储临时数组

不稳定排序算法

js 复制代码
function heapSort(arr) {
  // 构建最大堆
  buildHeap(arr);
  console.log(arr)
  // 排序
  for (var i = arr.length - 1; i > 0; i--) {
    swap(arr, 0, i); // 将堆顶元素与堆的最后一个元素交换,大顶堆中根元素是最大的,这里将最大的元素换到数组末尾
    console.log(arr)
    heapify(arr, 0, i); // 调整堆使其满足堆的性质,注意这里len = i,就是说每次将最大元素交换到末尾之后,这个元素不再参与堆的调整
  }
​
  return arr;
}
​
function buildHeap(arr) {
  var len = arr.length;
  var lastNonLeafIndex = Math.floor(len / 2) - 1; //拿到最后一个非叶子节点的索引值,完全二叉树中,叶子结点是所有结点的一半(向下取整)
​
  for (var i = lastNonLeafIndex; i >= 0; i--) {
    heapify(arr, i, len);
  }
}
​
function heapify(arr, i, len) {
  var largest = i; // 假设当前节点是最大的
  var left = 2 * i + 1; // 左子节点索引
  var right = 2 * i + 2; // 右子节点索引
​
  // 如果左子节点存在且大于当前节点,则更新最大值索引
  if (left < len && arr[left] > arr[largest]) {
    largest = left;
  }
​
  // 如果右子节点存在且大于当前节点,则更新最大值索引
  if (right < len && arr[right] > arr[largest]) {
    largest = right;
  }
​
  // 如果最大值索引不等于当前节点索引,则交换位置,并继续向下调整
  if (largest !== i) {
    swap(arr, i, largest);
    heapify(arr, largest, len);
  }
}
​
function swap(arr, i, j) {
  var temp = arr[i];
  arr[i] = arr[j];
  arr[j] = temp;
}
​
var arr = [5, 3, 8, 2, 1];
console.log(heapSort(arr)); // 输出 [1, 2, 3, 5, 8]
相关推荐
小镇程序员41 分钟前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
莫叫石榴姐1 小时前
数据科学与SQL:组距分组分析 | 区间分布问题
大数据·人工智能·sql·深度学习·算法·机器学习·数据挖掘
疯狂的沙粒1 小时前
对 TypeScript 中函数如何更好的理解及使用?与 JavaScript 函数有哪些区别?
前端·javascript·typescript
瑞雨溪1 小时前
AJAX的基本使用
前端·javascript·ajax
力透键背1 小时前
display: none和visibility: hidden的区别
开发语言·前端·javascript
程楠楠&M1 小时前
node.js第三方Express 框架
前端·javascript·node.js·express
weiabc1 小时前
学习electron
javascript·学习·electron
想自律的露西西★2 小时前
用el-scrollbar实现滚动条,拖动滚动条可以滚动,但是通过鼠标滑轮却无效
前端·javascript·css·vue.js·elementui·前端框架·html5
茶猫_2 小时前
力扣面试题 - 25 二进制数转字符串
c语言·算法·leetcode·职场和发展
白墨阳2 小时前
vue3:瀑布流
前端·javascript·vue.js