算法设计与分析--递归与分治

本文介绍了递归和分治这两种算法思想及其在解决问题中的应用。递归是从上到下从未知到已知,而分治则是将大问题分解为小问题进行解决。通过递归实现的算法如快速排序、斐波那契数列等,而分治法的例子包括二路归并排序、分治法计算半数集等。递归和分治虽然高效,但也可能导致效率低和堆栈溢出等问题。文章提供了多个示例代码来展示这些算法的实际应用。
摘要由CSDN通过智能技术生成

递推/递归既可以理解成是一种算法思想,也可以理解成是一种算法的实现方式。

分治算法策略主要通过递归实现,大规模问题分解成小规模问题;动态规划算法主要通过递推实现。

递归和递推的核心其实都是递推式。

递归:从上到下 从未知到已知解决问题。

递推:从下到上 从已知到未知解决问题。

第3章 递归与分治策略

分治法的设计思想:将一个难以直接解决的大问题,分解成一些规模较小的相同问题,以便各个击破,分而治之。

直接或间接地调用自身的算法/函数称为递归算法/函数。

用递归解决问题时,关键是确定递归体和递归出口。

分治法的基本思想是将一个规模为n的问题分解为k个规模较小的子问题,这些子问题互相独立且与原问题相同。

子问题的解可以合并为该问题的解。 

递归程序的优点: 在计算机算法设计与分析中,使用递归技术往往使函数的定义和算法的描述简洁且易于理解。

递归程序的缺点: 递归算法解题的运行效率较低 在递归调用过程中,系统为每一层的返回点、局部变量等开辟了堆栈来存储。递归次数过多容易造成堆栈溢出等。

01:寻找中位数 (快速排序版)

总时间限制:

100ms

内存限制: 

65535kB

描述

在N(1 <= N <= 100001 且N为奇数)个数中,找到中位数。

输入

第1行:N

第2行:N个整数

输出

输入的第2行N个整数的中位数。

样例输入

5
2 4 1 3 5

样例输出

3
#include <iostream>

using namespace std;

void quick_sort(int *a,int l,int r){
    if(l<r){
        int i=l;
        int j=r;
        int x=a[i];
        while(i<j){
            while(i<j&&a[j]>=x){
                j--;
            }
            if(i<j){
                a[i]=a[j];
            }
            while(i<j&&a[i]<x){
                i++;
            }
            if(i<j){
                a[j]=a[i];
            }
        }
        a[i]=x;
        quick_sort(a,l,i-1);
        quick_sort(a,i+1,r);
    }

}

int main()
{
    int n;
    cin>>n;
    int a[n];
    for(int i=0;i<n;i++)
    {
        cin>>a[i];
    }
    int mid=sizeof(a)/sizeof(a[0])/2;
    quick_sort(a,0,n);
    cout<<a[mid];
    return 0;
}

02:半数集(分治法)

总时间限制:

65535ms

内存限制: 

65535kB

描述

给定一个自然数n,由n 开始可以依次产生半数集set(n)中的数如下。
(1) n∈set(n);
(2) 在n 的左边加上一个自然数,但该自然数不能超过最近添加的数的一半;
(3) 按此规则进行处理,直到不能再添加自然数为止。
例如,set(6)={6,16,26,126,36,136}。半数集set(6)中有6 个元素。

输入

输入一个自然数n

输出

半数集中数的个数

样例输入

6

样例输出

6
#include <iostream>

using namespace std;

int a[1001];
int comp(int n)
{
    int ans=1;
    if(a[n]>0) return a[n];
    for(int i=1;i<=n/2;i++)
        ans+=comp(i);
    a[n]=ans;
    return ans;
}
int main()
{
    int n;
    cin>>n;
    cout<<comp(n)<<endl;
    return 0;
}

03:众数及重数 

总时间限制:

1000ms

内存限制: 

1000kB

描述

给定含有n个元素的多重集合S,每个元素在S中出现的次数称为该元素的重数。多重集S中重数最大的元素称为众数。例如,S={1,2,2,2,3,5}。多重集S的众数是2,其重数为3。对于给定的n个自然数组成的多重集S,计算S的众数及其重数 。

输入

输入集合大小n及n个数

输出

输出两行
第一行为众数
第二行为重数

样例输入

6
1 2 2 2 3 5

样例输出

2
3
#include <iostream>
using namespace std;

void find(int a[],int n){
    int i;
    int c[n];
    for (i=0;i<n;i++){
        c[i]=0;
    }
    for (i = 0; i < n;)
    {
        c[i]=1;
        int j=i;
        while(a[i]==a[j]){
            if (a[i]==a[i+1])
            {
                i++;c[j]++;
            }
            else{
                i++;
            }
        }
    }
    int max=c[0],k=0;
    for (int i = 0; i < n; i++)
    {
        if(max<c[i]) {
            max=c[i];
            k=i;
        }
    }
    cout<<a[k]<<endl;
    cout<<max<<endl;
}

int main(int argc, char const *argv[])
{
    int n;
    cin>>n;
    int a[n];
    for(int i=0;i<n;i++)
        cin>>a[i];
    find(a,n);
    return 0;
}

04:递归求平方和函数(递归法)

总时间限制:

1000ms

单个测试点时间限制: 

100ms

内存限制: 

65535kB

描述

用递归函数,求f(n),n的值由主函数输入。

输入

输入n的值

输出

输出1到n的平方和

样例输入

4

样例输出

30
#include <iostream>

using namespace std;

int main()
{
    int n;
    cin>>n;
    int sum=0;
    for(int i=1;i<=n;i++)
    {
        sum=sum+i*i;
    }
    cout<<sum<<endl;
    return 0;
}

05:放苹果(盘子相同)

总时间限制: 

65536ms

单个测试点时间限制: 

65535ms

内存限制: 

65535kB

描述

把M个同样的苹果放在N个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分法?(用K表示)5,1,1和1,5,1 是同一种分法。

输入

苹果个数m 和盘子个数n(0<=M,1<=N<=10)

输出

不同的放法数目

样例输入

7 3

样例输出

8
#include <iostream>

using namespace std;

int main()
{
    int m,n,dp[15][15];
    cin>>m>>n;
    for(int i=0;i<=m;i++){
        dp[i][0]=0;
        dp[i][1]=1;
    }
    for(int i=1;i<=n;i++)
        dp[0][i]=1;
    for(int i=1;i<=m;i++){
        for(int j=1;j<=n;j++){
            if(i<j)
                dp[i][j]=dp[i][i];
            else
                dp[i][j]=dp[i][j-1]+dp[i-j][j];
        }
    }
    cout<<dp[m][n]<<endl;
    return 0;
}

06:二路归并排序

总时间限制: 

1000ms

内存限制: 

65536kB

描述

给定两个n个有序数和m个有序数这样两组数,要求输出两组数合并后的有序数列。

输入

一行一个n,m分别代表接下俩会有n个有序数和m个有序数。
一行一组拥有n个数的从小到大的有序数列。
一行一组拥有m个数的从小到大的有序数列。

输出

这n+m个数的有序形式,要求从小到大。

样例输入

5 4
1 6 7 8 10
2 5 6 7 

样例输出

1 2 5 6 6 7 7 8 10
#include <iostream>
using namespace std;
class Sort
{
public:
    Sort(int r[ ], int n);
    ~Sort( );
    void binarySearch(int first, int last);
    void Print( );
private:
    void binary(int first1, int last1, int last2);
    int *data;
    int length;
};

Sort :: Sort(int r[ ], int n)
{
    data = new int[n];
    for (int i = 0; i < n; i++)
        data[i] = r[i];
    length = n;
}

Sort :: ~Sort( )
{
    delete[ ] data;
}

void Sort :: binary(int first1, int last1, int last2)
{
    int *temp = new int[length];
    int i = first1, j = last1 + 1, k = first1;
    while (i <= last1 && j <= last2)
    {
        if (data[i] <= data[j])
            temp[k++] = data[i++];
        else
            temp[k++] = data[j++];
    }
    while (i <= last1)
        temp[k++] = data[i++];
    while (j <= last2)
        temp[k++] = data[j++];
    for (i = first1; i <= last2; i++)
        data[i] = temp[i];
    delete[ ] temp;
}

void Sort ::binarySearch(int first, int last)
{
    if (first == last)
        return;
    else
    {
        int mid = (first + last) / 2;
        binarySearch(first, mid);
        binarySearch(mid + 1, last);
        binary(first, mid, last);
    }
}

void Sort :: Print( )
{
    for (int i = 0; i < length; i++)
        cout << data[i] << " ";
    cout << endl;
}

int main() {
    int n1;
    cin >> n1;
    int n2;
    cin >> n2;
    int arr[n1+n2];
    for(int i = 0; i < n1+n2; i ++ ){
        cin >> arr[i];
    }
    Sort L(arr, n1+n2);
    L.binarySearch(0, n1+n2 - 1);
    L.Print( );
    return 0;
}

08:排队购票​​​​​​​

总时间限制: 

10000ms

内存限制: 

10000kB

描述

一场球赛开始前,售票工作正在紧张进行中。 每张球票为50元, 有m+n个人排队等待购票, 其中有m 个人手持50元的钞票, 另外n个人手持100元的钞票。 求出这m+n个人排队购票,使售票处不至出现找不开钱的局面的不同排队种数 。(约定:开始售票时售票处没有零钱;拿同样面值钞票的人对换位置为同一种排队。)

输入

输入 m,n(m 持有50元的人数,n持有100元的人数)

输出

m+n个人的排队种书

样例输入

1 1

样例输出

1
#include <iostream>
using namespace std;

int f(int m,int n)
{
    if(n==0){
        return 1;
    }
    else if(m<n){
        return 0;
    }
    else{
        return f(m,n-1)+f(m-1,n);
    }
}

int main()
{
    int m;int n;
    cin>>m>>n;
    cout<<f(m,n)<<endl;
    return 0;
}

09:输出不重复的数​​​​​​​

总时间限制: 

10000ms

内存限制: 

10000kB

描述

输入n个数,从小到大将他们输出,重复的数只输出一次

输入

输入两行。
第一行: n,代表要输入的数的个数
第二行: n个数

输出

从小到大输出不重复的数

样例输入

5
1 2 3 4 5

样例输出

1 2 3 4 5
#include <iostream>
#include <algorithm>
using namespace std;

int main()
{
    int n;
    cin>>n;
    int a[n];
    int i;
    for(i=0;i<n;i++)
        cin>>a[i];
    sort(a,a+n);
    cout<<a[0]<<" ";
    for(i=1;i<n;i++)
        if(a[i]!=a[i-1])
        cout<<a[i]<<" ";
    return 0;
}

10:全排列​​​​​​​(递归法)

总时间限制: 

65535ms

内存限制: 

65535kB

描述

输入一个n,输出1~n之间数的全排列(n<=5)

输入

n

输出

全排列

样例输入

3

样例输出

1 2 3
1 3 2
2 1 3
2 3 1
3 2 1
3 1 2
#include <iostream>
using namespace std;

void Perm(int list[],int k,int m)
{
    if(k==m)
    {
        for(int i=0;i<=m;i++)
            cout<<list[i]<<" ";
        cout<<endl;
    }
    else
        for(int j=k;j<=m;j++)
    {
        swap(list[k],list[j]);
        Perm(list,k+1,m);
        swap(list[k],list[j]);
    }
}

int main()
{
    int n;
    cin>>n;
    int list[]={1,2,3,4,5};
    Perm(list,0,n-1);
    return 0;
}

11:求n!(递归法)

可用下面的递归公式表示:

#include <iostream>
using namespace std;

int fac(int n)
{
    int f;
    if(n<0)
        cout<<"n<0,不满足阶乘存在条件";
    else if(n==0||n==1)
        f=1;
    else f=fac(n-1)*n;
    return(f);
}

int main()
{
    int n,y;
    cout<<"输入一个大于0的数n:";
    cin>>n;
    y=fac(n);
    cout<<n<<"的阶乘为:"<<y<<endl;
    return 0;
}

12:斐波那契数列(递归法)

用递归方法求菲波那契(Fibonacci)数列。

这个数列有如下特点:第1、2两个数为1、1。从第3个数开始,该数是其前面两个数之和。即:

#include <iostream>
using namespace std;

int fibonacci(int n)
{
    if(n==0||n==1)
        return 1;
    else
        return fibonacci(n-1)+fibonacci(n-2);
}

int main()
{
    int n,y;
    cout<<"输入一个大于等于0的数n:";
    cin>>n;
    y=fibonacci(n);
    cout<<n<<"的斐波那契数列为:"<<y<<endl;
    return 0;
}

13:猴子摘桃问题(递归法)

猴子第一天采摘了一些桃子,第二天吃了第一天的一半多一个,第三天吃了第二天的一半多一个...直到第十天就剩下一个。问:猴子第一天摘了多少桃子?

递推关系: f(n)=f(n-1)/2-1

f(n-1)=(f(n)+1)*2

边界条件:f(10)=1

#include <iostream>
using namespace std;

int func(int n)
{
    if(n==10)
        return 1;
    else
        return (func(n+1)+1)*2;
}

int main()
{
    int n;
    cout<<"输入摘桃的天数n:";
    cin>>n;
    cout<<"第"<<n<<"天有"<<func(n)<<"个桃子!"<<endl;
    return 0;
}

14:十进制转换为二进制(递归法)

#include <iostream>
using namespace std;

void db(int n)
{
    if(n==0)
        return ;
    else{
        db(n/2);
        cout<<n%2;
    }
}

int main()
{
    int n;
    cout<<"输入一个十进制数:";
    cin>>n;
    if(n==0)
        cout<<"0"<<endl;
    else{
        db(n);
    }
    cout<<endl;
    return 0;
}

15:逆序输出一个正数中的每一位数(递归法)

#include <iostream>
using namespace std;

int shuchu(int n)
{
    if(n/10==0){
        cout<<n;
    }
    else{
        cout<<n%10<<" ";
        shuchu(n/10);
    }
}

int main()
{
    int n;
    cout<<"输入一个正整数n:";
    cin>>n;
    shuchu(n);
    cout<<endl;
    return 0;
}

16:整数因子分解问题(分治法)

大于1的正整数n可以分解为: 当n=12时,共有8种不同的分解式:

对于给定的正整数n,编程计算n共有多少种不同的分解式。

#include <iostream>
using namespace std;

int total;
void solve(int n)
{
    if(n==1) total++;
    else
        for(int i=2;i<=n;i++)
        if(n%i==0)
        solve(n/i);
}

int main()
{
    int n;
    while(cin>>n)
    {
        total=0;
        solve(n);
        cout<<total;
    }
}

17:棋盘覆盖(分治法)

在一个2k×2k个方格组成的棋盘中,恰有一个方格与其他方格不同,称该方格为一特殊方格,且称该棋盘为一特殊棋盘。

在棋盘覆盖问题中,要用图示的4种不同形态的L型骨牌覆盖给定的特殊棋盘上除特殊方格以外的所有方格,且任何2个L型骨牌不得重叠覆盖。

#include <iostream>
using namespace std;

#define N 4
int tile=1;
int board[N][N];
void CB(int tr,int tc,int dr,int dc,int size)
{
    if(size==1) return;
    int t=tile++;//L型骨牌顺序号
    int s=size/2;//分割棋盘
    //处理左上角子棋盘
    if(dr<tr+s&&dc<tc+s)//特殊方格在此棋盘中
        CB(tr,tc,dr,dc,s);
    else{//用t号L型骨牌覆盖右下角
        board[tr+s-1][tc+s-1]=t;
        //覆盖其余方格
        CB(tr,tc,tr+s-1,tc+s-1,s);
    }
    //处理右上角子棋盘
    if(dr<tr+s&&dc>=tc+s)//特殊方格在此棋盘中
        CB(tr,tc+s,dr,dc,s);
    else{//此棋盘中无特殊方格
            //用t号L型骨牌覆盖左下角
        board[tr+s-1][tc+s]=t;
        //覆盖其余方格
        CB(tr,tc+s,tr+s-1,tc+s,s);
    }
    // 处理左下角子棋盘
    if (dr >= tr + s && dc < tc + s)// 特殊方格在此棋盘中
        CB(tr+s, tc, dr, dc, s);
    else {
        board[tr + s][tc + s - 1] = t;
    // 覆盖其余方格
        CB(tr+s, tc, tr+s, tc+s-1, s);
    }
    // 处理右下角子棋盘
    if (dr >= tr + s && dc >= tc + s)// 特殊方格在此棋盘中
        CB(tr+s, tc+s, dr, dc, s);
    else {
        board[tr + s][tc + s] = t;
        // 覆盖其余方格
        CB(tr+s, tc+s, tr+s, tc+s, s);
    }
}

int main()
{
    cout<<"输入棋盘k值:";
    int n,dr,dc;
    cin>>n;
    cout<<"构成一个2^"<<n<<"X2^"<<n<<"的矩阵";
    cout<<"输入特殊方格的行号和列号:";
    cin>>dr>>dc;
    for(int i=0;i<n;i++)
        for(int j=0;j<n;j++)
        board[i][j]=0;
    CB(0,0,dr,dc,2^n);
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<n;j++)
            cout<<board[i][j]<<" ";
        cout<<endl;
    }
}

18:找出n个元素中第k小的元素(分治法)

元素选择问题:给定线性序集中n个元素和一个整数k,1≤k≤n,要求找出这n个元素中第k小的元素。

对于给定的n个元素的数组a[0:n-1],要求从中找出第k小的元素。

输入:输入有多组测试例。 对每一个测试例有2行,第一行是整数n和k(1≤k<n≤1000),第二行是n个整数。

输出:第k小的元素。

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

int a[101];
int select(int left,int right,int k)
{
    if(left>=right)//递归出口
        return a[left];
    int i=left;int j=right+1;
    int p=a[left];//根据快速排序,最左边元素作为分界
    while(true)
    {
        do
        {
            i=i+1;
        }while(a[i]<p);//从左到右
        do
        {
            j=j-1;
        }while(a[j]>p);//从右到左
        if(i>=j)
            break;//一趟排序结束
        swap(a[i],a[j]);
    }
    if(j-left+1==k)//j-left+1 :左区元素的个数
        return p;
    a[left]=a[j];
    a[j]=p;
    if(j-left+1<k)//在右区域找
        return select(j+1,right,k-j+left-1);
    else
        return select(left,j-1,k);
}

int main()
{
    int n,k;//n个元素,找第k小的元素
    cin>>n>>k;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    //sort(a+1,a+n+1);
    cout<<select(1,n,k)<<endl;
    return 0;

}

  • 1
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值