算法系列--排序

根据时间复杂度分类,常见的排序算法有:

  • O(n^2): 冒泡、插入、选择;
  • O(nlogn): 快排、归并;
  • O(n): 桶、计数;

冒泡排序

  • 时间复杂度: 平均O(n^2)
  • 稳定性: 稳定
// 普通版
function bubbleSort(arr) {
  let n = arr.length
  for (let i = 0; i < n; i++) {
    for (let j = 0; j < n - i - 1; j++) {
      if (arr[j] > arr[j+1]) {
        let temp = arr[j]
        arr[j] = arr[j+1]
        arr[j+1] = temp
      }
    }
  }
  return arr
}

// 优化版
function bubbleSort(arr) {
  const n = arr.length
  if (n <= 1) return
  for (const i = 0; i < n; i++) {
    // 是否交换标志
    let change = false
    for (const j = 0; j < n-i-1; j++) {
      if (arr[j] > arr[j+1]) {
        [arr[j], arr[j+1]] = [arr[j+1], arr[j]]\
        change = true
      }
    }
    // 若没有修改,说明排序已经完成
    if (!change) break
  }
}

插入排序

  • 时间复杂度: 平均O(n^2)
  • 稳定性: 稳定
function insertSort(arr) {
  let n = arr.length
  if (n <= 1) return
  for (let i = 1; i < n; i++) {
    let temp = arr[i]
    for (let j = i - 1; j >= 0; j--) {
      // 大于插入元素,则后移
      if (arr[j] > temp) {
        arr[j+1] = arr[j]
      } else { // 小于,则插入
        arr[j+1] = temp
        break
      }
    }
  }
}

选择排序

  • 时间复杂度: 平均O(n^2)
  • 稳定性: 稳定
function selectSort(arr) {
  let n = arr.length
  if (n <= 1) return
  for (let i = 0; i < n; i++) {
  	let min = i
    for (let j = i + 1; j < n; j++) {
      if (arr[j] < arr[min]) {
        min = j
      }
    }
    // 找到最小值,交换
    [arr[min], arr[i]] = [arr[i], arr[min]]
  }
}

快速排序

  • 时间复杂度: 平均O(nlogn)
  • 稳定性: 不稳定
// 基础版
function quickSort(arr) {
  let n = arr.length
  if (n <= 1) return arr
  const left = [], right =[]
  let temp = arr[0]
  for (let i = 1; i < n; i++) {
    if (arr[i] < temp) {
      left.push(arr[i])
    } else {
      right.push(arr[i])
    }
  }
  return [...quickSort(left), temp, ...quickSort(right)]
}

// 原地排序版
function quickSort(arr, start, end) {
  if (start >= end) return
  // 获取分区下标
	const p = getPartition(arr, start, end)
  quickSort(arr, start, p-1)
  quickSort(arr, p+1, end)
}

// 获取分区下标
function getPartition(arr, start, end) {
  const base = start  // 第一个作为基数
  let p = start + 1  // 分区下标
  for (let i = p; i <= end; i++) {
    if (arr[i] < arr[base]) {
      // 小于基数值,交换位置
      [arr[i], arr[p]] = [arr[p], arr[i]]
      p++
    }
  }
  // 交换基数与分区
  p = p - 1
  [arr[p], arr[base]] = [arr[base], arr[p]]
  return p
}

// 获取分区下标
function getPartition2(arr, start, end) {
  const base = end  // 最后一个作为基数
  let p = start  // 分区下标
  for (let i = p; i < end; i++) {
    if (arr[i] < arr[base]) {
      // 小于基数值,交换位置
      [arr[i], arr[p]] = [arr[p], arr[i]]
      p++
    }
  }
  // 交换基数与分区
  [arr[p], arr[base]] = [arr[base], arr[p]]
  return p
}

归并排序

  • 时间复杂度: 平均O(nlogn)
  • 稳定性: 稳定
function mergeSort(arr, start, end) {
  if (start >= end) return
  // 分
  const mid = Math.floor((start + end) / 2)
  mergeSort(arr, start, mid)
  mergeSort(arr, mid + 1, end)  
  // 合
  const left = arr.slice(start, mid + 1)
  const right = arr.slice(mid + 1, end + 1)
  const mergeArr = merge(left, right)
  // 替换原数组
  for (let i = 0, n = mergeArr.length; i < n; i++) {
    arr[start + i] = mergeArr[i]
  }
  
  // 合并两个有序数组
  function merge(arr1, arr2) {
    const temp = []
		while (arr1.length && arr2.length) {
      if (arr1[0] < arr2[0]) {
        temp.push(arr1.shift())
      } else {
        temp.push(arr2.shift())
      }
    }
    temp = [...temp, ...arr1, ...arr2]
    return temp
  }
}

桶排序

  • 时间复杂度: 平均O(n)
  • 稳定性: 看桶内排序算法而定
function bucketSort(arr) {
  let newArr = []
  let min = Math.min(...arr)
  let max = Math.max(...arr)
  const size = 2 // 桶大小
  const num = Math.floor((max - min) / 2) + 1 // 桶数量
  const bucketArr = new Array(num) // 二维数组,存放桶
  for (let i = 0; i < num; i++) {
    bucketArr[i] = []
  }
  // 遍历数组,放入桶内
  for (let i = 0, n = arr.length; i < n; i++) {
    const index = Math.floor((arr[i] - min) / size)
    bucketArr[index].push(arr[i])
  }
  // 桶内排序
  for (let i = 0, n = bucketArr.length; i < n; i++)  {
    // 内置排序函数, 也可以用自己写的快速排序
    bucketArr[i].sort((a, b) => { return a - b })
    // 原数组排序
    newArr.push(...bucketArr[i])
  }
  return newArr
}

计数排序

  • 时间复杂度: 平均O(n)
  • 稳定性: 稳定
  • 适用场景:数组的取值范围远小于数组长度; 如50w考生,分数范围0-750;
function countingSort(arr) {
  const max = Math.max(...arr)
  let countArr = new Array(max + 1).fill(0)
  // 统计个数, 有点类似哈希表的味道
  for (let item of arr) {
    if (countArr[item] !== void 0) {
      countArr[item] += 1
    } else {
      countArr[item] = 1
    }
  }
  // 排序, 从小到大填充
  for (let i = 0, k = 0, n = countArr.length; i < n; i++) {
    for (let j = 0; j < countArr[i]; j++) {
      arr[k] = i
      k++
    }
  }
}

排序相关的算法题

(1) 无序数组中,求第k大元素,时间复杂度要求O(n);
分析

  • 由于时间复杂度要求,先排序后取值是行不通的;
  • 快速排序中,获取分区点p,如果k>p, 说明目标元素在右边,否则在左边;
// 无序数组中,第K大元素
function fn (arr, k) {
  let start = 0
  let end = arr.length - 1
  if (k > end) {
    return -1 
  }
  let p = getPartition(arr, start, end)
  while (p !== k) {
    console.log('p', p)
    if (p < k) {
      start = p + 1
    } else {
      end = p - 1
    }
    p = getPartition(arr, start, end)
  }
  return arr[p]

  function getPartition(arr, start, end) {
    let base = end
    let p = start
    for (let i = p; i < end; i++) {
      if (arr[i] < arr[base]) {
        [arr[i], arr[p]] = [arr[p], arr[i]]
        p++
      }
    }
    [arr[p], arr[base]] = [arr[base], arr[p]]
    return p
  }
}

let arr = [5, 6, 7, 2, 1, 4]
let k = 88
let result = fn(arr, k - 1)
console.log('result', result)

(2) 无序数组中,求最小k个数,时间复杂度要求O(n);

// 最小k个数
function fn(arr, k) {
  let start = 0, end = arr.length - 1
  if (k > end) return []
  let p = getPartition(arr, start, end)
  while (p !== k) {
    if (p < k) {
      start = p + 1
    } else {
      end = p - 1
    }
    p = getPartition(arr, start, end)
  }
  return arr.slice(0, k)

  function getPartition(arr, start, end) {
    let base = end
    let p = start
    for (let i = start; i < end; i++) {
      if (arr[i] < arr[base]) {
        [arr[i], arr[p]] = [arr[p], arr[i]]
        p++
      }
    }
    [arr[p], arr[base]] = [arr[base], arr[p]]
    return p
  }
}
let arr = [2,3,1,2,4,5,6,7]
let k = 77
let result = fn(arr, k)
console.log('result', result)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值