排序算法
如何找出两个数中较小的一个
必备知识
- 数据结构
- 用数组
[a,b]表示两个数字 - JS中没有二元组,用数组代替
- 用数组
- 编程知识:
A?B:C表达式
minOf2 的实现
|
|
let minOf2 = ([a,b]) => a<b?a:b的写法叫析构赋值,先把结构拆开,依次赋值
调用
|
|
算法就是把你解决问题的思路用代码表示出来
现成的API
JS内置Math.min
|
|
关于Math
- 看起来
Math像Object一样是构造函数,但不是,没有函数的共用属性 - 实际上
Math只是一个普通的内置对象 - 这是目前(2020)唯一的特例:首字母大写是构造函数
minOf3 的实现
把两个数升级为三个数之间的比较,从三个数找出最小的那个
|
|
推理
minOf4
|
|
- minOfN -> minOf(N-1) -> … -> minOf2([b,c])
- 推广:任意长度数组求最小值,都可以通过
minOf2,最终实现
min 的实现
找出数组中最小值
|
|
- 求最小值,最终还是两两(数组)元素之间比较大小
- 二元法分拆:取出第一个元素;去掉第一个元素后的新数组
arr.slice(1) - 对新数组再次进行二元法分拆
- 最终只剩两个元素,返回较小值,和最近一次分拆的元素再次比较
- 直至和原始数组的第一个元素比较,最终返回的值即为数组的最小元素
[numbers[0], getMin(numbers.slice(1))]- 去掉数组第一项
numbers.slice(1)后,并经过比较大小的数比较 - 代码会死循环,不停调用自己,需要添加一个终止条件
|
|
- 用代入法理解一遍
getMin([2, 4, 3, 1])
getMin([2, getMin([4, 3, 1])])
getMin([2, getMin([4, getMin([3, 1]))]])
getMin([2, getMin([4, Math.min.apply(null,[3, 1]))]])
getMin([2, getMin([4, 1])])–>getMin([2, Math.min.apply(null, [4, 1])])
getMin([2, 1])–>Math,min.apply(null, [2, 1])
1
用循环遍历实现getMin
无需考虑终止条件,逐项比较
|
|
重写minIndex
永远有两种写法:「递归」和「循环」
- 目前的
minIndex繁琐
|
|
所有递归都可以改成循环
递归
特点
- 函数不停调用自己,每次调用的参数略有不同,可能是上一次的返回值
- 当满足条件时,则实现一个简单的调用
- 确定终止条件
- 递进到终止条件,代入值,回归,
- 最终算出结果
理解
- 用代入法理解递归
- 用调用栈理解递归(压栈 弹栈)
难度升级:将正整数数组从小到大排序
实现 sort 排序
思路
- 用循环实现:容易理解,但代码复杂
- 用递归实现:代入理解,代码简洁
递归思路
- 选择排序
小试牛刀
将长度为2的数组排序
|
|
- 这里
sort2为终止条件 即递归基准
将长度为3的数组排序
|
|
- 发现
???拿不到具体的最小的值,即无法将最小值从数值从数组里排除掉 - 必须知道最小值元素的下标,根据下标删掉最小元素
let index = minIndex(numbers)从
numbers里删掉最小的元素numbers.splice(index,1),改变原数组
let min = numbers[index]改进代码
[min].concat(numbers)用求最小值的方法,先把三个数中最小的放到最前面的位置
然后将后面两个数用两数排序的方法排列
|
|
将长度为4的数组排序
|
|
推广
每次选择一个最小的数放最前面,连接剩余的元素组成的新数组
然后对后面的数做同样的事情
直到剩下最后两个互相比较排序
回归
|
|
- 用代入法理解一遍(SICP)
mySort([12,5,8,7,9])
= [5] + mySort([12,8,7,9])
= [5] + ([7] + mySort([12,8,9]))
= [5] + ([7] + [8] + mySort([12,9]))
= [5] + ([7] + [8] + [9,12])
= [5] + ([7] + [8,9,12])
= [5] + [7,8,9,12]
= [5,7,8,9,12]
如何调试代码
|
|
选择排序的循环写法
选择排序的循环思路
用循环改写递归的
sort思路不变
每次找到最小的数,放前面
然后对后面的书做同样的事情(递归)然后 i++(循环)
|
|
分析
- 如何知道
i < ???处应该怎么写 - 提前写好
minIndex省略实现,能有效简化问题 - 用
swap占位能有效简化问题
实现swap 互换传值/址
|
|
容易错误地实现
swap
|
|
会发现,
numbers[1]和numbers[2]的值原封不动因为
a b是简单类型,传参的时候会复制值而
numbers是对象,传参是复制地址传值 V.S. 传址
- 用
ES6的析构赋值
|
|
思路改进:循环选择排序
分析
- 如何知道
i < ???处应该怎么写
|
|
- 暴力分析,代入法:假设
numbers的长度n = 4
| n | i | index可能的结果 |
index预期的结果 |
|---|---|---|---|
| 4 | 0 | swap(numbers, index, 0),index可能为0/1/2/3 |
1/2/3 |
| 4 | 1 | swap(numbers, index, 1),index可能为0/1/2/3 |
2/3 |
| 4 | 2 | swap(numbers, index, 2),index可能为0/1/2/3 |
3 |
| 4 | 3 | swap(numbers, index, 3),index可能为0/1/2/3 |
在上一次已经取完,不存在 |
- 发现问题:
每次
index都在数组的所有元素中取有可能找到已经排好的第一个最小值,不再调换剩余项的位置
即
minIndex查找index范围有问题
let index = minIndex(numbers)错误,不该在原数组里找- 解决问题:
i的最大值应为numbers.length - 1即
???里应该写numbers.length - 1每次循环对比上一次,应该去掉上次找到的最小值
- 如果上次循环已经找到一个最小的值,那么下一次找最小值时候,就必须忽略(排除)上次那个找到的最小值
- 把本次找到的最小值提前
swap() - 截取第
i次之后的新数组numbers.slice(i),不改变原数组 - 得到截取后最小值的下标
minIndex( numbers.slice(i) ) - 将最小值序号变为
minIndex( numbers.slice(i) ) + i - 即
let index = minIndex( numbers.slice(i) ) + i,i为遍历的下标范围小于numbers.length - 1
| i | 要找几个 | 忽略几个 |
|---|---|---|
| 0 | 4 | 0 |
| 1 | 3 | 1 |
| 2 | 2 | 2 |
- 每次找
minIndex(numbers.slice(i))里面最小的
为什么要
+ i
- 如果不加,那么
index总是从0数起,每次都会少掉i个 minIndex(numbers.slice(i))切掉下标的要加回来
重新分析代码
|
|
- 代入法:假设
numbers的长度n = 4
| n | i | index可能的结果 |
|---|---|---|
| 4 | 0 | swap(numbers, index, 0),index可能为 1/2/3 |
| 4 | 1 | swap(numbers, index, 1),index可能为 2/3 |
| 4 | 2 | swap(numbers, index, 2),index可能为 3 |
| 4 | 3 | swap(numbers, index, 3),numbers.slice(3)是[i],i = 3不行,所以i < n - 1 |
i < ???应为numbers.length - 1
画图
[27,44,35,16]
Object.keys([27,44,35,16]) -> Array(4) [ "0", "1", "2", "3" ]最小值
16,最小值下标"3"
i = 0,最小值下标"3",下标 i 和"3"的值 对换位置swap()新数组
[16,44,35,27],第1项固定
i = 1,找后面3个元素中最小值 下标"3",下标 i 和"3"的值 对换位置swap()新数组
[16,27,35,44],前2项固定
i = 2,找后面2个元素中最小值 下标"2",下标 i 和"2"的值 不用对换比较完了,不必再进行循环
i = 3
改进代码
|
|
最终代码
|
|
循环选择排序小结
- 所有递归都能改成循环
- 循环的时候有很多细节
- 难以想清楚
- 代入法手动列出表格,找规律
- 难以确定边界条件,即终止条件
- 未实现处理长度为
0和1的数组
- 如何
Debug- 看控制台
- 打
log - 打
log同时添加标记
搞定选择排序,每次选择最小/最大的,选完就排完
选择排序算法总结
- 求最小值
- 2个数
- 3个数
- N个数
- 排序
- 2个数
- 3个数
- 4个数
- N个数
- 用到的概念
- 数组(数据结构)
slice、concat、splice、reverse - 递归:代入法理解;归纳法;确定终止条件分支
- 循环写法
- 数组(数据结构)
算法心得
- 擦起键盘肝->运行调试->循环->优化
- 耐心
- 查改删 筛重排
手写题
|
|
算法中的递归
1.递归的概念 2.递归三要素:
- 拆解寻找子问题
- 最小子问题(基本问题)
- 递归终止退出条件
3.高频题目精讲
1. 递归的概念
2. 递归三要素
3. 高频题目精讲
参考文章
相关文章
- 无
- 作者: Joel
- 文章链接:
- 版权声明
- 非自由转载-非商用-非衍生-保持署名
- 河 掘 思 知 简