一、编程的概念
编程语言不难,难的是抽象概念
-
初学看不懂是因为没有学习抽象知识
-
把变成编程语言抽象了。几种编程语言根本就没有区别
-
首先理解这些语言的不变之处
所有编程都是在写逻辑
三段论逻辑
条件1: 水果有热带的,有温带的
条件2: 香蕉不是温带的
推论:如果命题1和命题2都是ture,那香蕉就是热带的
逻辑和直觉
-
直觉可以快速让人学会一些东西,又可以阻止人学会另一些反直觉的东西,这时只能依靠逻辑说服自己
-
如果把所有的推理结果都否决了,那么最后剩下的一种可能不管多离奇都是事情的真想————福尔摩斯
几乎所有语言都能用最少三种语句指定所有逻辑(但错误处理很麻烦)
顺序执行语句
- 按从上往下的顺序执行代码
条件判断语句
-
if...then...else... -
if...lese if... else...
循环语句
-
while...do... -
for i from 1 to n...
二、流程图与伪代码
伪代码
-
伪代码可以让你不被编程语言所束缚,你自己可以发明API,只要能把逻辑理清就行
-
伪代码可以作为在写代码前的演算,如果伪代码都写不好,那代码就更写不好
-
伪代码不需要运行在计算机里,伪代码是运行在脑袋里的逻辑
流程图
- 锻炼大脑,把逻辑画出来
三、算法前置学习
如何找到两个数中最小的那个
- 用数组
[a,b]表示两个数字
普通写法
let minOf2 = (numbers) => {
if (numbers[0] < numbers[1]) {
return numbers[0]
} else {
return numbers[1]
}
}
- 默认numbers是长度为2的数组,比较下标0和下标1的两个数字谁大,然后返回小的那一个数字
用问号冒号表达式优化
let minOf2 = numbers =>
numbers[0] < numbers[1] ? numbers[0] : numbers[1]
用析构赋值优化代码
let minOf2 = ([a, b]) => a < b ? a : b
如何找到三个数中最小的那个
- 用数组
[a,b,c]表示三个数字
let minOf3 = ([a, b, c]) => {
return minOf2([minOf2([a, b]), c])
}
//或者
let minOf3 = ([a, b, c]) => {
return minOf2(a, minOf2([a, b]))
}
-
把三个数拆解成两个数字的数组和一个数字的数组,分别对两个数组进行
minOf2()的操作就能得到三个数的最小值 -
采用了递归
从上面推理出四个数中找到最小的那个
let minOf4 = ([a, b, c, d]) => {
return minOf2([a, minOf3(b, c, d)])
}
- 思路和上面一样,任意长度的最小值都可以用
minOf2()实现
JS内置了最小值APIMath.min()
-
Math.min(1,2)// 1 -
Math.min.call(null,1,2) -
Math.min.apply(null,[1,2])
关于Math
-
虽然
Math看起来像Object一样的构造函数,但它只是普通对象 -
这是唯一的特例: 首字母大写不是构造函数
递归
- 所有递归都能用循环改写
特点
-
函数不停调用自己,每次调用的参数略有不同
-
当满足某个简要条件时,实现一个简单的调用
-
最终算出结果
理解
四、排序算法
选择排序
- 可以用递归实现也可以用循环实现
两个数排序
let sort2 = ([a, b]) => {
if (a < b) {
return [a, b]
} else {
return [b, a]
}
}
// 优化一下
let sort2 = ([a, b]) => {
a < b ? [a, b] : [b, a]
}
思路是把两个数相比较,如果第一个数比第二个数小,直接返回原数组,反之调换两个数的顺序后返回
三个数排序
// 最小值函数
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 sort3 = (numbers) => {
let index = minIndex(numbers)
let min = numbers[index]
numbers.splice(index, 1) // 从numbers删掉min
return [min].concat(sort2(numbers))
}
先获取最小值,然后获取这个最小值在数组里的下标,把最小值从数组内删掉,用最小值拼接数组内剩余数字继续调用sort3函数
任意数字的排序
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 : numbers.reverse()
}
}
思路和上面的思路差不多,先获取最小值以及下标,从原数组内单独拿出来,数组内剩余数字继续调用函数,如果数组内剩余数字小于两个,就直接用两个数字的排序
快速排序
思路
-
想象你是一个体育委员,面对的同学为一个数组分别是
[12,3,7,21,5,9,4,6] -
以任意一个数为基准,比它小的数去前面,比它大的数去后面
-
重复这个操作就可以排序
阮一峰老师写的代码如下
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))
}
-
声明一个变量把排序数组中间值的下标保存起来
-
再声明一个变量把这个下标对应的数组从原数组中取出来
-
声明两个空数组,一个用来存比它小的数,一个存比它大的数
-
进行for循环如果原数组的数字比它大就push进大数组,反之亦然
-
然后重复对左边数组和右边数组进行同样的操作,但要把中间的这个比较的数字加起来
-
如果发现这个数组的长度已经等于1了那么就直接返回它自己,最后得到的数组就是排好序的数组
归并排序
思路和快速排序差不多
-
你还是那个体育委员,还是面对一样的数组
-
但这次你把他们分成两半,让左边的人自己排序,让右边的人也自己排序
-
排好序后再让左边右边的数字合并起来(merge)
代码
let mergeSort = arr => {
if (arr.length === 1) {
return arr
}
let left = arr.slice(0, Math.floor(arr.length / 2))
let right = arr.slice(Math.floor(arr.length / 2))
return merge(mergeSort(left), mergeSort(right))
}
let merge = (a, b) => {
if (a.length === 0) return b
if (b.length === 0) return a
if (a[0] > b[0]) {
return [b[0]].concat(merge(a, b.slice(1)))
} else {
return [a[0]].concat(merge(a.slice(1), b))
}
}
-
其实归并排序重点不是上面的mergeSort函数,而是下面的merge函数,因为排序的工作是下面的merge函数做的
-
先声明两个变量,把原数组切成两半分别存进去
-
然后对两个数组重复执行上面的操作,然后再用merge对操作后的结果进行操作
-
在merge里要首先判断两个数组的长度是否为0,如果为0就直接返回另一个数组
-
如果不为0继续进行判断,看左边数组的第一个数字和右边数组的第一个数字谁小,然后把他们拼接起来返回出去
-
返回后继续进行merge操作直到他们的长度为0,就代表数组排序完成了
计数排序
思路
-
类似于把扑克牌进行排序操作,把每个数字对应的四张牌进行排序
-
用一个哈希表作记录
-
发现数组N就记N:1,如果再次发现N,就让N的value加1
-
最后把哈希表的key全部打出来,假设N的value是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
-
声明一个空的对象当做哈希表,声明一个最大值,初始化为0,声明一个空数组用来打印哈希表内的key
-
遍历数组
-
判断如果原数组被遍历到的数字不存在于哈希表中,那哈希表内就要新增这个数字并把value值初始化为1,如果存在 于哈希表内,就把这个数字的value加1
-
还让每一个被遍历到的数字和max相比,得到原数组中的最大值
-
最后需要从0到原数组中最大值之间遍历哈希表,这里需要判断这个遍历的j是否存在于哈希表中
-
如果存在就把这个哈希表存的key遍历并push进result空数组中,完成排序
-
可以再确定一个原数组中的最小值,遍历哈希表时可以通过最小值到最大值进行遍历,节约内存
算法时间复杂度
-
选择排序 O(n²)
-
快速排序 O(log2n)
-
归并排序 O(log2n)
-
计数排序 O(n+max)
算法学习总结
-
脑壳头想,想不出来画图,写伪代码
-
拿键盘敲,运行,出错,调试,再运行,直到没有错
-
有些细节很难想清楚, 动手列表格找规律
-
多画表,多画图,多log
-
如果js代码有问题,可以先用伪代码