递归和分治

递归

递归(英语:Recursion),在计算机科学中,递归指的是一个函数在其定义中调用自身的方法。这种技术允许程序解决复杂问题,通过将它们分解为更小、更易管理的相似问题。递归通常与分治策略相关联,后者涉及将一个大问题分解为若干个小问题,单独解决这些小问题,然后将结果合并以得到最终解。

递归函数通常具有以下两个主要特征:

  1. 基本情况(Base Case):这是递归停止的条件。在达到基本情况时,函数将停止调用自身。例如,在计算阶乘的递归函数中,0!1! 通常作为基本情况。
  2. 递归步骤(Recursive Step):在这一步中,函数调用自身来解决子问题。这些子问题是原始问题的更小版本。
  • 例题:面试题 08.06. 汉诺塔问题 - 力扣(LeetCode)

在经典汉诺塔问题中,有 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]

提示:

  1. A中盘子的数目不大于14个。

思路:

先分析简单情况:

一.n=1 时:直接将这个盘子从A拿出,放到C。

二.n=2 时:假设上面的盘子是甲,下面的盘子是乙。

甲:A->B 乙:A->C 甲:B->C

三.n时 :可以把上面n-1个盘子看出一个整体(一个盘子)甲,下面的第n个盘子是乙。

甲:A->B 乙:A->C 甲:B->C

此时:甲:A->B 这个就是一个小问题,把n-1个盘子从A移动到B,可以用C当媒介—递归

​ 甲:B->C 这个也是一个小问题,把n-1个盘子从B移动到C,可以用A当媒介—递归

#include 
#include 
using namespace std;

void move(int n, vector<int>& source, vector<int>& target, vector<int>& auxiliary) {
    if (n == 1) {
        // 直接移动
        target.push_back(source.back());
        source.pop_back();
    } else {
        // 移动上面的 n-1 个盘子从 source 到 auxiliary
        move(n - 1, source, auxiliary, target);

        // 移动底下的盘子从 source 到 target
        target.push_back(source.back());
        source.pop_back();

        // 移动 n-1 个盘子从 auxiliary 到 target
        move(n - 1, auxiliary, target, source);
    }
}

void hanota(vector<int>& A, vector<int>& B, vector<int>& C) {
    move(A.size(), A, C, B);
}

int main() {
    vector<int> A = {3, 2, 1, 0}; // Tower A
    vector<int> B; // Tower B
    vector<int> C; // Tower C

    hanota(A, B, C);

    // Output the result
    cout << "Tower C after moving disks: ";
    for (int disk : C) {
        cout << disk << " ";
    }
    cout << endl;

    return 0;
}

分治算法 (Divide and Conquer)

分治算法是一种特殊类型的递归算法,具体包括以下三个步骤:

  1. 分解(Divide):将原问题分解成一系列子问题。这些子问题是原问题的较小版本,但与原问题具有相同的形式。
  2. 解决(Conquer):递归地解决这些子问题。如果子问题足够小,可以直接求解。
  3. 合并(Combine):将子问题的解合并起来,形成原问题的解。

分治算法的经典例子包括快速排序、归并排序、二分搜索等。在这些算法中,问题被分解成更小的部分,独立解决这些小问题,然后将解合并以解决原始问题。

  • 例题 53. 最大子数组和 - 力扣(LeetCode)

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

子数组 是数组中的一个连续部分。

示例 1:

输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。

示例 2:

输入:nums = [1]
输出:1

示例 3:

输入:nums = [5,4,-1,7,8]
输出:23

提示:

  • 1 <= nums.length <= 105
  • -104 <= nums[i] <= 104

**进阶:**如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的 分治法 求解。

#include 
#include 
#include 
using namespace std;

class Solution {
public:
    int maxSub(vector<int>& nums, int l, int r){
        if(l == r){
            return nums[l];
        }

        // 递归体——分治求解
        int mid = (l + r) / 2;

        // 左边最大子序和
        int lmax = maxSub(nums, l, mid);
        // 右边最大子序和
        int rmax = maxSub(nums, mid + 1, r);

        // 跨越中点的最大子序和
        int mmax =  mid_sum(nums, l, r, mid);

        // 返回三者中的最大值
        return max(max(lmax, rmax), mmax);
    }

    int mid_sum(vector<int>& nums, int l, int r, int mid){
        // 从中点向左扩展
        int lmax = INT_MIN; // 左边最大值
        int sum = 0; // 当前和
        for(int i = mid; i >= l; i--){
            sum += nums[i];
            lmax = max(lmax, sum);
        }

        // 从中点向右扩展
        int rmax = INT_MIN; // 右边最大值
        sum = 0; // 当前和
        for(int i = mid + 1; i <= r; i++){
            sum += nums[i];
            rmax = max(rmax, sum);
        }

        // 返回左右最大值之和
        return lmax + rmax;
    }

    int maxSubArray(vector<int>& nums) {
        int n = nums.size();
        int ans = INT_MIN;
        ans = maxSub(nums,0, n - 1);
        return ans;
    }
};

int main(){
    Solution s;
    vector<int> nums = {-2,1,-3,4,-1,2,1,-5,4};
    cout << s.maxSubArray(nums) << endl;
    return 0;
}

递归和分治_第1张图片

思路:

1.暴力:枚举起点和终点,计算区间内的和,取最大值。时间复杂度:O(n^2)

2.分治:时间复杂度:O(nlogn)

取中心点m=(l+r)/2。 和最大的区间要么出现在中心点左边,要么出现在中心点右边,要么横跨中心点。所以问题分解为求这三部分的最大区间和,然后取最大值。----分成3个独立的小问题,分治。

(1) 中心点左边的最大区间和:和原问题相同,递归。

(2) 中心点右边的最大区间和:和原问题相同,递归。

(3)横跨中心点的最大区间和:贪心求解—从中心点往左右两边延申。

从中心点不断往左延申,同时记录最大值lmax

从中心点不断往右延申,同时记录最大值rmax

横跨中心点的最大区间和:lmax+rmax

3.动态规划:线性动态规划。时间复杂度 O(n)。

状态:dp[i]=x 以第i个元素结尾的和最大的区间,最大和是 x

初始状态:dp[0]=nums[0]

转移方程:dp[i]=max(dp[i-1]+nums[i],nums[i])

最终答案:max(dp[i])

你可能感兴趣的:(算法集训,c++,算法,数据结构)