递归和分治法

分治法

将问题分解,通过求解局部性的小问题来解开原本的问题。这种技巧称为分治法。

递归

  • 实现分治法需要使用递归。
  • 在使用递归时我们需要设计递归函数,递归函数指的是自己调用自己的函数,是设计算法时候的一种技巧。
  • 在我看来,递归最重要的无非是寻找大问题与小问题之间的关系和最终的递归终止条件。
  • 在笔记(四)中,二分搜索曾经用到了递归的方法。
  • 这里,先以最为经典的求n的阶乘作为例子来分析一下。
  • 固然,这个问题可以用循环来实现
int factorial(int n){
	if (n == 0 || n == 1) return 1;
	else{
		int ans = 1;
		//循环
		for (int i = 1; i <= n; i++){
			ans *= i;
		}
	}
	return ans;
}
  • 但是当我们换一种思路,通过分治法的思想来考虑问题。首先,很容易能够得出n!和(n-1)!之间的关系,即n! = (n-1)! *n,如此我们就得到了一个比原来的问题小的局部问题:求(n-1)! 。对于(n-1)!又可以分为更小的与n-2相关的局部问题,就这样一直下去,直到终点。关于终止条件,也很容易想出当n是1或者n是0的时候停止,并返回1。代码如下
int factorial(int n){
    if (n == 0 || n == 1) return 1;		//终止条件
    else return n * factorial(n-1);		//递归调用
}
  • 对于分治法来说,一旦想出了如何把大问题按照一种规则不断分解成局部问题,在程序的实现上可能会比其他的方法要简单一些,以下用leetcode的几道题来具体分析一下。
面试题 08.06. 汉诺塔问题

在经典汉诺塔问题中,有 3 根柱子及 N
个不同大小的穿孔圆盘,盘子可以滑入任意一根柱子。一开始,所有盘子自上而下按升序依次套在第一根柱子上(即每一个盘子只能放在更大的盘子上面)。移动圆盘时受到以下限制:
(1) 每次只能移动一个盘子;
(2) 盘子只能从柱子顶端滑出移到下一根柱子;
(3) 盘子只能叠在比它大的盘子上。

请编写程序,用栈将所有盘子从第一根柱子移到最后一根柱子。

你需要原地修改栈。

示例1:

输入:A = [2, 1, 0], B = [], C = [] 输出:C = [2, 1, 0]

示例2:

输入:A = [1, 0], B = [], C = [] 输出:C = [1, 0]

提示:

A中盘子的数量不大于14个

  • 汉诺塔可以说是最经典的可以利用分治法求解的问题之一。
  • 我们要达到的目的就是把A的全部挪到C,但是只能操作最顶端的一块,并且大的盘子不能放在小的盘子上。
  • 以下使用leetcode题解中的图片(by Pumpkin🎃)
    在这里插入图片描述
  • 对于复杂问题,我们可以先从简单的入手。
  • 对于1块盘子来说,很简单,直接从A挪到C。
  • 而对于两块盘子,我们就需要先将小盘子A放到B,再将比它大一点的盘子挪到C,最后再将它从B挪到C。
    在这里插入图片描述- 对于n块来说,我们通过通过分治法的思路来求解问题:

1.首先将n-1个盘子移到B
2.将最大的盘子从A移到C
3.把B上面的n-1个盘子移到C

在这里插入图片描述- 可以看到以上这三步中,1和3都是可以通过递归求解的,而他们又都是相同类型的移动问题,所以我们只需要一个递归函数就可以解决

//首先我们来设计这个函数
void move(vector<int> &From, vector<int> &To, vector<int> &Swap, int amount){
    //如果只有一个,就直接移到要去的地方
    if (amount == 1) {
        To.push_back(From.back());
        From.pop_back();
        return;
    }
    else{
        //将上面的amount-1个移到空盘子上
        move(From, Swap, To, amount-1);
        //将最底下的一个移到要去的地方
        To.push_back(From.back());
        From.pop_back();
        //再将刚刚用来作为暂时交换盘子上amount-1个盘子移到要去的地方
        move(Swap, To, From, amount-1);
    }
}
  • 主体函数部分就是正确调用一遍move函数
class Solution {
public:
    void hanota(vector<int>& A, vector<int>& B, vector<int>& C) {
        move(A, C, B, A.size());
    }
    void move(vector<int> &From, vector<int> &To, vector<int> &Swap, int amount){
        //如果只有一个,就直接移到要去的地方
        if (amount == 1) {
            To.push_back(From.back());
            From.pop_back();
            return;
        }
        else{
            //将上面的amount-1个移到空盘子上
            move(From, Swap, To, amount-1);
            //将最底下的一个移到要去的地方
            To.push_back(From.back());
            From.pop_back();
            //再将刚刚用来作为暂时交换盘子上amount-1个盘子移到要去的地方
            move(Swap, To, From, amount-1);
        }
    }
};

返回目录

判断两个二叉树是否同构可以使用递归分治法思想,下面是两种实现方式: 1. 递归实现 递归实现的思路是分别判断两个二叉树的左子树和右子树是否同构,如果左右子树都同构,则判断两个二叉树的根节点是否相同。代码如下: ```python def is_isomorphic(root1, root2): if not root1 and not root2: return True if not root1 or not root2: return False if root1.val != root2.val: return False return (is_isomorphic(root1.left, root2.left) and is_isomorphic(root1.right, root2.right)) or \ (is_isomorphic(root1.left, root2.right) and is_isomorphic(root1.right, root2.left)) ``` 2. 分治实现 分治实现的思路是将两个二叉树分别划分成左子树和右子树,然后递归判断左子树和右子树是否同构,最后判断两个二叉树的根节点是否相同。代码如下: ```python def is_isomorphic(root1, root2): if not root1 and not root2: return True if not root1 or not root2: return False if root1.val != root2.val: return False return (is_isomorphic(root1.left, root2.left) and is_isomorphic(root1.right, root2.right)) or \ (is_isomorphic(root1.left, root2.right) and is_isomorphic(root1.right, root2.left)) def is_isomorphic_dc(root1, root2): if not root1 and not root2: return True if not root1 or not root2: return False if root1.val != root2.val: return False return is_isomorphic_dc(root1.left, root2.left) and \ is_isomorphic_dc(root1.right, root2.right) and \ is_isomorphic_dc(root1.left, root2.right) and \ is_isomorphic_dc(root1.right, root2.left) ``` 以上两种方法都可以判断两个二叉树是否同构,其中递归实现的代码比较简洁,分治实现的代码稍微复杂一些,但是思路清晰。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值