js算法入门

312 阅读7分钟

逻辑与算法

在使用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)