4种基础的排序算法

124 阅读3分钟

选择排序

  1. 寻找数组中最小数
  2. 交换操作:最小数总是在第一位
  3. 依此遍历数组
  4. 以下是代码实现
//循环写法
let sort = (numbers) => {
//确定了前面数的位置,那么最后一个数的位置自然而然就确定了,所以不需要遍历所有数
  for(let i=0; i< numbers.length -1; i++){          
    console.log(`----`) //这个log很精髓
    console.log(`i: ${i}`)
     //加i是执行完寻找最小值的函数获取索引的基础上再加上在原数组的索引值
    let index = minIndex(numbers.slice(i))+ i   //这是一个坑 加i绝对不能少 
    console.log(`index: ${index}`)
    console.log(`min: ${numbers[index]}`)
    if(index!==i){                           //也要判断该数是不是最小值,如果是就不需要换位
      swap(numbers, index, i)
      console.log(`swap ${index}: ${i}`)
      console.log(numbers)
    }
}
  return numbers
}

//寻找数组中的最小值,返回最小值的索引
let minIndex = (numbers) => {
  let index = 0
  for(let i=1; i<numbers.length; i++){
    if(numbers[i] < numbers[index]){
      index = i
    }
  }
  return index
}

//交换值,把数组中最小的数放在第一位
let swap = (array, i, j) => {
  let temp = array[i]
  array[i] = array[j]
  array[j] = temp
}
//递归写法
let min = (numbers) =>{
    if(numbers.length > 2){
    return min([numbers[0], min(numbers.slice(1))])
    }else{
    return Math.min.apply(null, numbers)
    }
}

let minIndex = (numbers) => numbers.indexOf(min(numbers))

let sort = (numbers) => {
    if(numbers.length > 2){
    let index = minIndex(numbers)
    let min = numbers[index]
    numbers.splice(index, 1)
    return [min].concat(sort(numbers))
    }else{
    return numbers[0]>numbers[1] ? numbers.reverse() : numbers
    }
}

快速排序

  1. 将数组中间的数定为一个基准数
  2. 小于基准数的数放在左边组成一个数组,大于基准数的数放在右边组成一个数组
  3. 依次执行1、2点
  4. 当数组的长度小于2时,即无法分组直接返回该数组
  5. 先看执行图

6. 以下是代码实现

let quickShort = (numbers)=>{
    if(numbers.length<2){
    return numbers
    }
    let index = Math.floor(numbers.length/2)
    let middel = numbers.splice(index,1)[0]     //splice返回的是一个只有一个数的数组
                                                //我们需要使用这个中间值,因此[0]
    console.log(`midedl:${middel}`)             //打log相当重要
    let left = []
    let right=[]
    for(let i=0;i<numbers.length;i++){
        if(numbers[i]>midel){
             right.push(numbers[i])
          }else{ 
            left.push(numbers[i])
           }
        }
  console.log(`left:${left}`)
  console.log(`right:${right}`)
  //用concat连接左边数组,基准数和右边数组
  return quickShort(left).concat(midel, quickShort(right))            
}
let numbers = [1,5,6,14,2,12,7,10,0,22,11]
quickShort(numbers)

归并排序(先拆再合)

  1. 不存在基准数,将数组对半分为左右两个数组
  2. 继续执行1操作,直到数组里面只存放了一个数,其实是一个入口
  3. 两两数组进行比较
  4. 将排好序的数组合并起来
  5. 先看执行图

6. 下面是代码实现

let mergeShort = (numbers)=>{
  if(numbers.length<2){     //判断数组长度,小于2直接返回该数组
    return numbers
  }
  //拆的目的是最终每一个数组只有1个数时两两比较他们的大小,相当于提供一个入口
  let left = numbers.slice(0, Math.floor(numbers.length/2))
  let right = numbers.slice( Math.floor(numbers.length/2),numbers.length)
  console.log(`left:${left}`)
  console.log(`right:${right}`)
  return merge(mergeShort(left),mergeShort(right))  
}
//真正的操作:比较大小进行组合
let merge = (a,b)=>{
  if(a.length === 0){   //当数组 a 的长度为 0 时直接返回 b 数组
    return b
  }
  if (b.length === 0){  //当数组 b 的长度为0时直接返回 a 数组
    return a
  }
  //当 a 数组的第一个数比 b 数组的第一个数大时 返回一个数组 数组的第一个数为b数组的第一个数
  return a[0]>b[0] ? [b[0]].concat(merge(a,b.splice(1))) : [a[0]].concat(merge(b,a.splice(1)))
}
let numbers = [2,9,1,7,4,11,8,0,13,6] 
mergeShort(numbers)

计数排序

  1. 遍历数组中的每一项放入哈希表,并且标记最大值
  2. 遍历哈希表(0~max),存在数组中的值就push
  3. 先看执行图

4. 以下是代码实现

let countSort = numbers=>{
  let max = 0
  let hashTable = {}
  let result = []
  for(let i=0;i<numbers.length;i++){
    if(numbers[i]>max){     //取最大值
      max = numbers[i]
    }
    if(!(numbers[i] in hashTable)){     //如果number[i]在以往操作中不存在哈希表里
      hashTable[numbers[i]] = 1         //value = 1
     }else{                             //存在的就使 value += 1
      hashTable[numbers[i]] +=1 
     }
  }
  for(let j=0;j<=max;j++){              //遍历哈希表
    if(j in hashTable){
      for(let i=0;i<hashTable[j];i++)       //如果有数存了两次也都要push出来
      result.push(j)
    }
  }
  return result
}
let numbers = [9,1,7,4,11,2,8,13,6,15,3]
countSort(numbers)

总结

  • 所有的递归都可改成循环
  • 算法难以理解的时候借助画执行图就容易多了
  • 使用伪代码也是非常好的方法,根据代码代入数据理解起来也是不难的
  • 当算法用代码实现时发现错误很多,log调试法会给你很大的帮助
  • 用循环写代码时要处理的细节比较多,可能你在理解算法的时候没有注意到,像要判断边界条件这些细节都要考虑到