基础算法--递归与分治

0 阅读3分钟

递归


递归,就是在一个函数中调用本身,可以将一个大问题不断拆解到最基础的情况。但是需要注意,对于递归的方法,需要明确递归出口,即“什么情况下递归结束”。否则递归函数会一直调用其本身,直到内存溢出。

递归问题有一道十分经典的例题,就是大名鼎鼎的汉诺塔问题:

递归算法.png

分析: 我们从递归的视角去分析,我们想要的第一步就是把1~n-1的盘子从A移到B上,这样就可以顺利把最大的盘子移到C上,最后把1~n-1移到C上即可(这就是盘子数量为n-1的汉诺塔问题)。 如此分析,可以得出递归参数: 4个参数(盘子数量x,起点柱子s,终点柱子e,媒介柱子h)

下面是代码示例:

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

void move(int x, vector<int>& s, vector<int>& e, vector<int>& h, char s1, char e1, char h1){
    if(x == 1){
        int d = s.back();
        s.pop_back();
        e.push_back(d);
        cout << d << ' ' << s1 << "-->" << e1 << endl;
        return;
    }
    move(x-1, s, h, e, s1, h1, e1);
    int d = s.back();
    s.pop_back();
    e.push_back(d);
    cout << d << ' ' << s1 << "-->" << e1 << endl;
    move(x-1, h, e, s, h1, e1, s1);
}

void hanota(vector<int>& A, vector<int>& B, vector<int>& C) {
    int n = A.size();
    move(n, A, C, B, 'a', 'c', 'b');//把n个盘子从A移到C,经由B的帮助
}

int main(){
    vector<int> a;
    vector<int> b;
    vector<int> c;
    int n, x;

    cin >> n;
    for(int i = 0; i < n; ++i){
        cin >> x;
        a.push_back(x);
    }
    hanota(a, b, c);
    for(int i = 0; i < n; ++i){
        cout << c[i] << ' ';
    }
    cout << endl;

    return 0;
}

分治

分治,字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或不相同的子问题,子问题相互独立,直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。

我们来看一道例题~

递归算法-1.png

分析: 这道题可以用暴力、分治、动态规划来做,我们这里只讲解分治的算法,大家有兴趣可以去思考一下动态规划的做法。 我们把这个数组从中间分开,那么会有一部分子数组在左侧,会有一部分子数组在右侧,还有一部分子数组两侧各有一些元素。我们分开算出这三种情况的答案,然后取max即可。

对于左侧和右侧的情况,我们发现其实就是求l到mid以及mid到r的最大子数组和,那么我们可以考虑递归求解; 对于横跨中间的子数组,我们考虑从mid先向左延伸所能取到的最大值,再考虑向右延伸所能取到的最大值,两者相加即可。

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

int maxxmid(vector<int>& nums, int l, int mid, int r){
    //包括mid,向左延伸最大值
    int lmax = INT_MIN;
    int sum = 0;
    for(int i = mid; i >= l; --i){
        sum += nums[i];
        lmax = max(lmax, sum);
    }

    //不包括mid,向右延伸最大值
    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 maxxans(vector<int>& nums, int l, int r){//求nums[l]~nums[r]的最大子数组和
    if(l == r){
        return nums[l];
    }
    int mid = (l+r) / 2;
    //l ~ mid
    int a1 = maxxans(nums, l, mid);
    //mid+1 ~ r
    int a2 = maxxans(nums, mid+1, r);
    //横跨mid
    int a3 = maxxmid(nums, l, mid, r);

    return max(max(a1, a2), a3);
}
int maxSubArray(vector<int>& nums) {
    int ans = INT_MIN;
    int n = nums.size();
    ans = maxxans(nums, 0, n-1);

}

int main(){
    int n, x;
    vector<int> nums;
    cin >> n;
    for(int i = 0; i < n; ++i){
        cin >> x;
        nums.push_back(x);
    }
    int ans = maxSubArray(nums);

    cout << ans << endl;

    return 0;
}

这种做法时间还是有点长,感兴趣的可以去试试动态规划,时间复杂度只有O(n)。