排序算法(下)

167 阅读4分钟

优化上个章节的代码

重写minIndex

let minIndex = (numbers) =>{
    let index = 0
    for(let i = 1; i < numbers.length; i++){
        if(numbers[i] < numbers[index]){
            index = i
        }
    }
    return index
}

选择排序

所有的递归都可以改成循环

代码1:确定思路

思路不变,每次找到最小的数放在前面,然后i++

  1. 先忽略 i < ??? 写什么
  2. 提前写好 minindexswap 来占位能有效简化问题
let sort = (numbers) =>{
    for (let i = 0; i < ???; i++){
        // 找到最小的数
        let index = minIndex(numbers)
        // 每次把最小的数放在前面
        swap(numbers,index,i)
    }
}

代码2:实现swap

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

swap的误区

在这里,因为a,b是简单类型,传输参数的时候会复制整个值到新的stack栈内存,所以事实上有两个a, 两个b。 原先的ab不受影响

var numbers = [100, 200]

var swap = (a,b) =>{ 
    let temp =a; 
    console.log(`a:${a},temp:${temp}`); 
    a =b; 
    console.log(`a:${a},b:${b}`); 
    b = temp; 
    console.log(`a:${a},b:${b}, temp:${temp}`) 
}
swap(numbers[0], numbers[1])

// a:100,temp:100
// a:200, b:200
// a:200,b:100, temp:100

minIndex(numbers)的误区

let sort = (numbers) =>{
    for (let i = 0; i < ???; i++){
        // 找到最小的数
        let index = minIndex(numbers)
        // 每次把最小的数放在前面
        swap(numbers,index,i)
    }
}
let index = minIndex(numbers)

在上面的代码中,如果数组的长度为4, 那么i的值分别为0,1,2,3

minIndex的查找范围有问题,因为上次循环已经找到了第一个最小的数字,所以下次循环查找中可以把它给剔除,不然就会在不同的i里反复替换最小的数字

let index = minIndex(numbers.slice(i) + i)

在这里,numbers.slice(i)就会把前面已经查找过的i剔除,在剩下的数字里找到最小的值。+i是寻找实际数组中被替换数组的位置,不然index总是从新一轮的0数起。

使用console.log()调试

将下面的代码写入循环里,可以显示每轮循环的开始

console.log(`---`)

最终代码

let swap =(array,i,j) =>{
    let temp = array[i]
    array[i] = array[j]
    array[j] = temp
}
let minIndex = (numbers) =>{
    let index = 0
    for(let i = 1; i < numbers.length; i++){
        if(numbers[i] < numbers[index]){
            index = i
        }
    }
    return index
}

let sort = (numbers) =>{
    for(let i = 0; i < numbers.length-1; i++){
        let index = minIndex(numbers.slice(i)) + i
        if(index !== i ){
            swap(numbers, index, i)
        }
    }
    return numbers
}
sort([1,199,27,93,124,4903,4])

总结

  • 所有的递归都能改成循环
  • 循环的时候有很多细节,需要动手才能找出规律,边界条件很难确定,也没有处理长度为01的数组
  • 需要学会console.log调试,不断的debug

quickSort快速排序

思路

以某个数为基准,比它小的去前面,比它大的去后面,重复操作,就可以得到完整的排序。

代码

var quickSort = function(arr) {
if (arr.length <= 1) { return arr; }
  var pivotIndex = Math.floor(arr.length / 2);
  var pivot = arr.splice(pivotIndex, 1)[0];
  var left = [];
  var right = [];
  for (var 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));

};

mergeSort合并排序

思路

把一个数组,分为两部分,左右两边分别拍好顺序,再进行合并,得到完整的数组

代码

let mergeSort = arr =>{
  let k = arr.length
  if(k===1){return arr}
  let left = arr.slice(0, Math.floor(k/2))
  let right = arr.slice(Math.floor(k/2))
  return merge(mergeSort(left), mergeSort(right))
}

let merge = (a, b) => {
  if(a.length === 0) return b
  if(b.length === 0) return a
  return a[0] > b[0] ?
     [b[0]].concat(merge(a, b.slice(1))) :
     [a[0]].concat(merge(a.slice(1), b))
}

countSort计数排序

思路

用一个哈希表最为记录,发现数字N,就记作N:1, 如果再次发现N就加1,最后把哈希表的key全部打出来,假设N:M,那么N就需要打印M

代码

let countSort = arr =>{
  let hashTable = {}, max = 0, result = []
  for(let i=0; i<arr.length; i++){ // 遍历数组
    if(!(arr[i] in hashTable)){ 
      hashTable[arr[i]] = 1
    }else{
      hashTable[arr[i]] += 1
    }
    if(arr[i] > max) {max = arr[i]}
  }
  for(let j=0; j<=max; j++){ // 遍历哈希表
    if(j in hashTable){
      for(let i = 0; i<hashTable[j]; i++){
        result.push(j)
      }
    }
  }
  return result
}

特点

数据结构不同,使用了额外的hashTable哈希表, 只遍历了一次数组和一次哈希表,这叫做用空间换时间

花费时间的对比

  • 选择排序O(n^2)
  • 快速排序O(n * log2 n)
  • 归并排序O(n * log2 n)
  • 计数排序O(n + max)

更多排序算法

快速排序(Quicksort)的Javascript实现

冒泡排序

插入排序

希尔排序

基数排序