接上 继续选择排序
学算法为什么难
- 说起来容易做起来难
- 运行,发现写错,用log调试
- 再运行,再写错,再调试
- 运行,没有错,继续想更好的写法
- 循环
- 拿起键盘干,动手最重要
- 就算你看了正确答案,你自己写也会错
- 没有人可以帮你完成这个过程
minIndex
- 你永远都有两种写法
- 递归和循环
- 当前的minIndex
let minIndex = (numbers)=>numbers.indexOf(min(numbers))
let min = (numbers)=>{
return min(
[numbers[0],min(numbers.slice(1))]
)
}
- 缺点:繁琐=>重写
minIndex重写
let minIndex = (numbers)=>{
let index = 0
for(let i=1;i<numbers.length;i++){
if(numbers[i]<numbers[index]){
index = i
}
}
return index
}
- “渣男算法”:遇到小的就换女朋友
- 一目了然,一听就会,一写就错
- 写错记得测试几次
所有的递归都可以改写成循环
改写sort
- 思路不变:每次找到最小的数放前面,然后i++
//循环
let sort = (numbers)=>{
for(let i=0;i<???;i++){
let index = minIndex(numbers)
//index是当前最小数的下标
//index对应的数应该放到i处
swap(numbers,index,i)//把最小的和当前的交换位置 swap还没实现,只是先占位
//index、i都是index的意思,建议i改名
}
}
- 怎么知道i<???处应该写什么
- 提前写好minIndex能有效简化问题
- 用swap占位能有效简化问题
实现swap
let swap = (array,i,j)=>{
let temp = array[i]
array[i] =array[j]
array[j] = temp
}
swap(numbers,1,2)
- 怎么调换气球位置
- 第三个人介入 代码中的temp
错误地实现swap
let swap = (a,b)=>{
let temp = a
a = b
b = temp
}
swap(numbers[1],numbers[2])
- 你会发现numbers[1]和numbers[2]的值原封不动
- 因为a、b是简单类型,传参的时候会复制值(在stack里面放着)
- 而上一个正确代码中的numbers是array,是对象,传参复制地址(在heap里放着)
- 传值vs传址
分析
- 怎么知道i<???处应该写什么
- 暴力分析
let sort = (numbers)=>{
for(let i=0;i<???;i++){
let index = minIndex(numbers)
swap(numbers,index,i)
}
}
- 假设numbers的长度为4
- bug:minIndex范围有问题,每次都在4个数字里找最小
let index = minIndex(numbers)有问题- 如果上次循环已经找到第一个最小的数字,那么之后找最小数字的时候可以忽略第一个
-let index = minIndex(numbers.slice(i))+1
- 为什么要+i
- 如果不加,那么index总是从0算
let sort = (numbers) =>{
for(let i=0;i< numbers.length -1;i++){
console.log(`----`)//这个log很精髓,下划线防眼花
console.log(`i:${i}`)
let index = minIndex(numbers.slice(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 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
}
- 最终代码删掉log
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
}
总结
- 所有的递归都能改成循环
- 循环的时候有很多细节很难想清楚
- 要动手列出表格找规律
- 尤其是边界条件很难确定,比如是到length还是length-1
- 我们没有处理长度为0和1的数组
- 如果debug:学会看控制台;学会打log;打log时注意加标记
快速排序
递归思路
- 想象你的体育委员
- 面对的同学为[12,3,7,21,5,9,4,6]
- “以某某为基准,小的去前面,大的去后面”
- 你只需要重复说这句话就能排序
- 画图说明
- 代码:阮一峰版本www.ruanyifeng.com/blog/2011/0…
let 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))
} //pivot轴
归并排序
递归思路
- 不以某某为基准
- 你面对的同学为[12,3,7,21,5,9,4,6]
- 左边一半排好序,右边一般排好序
- 然后把左右两边合并(merge)起来
- 画图说明
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)
}
计数排序
引入hash哈希表:按AAAA2222...KKKK顺序排扑克牌
思路
- 用一个哈希表做记录 {A:0;2:0;3:0;...;k:0}
- 发现数字N就记N:1,如果再次发现N就加1
- 最后把哈希表的key全部打出来,假设N:m,那么N需要打印m次
- 画图演示:整个过程就是记录max和key:vaule。从0遍历到max,有的话就打印1次,有两个就打印两次
let countSort = arr =>{
let hashTable = {},max = 0,result = []
for(let i=0;i<arr.length;i++){//遍历数组
if(!(arr[i] in hashTable)){ //收集第i项的值
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){
result.push(j)
}
}
return result
} //目前代码有bug:如果j出现两次的情况怎么办
for(let j=0;j<=max;j++){//遍历哈希表
if(j in hashTable){
for(let i=0;i<hashTable[j];i++){ //j出现几次就push几次
result.push(j)
}
}
}
计数排序的特点
1.数据结构不同
- 使用了额外的hashTable
- 只遍历数组一遍(不过还要遍历一次hashTable)
- 这叫做“用空间换时间”
2.时间复杂度对比
- 选择排序O(n^2)
- 快速排序O(n log2 n)
- 归并排序O(n log2 n)
- 计数排序O(n + max)
算法总结
- 心法:战略上藐视敌人,战术上重视敌人
- 特点:思路简单;细节多;多花表,多画图,多log;如果实在不想陷入Js的细节可以用伪代码
还有哪些排序算法
- 冒泡排序visualgo.net/zh/sorting
- 插入排序visualgo.net/zh/sorting 点击INS(起扑克牌就是用这种方法)
- 希尔排序zhuanlan.zhihu.com/p/87781731 参考图解
- 基数排序visualgo.net/zh/sorting 点击RAD(适合多位数排序)