1 递归
- 分析问题和子问题的关系:采用
自顶而下的策略;比如求解f(n),就必须先求出n*f(n-1)
2. 递归的三大要素
递归前提条件:求解一个大问题,这个大问题可以划分为多个子问题求解,并且每个子问题的规模都一样;如果规模不一样则没法使用递归
1. 明确递归函数函数想要干什么
2. 寻找递归结束条件
3. 找出函数的等价关系式(注意是单分支还是双分支)
2.1 明确递归函数函数想要干什么
- 先不管函数里面的代码什么,而是要先明白,你这个函数是要用来干什么,这个函数的功能是什么
// 算 n 的阶乘(假设n不为0)
function f(n){
}
2.2 寻找递归结束条件
- 我们需要找出
当参数为啥时,递归结束,之后直接把结果返回,请注意,这个时候我们必须能根据这个参数的值,能够直接知道函数的结果是什么
2.3 找出函数的等价关系式
注意是单分支还是双分支
- 我们要
不断缩小参数的范围,缩小之后,我们可以通过一些辅助的变量或者操作,使原函数的结果不变
3 递归题
3.1 斐波那契(单分支)
// 题目:斐波那契数列的是这样一个数列:1、1、2、3、5、8、13、21、34….,即第一项 f(1) = 1,第二项 f(2) = 1…..,第 n 项目为 f(n) = f(n-1) + f(n-2)。求第 n 项的值是多少。
function f(n){
// 参数终止条件
if (n == 1 || n == 2){
return 1
}
// 等价关系 fn = f(n-1)+f(n-2)
return f(n-1)+f(n-2);
}
3.2 小青蛙跳台阶(多分支)
// 题目:一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
function f(n){
if (n <= 2){
return n;
}
return f(n-1)+f(n-2)
}
3.3 反转单链表
// 题目:反转单链表。例如链表为:1->2->3->4。反转后为 4->3->2->1
function reverseList(Node head){
if(head == null || head.next == null){
3 return head;
4 }
// 递归2->3->4 返回4->3->2; 此时head还是指向1,因为1没动
Node newList = reverseList(head.next);
// 改变 1,2节点的指向。
Node tmp = head.next;
tmp.next = head;
head.next = null;
return newList;
}
3.4 求n!的阶乘
function f(n){
if (n <= 1){
return 1;
}
return n*f(n-1);
}
3.5 反转二叉树
function Node(value){
this.left = null;
this.right = null;
this.value = value;
}
function reverseBinaryTree(Node root){
// 叶子结果不能翻转
if(root == null){
return null;
}
// 翻转左节点下的左右节点
Node leftRoot = reverseBinaryTree(root.left);
// 翻转右节点下的左右节点
Node rightRoot = reverseBinaryTree(root.right);
// 左右节点下的二叉树翻转好后,翻转根节点的左右节点
root.left = rightRoot;
root.right = leftRoot;
return root;
}
3.6 汉诺塔
// 题目:从左到右有A、B、C三根柱子,其中A柱子上面有从小叠到大的n个圆盘,现要求将A柱子上的圆盘移到C柱子上去,期间只有一个原则:一次只能移到一个盘子且大盘子不能在小盘子上面,求移动的步骤和移动的次数
// 思路:以下几点递归思想至关重要
1. 定义问题的递归函数,明确函数的功能,我们定义这个函数的功能为:把 A 上面的 n 个圆盘经由 B 移到 C
2. 分析问题和子问题的关系:要采用自上而下的分析方式要将 n 个圆盘从A经由 B 移到 C 柱上去,首先需要将n-1看成一个整体,接着将n-1从A经由C移动到B,再将第n个盘子从A移动到C,再将n-1个盘子从B经由A移动到C。
3. 解决问题思想:采用从下往上,比如这里A只有两个盘子,那么需要将A最上面的盘子移动到B上,再将A的最下面的盘子移动到C上,再将B上面的盘子移动到C上。是一种循环思路,不过在找问题的过程中 切忌把子问题层层展开,到汉诺塔这个问题上切忌再分析 n-3,n-4 怎么移,这样会把你绕晕,只要找到一层问题与子问题的关系得出可以用递归表示即可
// 以上分析可以得到递推公式
move(A from C) = move(n-1 from A to B) + move(A to C) + move(n-1 from B to C)
// 汉诺塔
// 第一步:明白函数含义是: 将 n个盘子 从A 经由B 移动到C
function hanoid(n, A, B, C){
// 第二步:终止条件是A上没有盘子可移动
if (n <= 0){
return;
}
// 将上面的 n-1 个圆盘 从A 经由 C 移到 B
hanoid(n-1, A, C, B);
// 此时将 A 底下的那块最大的圆盘移到 C
move(A, C);
// 再将 B 上的 n-1 个圆盘 从B 经由A 移到C上
hanoid(n-1, B, A, C);
}
function move(a, b){
console.log(`${a}->${b}`)
}
3.7 二叉树先序遍历
function preOrder(Node root){
if(root == null){
return;
}
console.log(root.data);
preOrder(root.left);
preOrder(root.right);
}
3.8 二叉树中序遍历
function inOrder(Node root){
if(root == null){
return;
}
inOrder(root.left);
console.log(root.data);
inOrder(root.right);
}
3.9 二叉树后续遍历
function postOrder(Node root){
if(root == null){
return;
}
postOrder(root.left);
postOrder(root.right);
console.log(root.data);
}
3.10 快速排序
// 快速排序图解:
基准这里默认是数组第一个数 m = 6
[6, 5, 2, 7, 3, 9, 8, 4, 10, 1]
i j m=6
arr[j] < m 所以需要将 arr[i] = arr[j]
[1, 5, 2, 7, 3, 9, 8, 4, 10, 1]
i j m=6
循环比较arr[i] < m ++i 一直到 arr[i] > m 停
[1, 5, 2, 7, 3, 9, 8, 4, 10, 1]
i j m=6
arr[i] > m 所以需要将 arr[j] = arr[j]
[1, 5, 2, 7, 3, 9, 8, 4, 10, 1]
i j m=6
[1, 5, 2, 7, 3, 9, 8, 4, 10, 7]
i j m=6
循环比较arr[j]>m --j 一直到 arr[j]<m 停
[1, 5, 2, 7, 3, 9, 8, 4, 10, 7]
i j m=6
[1, 5, 2, 4, 3, 9, 8, 4, 10, 7]
i j m=6
[1, 5, 2, 4, 3, 9, 8, 4, 10, 7]
i j m=6
[1, 5, 2, 4, 3, 9, 8, 9, 10, 7]
i j m=6
[1, 5, 2, 4, 3, 9, 8, 9, 10, 7]
i j m=6
[1, 5, 2, 4, 3, 9, 8, 9, 10, 7]
i m=6
j
i==j时,arr[i] = m 将m放入基准位置
[1, 5, 2, 4, 3, 6, 8, 9, 10, 7]
i m=6
j
// 快排解析
从最右边j开始每次用arr[j]和基准m比大小,如果arr[j]>m, 则--j,否则将arr[j]直接赋值给arr[i], 接着比较arr[i]<m,则++i
如果arr[i]>m, 那么直接将arr[i]赋值给arr[j], 再比较arr[j]和m大小,依次类推;最终找到一个基准位置将m的值放到基准位,这样m左边都比m小,右边都比m大; 然后再对左右两边递归
function findMidIndex(arr, left, right){
let i = left;
let j = right;
let key = arr[i];
while(i<j){
while(i < j && arr[j] >= key){
--j;
}
arr[i] = arr[j];
while(i < j && arr[i] <= key){
++i;
}
arr[j] = arr[i];
}
arr[i] = key;
return i;
}
function quickSort(arr, left, right){
if (left < right){
let m = findMidIndex(arr, left, right);
quickSort(arr, left, m-1);
quickSort(arr, m+1, right);
}
}
var arr = [6,5,2,7,3,9,8,4,10,1];
quickSort(arr, 0, arr.length-1);
console.log(arr);
4. 递归算法复杂度
递归算法的时间复杂度:子问题个数乘以解决一个子问题需要的时间