逻辑与算法
在使用js过程中,很多基础语法我们都知道,但是当语句叠加到一起时,往往就造成读不懂的尴尬。编程离不开思想,掌握抽象化的思维才能写好代码,而逻辑与算法还有数据结构都是编程中最核心的知识。
逻辑三段论?
先来看一段逻辑三段论
1、js中有七种数据类型,分别是Number、String、Boolean、Symbol、Null、NaN和Object
2、函数不属于前六种
3、那么可以得出函数是Object类型
上述是一个很简单的逻辑推论,只要假设前二为真,便可得出结论3。
但是有时候正确的逻辑推论是很难分辨真伪的,甚至会迷惑我们,这就跟我们的直觉有关。再来看一段推论:
1、所有函数都是Function构造的
2、Function本身就是函数
3、所以Function构造了Function自己
已知前2是正确的,按照正常人的思维,论点3完全不成立。但是从逻辑的角度来说,论点3又是正确的。
而实际上,在js中这个论点也是正确的,不信你可以问浏览器。
这种思维在编程中很正常,但是放到实际生活中又会觉得很怪。我们称之为直觉常识判断。
所以我们可以得出逻辑在编程中是一种虚无缥缈、非常抽象的思想。
编程时需要理清逻辑
我们可以使用以下三种方式来操作逻辑:
1、顺序代码
2、条件判断语句
3、循环
很高兴我们都已经学过了,但是很多时候写代码需要一些辅助来达到更好地理清思路。例如使用伪代码或者画流程图的方式来帮助理清思路。
判断谁最小的两种方式
循环方式
假设我们需要在10个数中判断谁最小。我们可以画个流程图:
- 我们先确认一个数据结构,用数组来保存这10个数字
- 然后先获取第一个数让他为最小值start,通过遍历循环来分别让后续跟这个数做对比
- 如果有更小的,那么就让更小的成为start
这个栗子非常简单,根据上面的流程图,我们可以写出以下代码:
let numbers = [1, 2, 3, 86, 2, 90, 32, 8, 99];
let minNum = numbers[0];
function min(numbers) {
for (let i = 1; i < numbers.length; i++) {
minNum > numbers[i] ? minNum = numbers[i] : minNum
}
return minNum
}
console.log(min(numbers))
递归思路
递归排序可以借用选择排序的思路
那我们先来回顾一下递归需要哪些条件?
1、函数不停调用自己
2、需要一个终止的条件
实现10个数字的思路可以是这样
首先我们确定[a,b]两个数之间的对比
let minOf2 = ([a, b]) => {
return b > a ? a : b //这里用到的是解构赋值
}
其次,不停使用递归自己调用自己来完成比大小的行为
function min(numbers) {
if (numbers.length > 2) {
return minOf2([numbers[0], min(numbers.splice(1))])
} else {
return minOf2([numbers[0], numbers[1]])
}
}
//最后的条件是只剩两个数时,互相比高低
整个递归的运行过程是这样的:
假设let numbers = [1, 2, 3, 86, 2];
那么实际运行函数:
minOf2[1,(minOf2[2,(minOf2[3,(minOf2(86,2))])])]
演变过程:
-
minOf2[1,([2,3,86,2])]
-
minOf2[1,(minOf2[2,([3,86,2])])]
-
minOf2[1,(minOf2[2,(minOf2[3,([86,2])])])]
-
minOf2[1,(minOf[2,(minOf2[3,(minOf2[86,2])])])]
实现数组内排序
我们还可以拓展一下题目,将数组内的数字进行排序
冒泡排序
假设我需要把[4,3,2]进行排序,怎么做呢?
我可以先把第一个数跟第二个数比对,如果第一个数比第二个大,那么换一下位置,然后循环重复这个过程。
一共涉及到两个循环
原理如下:
假设有5个数 5 4 3 2 1,从左往右从小到大排序
-
外层循环第0次:先使5跟4比较,如果5比较大,向前进一步...类推结束第1次内层循环
-
外层循环第1次:4与3比较,如果4大,向前一步...类推结束第2次内层循环
-
合计外层循环4次 0-3
-
内层循环的规律:当外层循环第0次时,循环4次;外层循环第1次,循环3次..外3-内2,外2-内1次
如果是长度为5的数组,那么循环4次就可以了,因为循环是从0开始,所以实际上循环到3即可。
我们设外层循环为y,y需要循环lenght-2次,那么演变成代码就是 y<length-1
那我们需要怎么将两个数换一下位置呢?只要设置变量x来当成中间值即可
最终实现代码如下:
function sort(numbers){
for(var y=0;y<numbers.length-1;y++){
for(var i=0;i<numbers.length-1-y;i++){
var x
if(numbers[i]>numbers[i+1]){
x=numbers[i];
numbers[i]=numbers[i+1];
numbers[i+1]=x
}
}
}
return numbers
}
递归排序
假设[a,b]两个数字,我们可以通过以下方式来排序
function sort2([a,b]){
return a<b?[a,b]:[b,a]
}
如果是三个数字呢?
以上方式肯定行不通。
转换一下思路:首先我们需要将一个最小值拿出来,再让执行上面的代码,最后把两者接到一起。
function sort3(numbers) {
let min = Math.min.apply(null, numbers) //获取最小值
let minIndex = numbers.indexOf(min)
let minNum = numbers.splice(minIndex, 1)
return minNum.concat(minOf2(numbers))
}
好像可以!但是如果把numbers换成[4,2,1,3]肯定不行
我们尝试用递归,直到递归到minOf2()为止
最后的代码就是
let minOf2 = ([a, b]) => {
return b > a ? [a, b] : [b, a]
}
function sort3(numbers) {
if (numbers.length > 2) {
let min = Math.min.apply(null, numbers);
let minIndex = numbers.indexOf(min)
let minNum = numbers.splice(minIndex, 1)
return minNum.concat(sort3(numbers))
} else {
return minOf2(numbers)
}
}
console.log(sort3(numbers));
我们可以用代入法来尝试理解
-
首先定义一个数组[4,3,7,6,5]
-
判断大于2,把最小的拿出来,结果为[3]+[4,6,7,5]
-
判断大于2,把最小的拿出来,结果为[3]+([4]+[6,7,5])
-
判断大于2,把最小的拿出来,结果为[3]+([4]+([5]+[7,6]))
-
判断小于2,结果为[3]+([4]+([5]+[6,7]))
-
最终结果为[3,4,5,6,7]
选择排序
上面的递归排序也可以用循环方式的选择排序来完成
原理如下:
假设有一个数组[5,4,3,2,1]
第一次我们通过Math.min.apply来获取里面最小的数,让他与第0位的做互换,此时i=0。数组为[1,4,3,2,5]
第二次当i=1时,我们绕过第0个数,从第1个数开始获取后面的最小值2,然后与第1位做互换。此时数组为[1,2,3,4,5]
...
我们可以发现一个规律,因为i是从0开始的,所以我们可以借助i来进行排序,只需要永远让后面的最小值与数组的第i位互换就行。
需要解决三个问题: 1、循环几次
2、怎样互换
3、如何绕过已获得的最小值,取后面数组的最小值
解决思路:
因为数组的序号是从0开始 所以我们只需要循环i-1次就能解决问题。
互换也很简单,使用第三方变量来交换即可
第三个问题我们可以使用Array.slice方法浅拷贝数组,只需要设置起点为i就可以避开第i-1个数获取后面数组的最小值
实现代码:
//选择排序的循环写法
let numbers = [3, 2, 1, 4, 9, 0, 999, 888, 283]
function sortSeletor(numbers) {
for (let i = 0; i < numbers.length - 1; i++) {
//每次循环都会找从下标i开始的数组里的最小的数
let minNum = Math.min.apply(null, numbers.slice(i))
//找出最小数的下标
let index = numbers.indexOf(minNum)
//设置第三方变量来交换
let targ = numbers[index]
numbers[index] = numbers[i]
numbers[i] = targ
}
return numbers
}
sortSeletor(numbers)
计数排序
计数排序是种很好理解的算法,假设我们现在有[1,2,3,2,3,6,8,8,2]这组数组,我们可以将他丢入哈希表{}中计数,这时候就变成了
{1:1,2:3,3:2,6:1,8:2}
然后创造一个for循环,当循环i存在于hash表中时,查看对应的value,有几个value就push几次i到新数组中,这样就可以获得一个排好序的数组。
//计数排序
let [hash, max, result] = [{}, 0, []] //析构赋值
function sort(numbers) {
//把数组都丢进hash表中
for (let i = 0; i < numbers.length - 1; i++) {
if (!(numbers[i] in hash)) {
hash[numbers[i]] = 1
} else {
hash[numbers[i]] += 1
}
//找到numbers中的最大值,让max成为第二个for循环的终点
if (max < numbers[i]) {
max = numbers[i]
}
}
// 用for循环产生的j来找hash表中的key
for (let j = 0; j <= max; j++) {
if (j in hash) {
for (let y = 0; y < hash[j]; y++) {
result.push(j)
}
}
}
return result
}
sort(numbers)