前言
本人涉及web开发领域的学习以来,很少去学习算法相关知识,今日对于基础的算法进行了初步探究,包括递归、二分查找等,有所感发,发表这篇博客供大家学习参考,相互交流!
1.递归算法
例题1:递归求斐波那契数列
const readline = require("readline"); const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); /* 输入要求第几位 */ rl.on("line", function (line) { let num = Number(line); const test = (a) => { if (a === 1 || a === 2) return 1; return test(a - 1) + test(a - 2); }; console.log(test(num)); });
递归的思想:
1.找出一个通用的解法模板,不断地去调用这个模板(自己调用自己)
2.这个模板在不断地被调用的过程中并不会得到解决,但是随着不能被解决的问题不断地堆积在栈内,总有一个点会找到一个准确的可以解决问题的值,然后会迅速向回解决所有未解决的问题
注意点:
1.递归的思想非常抽象,基本是无法通过看代码一眼看出最终运算结果的,要有抽象的思维
2.只要将一个通用的模板写好,那么接下来的递归调用就会自发进行,最后得到一个合理的结果,可以说是“山重水复疑无路,柳暗花明又一村”!
3.递归的方法中只写模板,并且这个模板是在宏观意义上直接“可以”输出结果的,只不过是抽象的“可以”。
比如n的阶乘,可以分解成n + n-1的阶乘,这种整体拆分的思想是要有的!
再比如斐波那契数列每一项就是前两项的和,那么我们递归的模板就是前两项的和
4.递归算法里面必须要有if(){return },这既是我们程序找到的那个突破点,也是保证不会栈溢出的必要操作
5.模拟递归场景要先取少数量的内容(小的数字),分析一下场景进行的过程,找到模板
6.看递归过程的时候,要每递归一次将问题数据集-1来看,这样问题会被一层一层拨开
例题2:汉诺塔问题
汉诺塔问题实际上步骤中包含了两个递归,稍微复杂一些!
/* 汉诺塔问题:汉诺塔是一个经典的递归问题。它包括三根柱子和一些大小不等的圆盘。 初始时,所有圆盘都叠在第一根柱子上,按照从大到小的顺序排列。 目标是将所有圆盘移动到第三根柱子上,并保持原有顺序。每次移动只能移动一个圆盘,并且不能将大圆盘放在小圆盘上面。 */ const readline = require("readline"); const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); /* 输入盘子数量 */ rl.on("line", function (line) { let num = Number(line); const han = (n, a, b, c) => { if (n === 0) console.log(a + "===>" + c); //程序突破口 else { //下面这三步在宏观意义上就可以得到我们想要的结果 han(n - 1, a, c, b); //将第一个移动到第二个(实际上还是重复这个模板) console.log(a + "===>" + c); //将第一个的最后一个盘子直接放到第三个 han(n - 1, b, a, c);//将第二个移动到第三个(实际上还是重复这个模板) } }; han(num, "A", "B", "C"); });
解题思路:
我们想象只有两个盘子,那么这个模板过程就出来了:将第一个柱子上除了最后一个盘子(n-1个)都移动到第二个柱子,将第一个柱子的最后一个盘子(1个)移动到第三个柱子,将第二个柱子的所有盘子(n-1个)移动到第三个柱子
2.二分查找法
b站对应视频:二分查找为什么总是写错?_哔哩哔哩_bilibili
例题:输出一个数组里面等于5的数的第一个索引
let arr = [1, 2, 5, 5, 7]; const test = (array) => { // console.log(array); let l = -1; let r = array.length; while (l + 1 !== r) { let num = parseInt(l + r); if (array[num] < 5) { l = num; } else { r = num; } } if (array[r] === 5) { //特判,判断是否确实是5 return r; } else { return -1; //不存在5 } }; console.log(test(arr));
以上使用的是二分查找的模板:(伪代码)
l = -1, r = N while l + 1 != N m = l + r / 2 (向下取整) if isBlue(m) l = m else r = m return l or r
二分查找的思想:
1.找到isBlue的条件,即临界点(红蓝分界线)
2.输出l还是r,即分界线的左侧还是右侧
策略:
蓝色的最右边第一个值必须满足isBlue的条件(条件一般只用小于号),可以在草稿上模拟几个数字,模拟出红蓝分界状况,以此来决定输出l还是r
例如:输出数组中最先匹配到的5的索引,那么条件可以为<5,那么就取r
输出数组中最后匹配到的5的索引,那么可以取<=5,那么就取l
3.对于递归和二分的思考
递归和二分中都对于灰色部分(无法解决的问题)的思想有涉及
1.递归是把灰色部分先扩大,把问题堆栈起来,找到了解决问题的点再将灰色部分逐渐减少
2.二分是一半一半地,逐渐缩小灰色部分,直到解决问题