递归算法和过程的详解

看了很多博主的博文,加上看了几个视频,做了一些题目,感觉对递归有了一些认识,希望大家观看以后会对自己有所帮助。

递归的算法详解

  • 递归的定义

如果一个对象部分地由它自身组成或按它自己定义,则称它是递归的,所以说递归就是函数/过程/子过程在运行过程中直接或间接调用自身而产生的重入现象。

  • 递归算法包含的两个部分

1 由其自身定义的与原始问题类似的更小规模的子问题(只有数据规模不同),它使递归过程持续进行,称为一般条件。
2 所描述问题的最简单的情况,它是一个能控制递归过程结束的条件,称为基本条件。(递归出口)
在这里插入图片描述

  • 递归的基本思想

就是把一个规模大的问题分为若干个规模较小的子问题求解,而每一个子问题又可以分为几个规模更小的子问题。
基本上,所有的递归问题都可以用递推公式来表示。
最重要的一点就是假设子问题已经解决了,现在要基于已经解决的子问题来解决当前问题;
或者说,必须先解决子问题,再基于子问题来解决当前问题
或者可以这么理解:递归解决的是有依赖顺序关系的多个问题。

  • 递归的优缺点

优点
逻辑清楚,结构清晰,可读性好,代码简洁,效率高(拓展:DFS深度优先搜素,前中后序二叉树遍历)
缺点
函数调用开销大,空间复杂度高,有堆栈溢出的风险。

  • 什么样的问题可以用递归来求解?

1.问题的解可以分解为几个子问题的解。(子问题:数据规模更小的问题)
2.问题与子问题,除了数据规模不同,求解思路完全一样
3.存在递归终止条件

递归的过程详解

  • 递归的基本过程

首先,我们要了解递归具有堆栈的结构特性:先进后出
当我们在解决问题的过程中发现,这个问题的解,是依赖于一个规模更小的子问题的解,然后我们转向去求一个数据规模小,过程相同的子问题时,发现它的解依赖于规模更小的子问题,之后我们一步一步的求,突然发现最后的子问题的解可以直接求得(这便是递去的过程),然后用这一个子问题的解,来解决与其有关联的上一级子问题的解,最终求得目标问题的解(这便是归来的过程)。
转化为代码中的思想
函数执行到递归函数入口时,就扩充一段完全一样的代码,执行完扩充的代码并return后,继续执行前一次递归函数中递归函数入口后面的代码。

  • 递归中的变量的解释

递去时:每进行一次新的调用,都将创建一批变量,他们将掩盖递归函数前一次调用所创建的变量。可以看出每一级的函数调用都有自己的局部变量,当追踪一个递归函数的执行过程时,必须把不同次调用的变量区分开来,以避免混淆。
如果问题复杂时就有可能有堆栈溢出的风险。
归来时:开始打印输出。然后函数返回,并开始销毁堆栈上的变量值。

  • 关于递归的理解

阅读递归函数最容易的方法不是纠缠于它的执行过程,而是相信递归函数会顺利完成它的任务。如果你的每个步骤正确无误,你的限制条件设置正确,并且每次调用之后更接近限制条件,递归函数总是能正确的完成任务。

返回的过程是隐秘的,但是确实存在的过程。

关于递归的基本知识就这些,下面我们来结合一些代码更加深入的了解递归。

  • 汉诺塔问题(经典)

Description
汉诺塔(又称河内塔)问题是印度的一个古老的传说。

开天辟地的神勃拉玛在一个庙里留下了三根金刚石的棒A、B和C,A上面套着n个圆的金片,最大的一个在底下,其余一个比一个小,依次叠上去,庙里的众僧不倦地把它们一个个地从A棒搬到C棒上,规定可利用中间的一根B棒作为帮助,但每次只能搬一个,而且大的不能放在小的上面。

僧侣们搬得汗流满面,可惜当n很大时这辈子恐怕就很搬完了。

聪明的你还有计算机帮你完成,你能写一个程序帮助僧侣们完成这辈子的夙愿吗?
Input
输入金片的个数n。这里的n<=10。
Output
输出搬动金片的全过程。格式见样例。
Sample
Input
2
Output
Move disk 1 from A to B
Move disk 2 from A to C
Move disk 1 from B to C
Hint
可以用递归算法实现。

第一步:将问题简化–复杂问题求解的基本方法
现在假设A杆上,只有2个圆盘,即汉诺塔有两层,n=2;求解过程如下:

在这里插入图片描述

第二步:对于n(n>1)个圆盘的汉诺塔,将n个圆盘分为两部分,“上面n-1个圆盘”看成一个整体(也就是上图中画斜线的部分)
所以就可以写出代码:

#include <iostream>

using namespace std;

void carry(int n,char a/*起始柱*/,char b/*中间柱*/,char c/*目标柱*/){
	if(n==1) printf("Move disk %d from %c to %c\n",n,a,c);  //结束条件:子问题中将起始柱上的第一个盘子移动到目标柱上 注意当前的a和c不一定代表A柱子和C柱子 
	else{
		carry(n-1,a,c,b);  //将起始柱上的n-1个盘子移动到中间柱子上    1
		printf("Move disk %d from %c to %c\n",n,a,c);
		carry(n-1,b,a,c);  //将中间柱子上的n-1个盘子移动到目标柱子上 
	}
}
int main()
{
	int n;
	cin>>n;
	carry(n,'A','B','C');
	return 0;
 } 

所谓快速排序,效率很快,基本思想,两边交替向中间遍历,把大的放一边,小的放一边,再利用递归最终实现排序。

#include <iostream>

using namespace std;
const int N=1e5+10;
int a[N];

void q_sort(int l,int r){
	int i=l;
	int j=r;
	int k=a[i];
	if(l>=r)  return ;   //如果左边界大于右边界返回空 
	else{
		while(i<j){
			while(i<j&&a[j]>=k)j--;   //发现一个大于k的值就让该值放在之前k的位置 
			a[i]=a[j];
			while(i<j&&a[i]<=k)i++;
			a[j]=a[i];
		}
		a[i]=k;         //把之前提取出来的一个值放到中间 
		q_sort(l,i-1);   //重复这个过程 
		q_sort(i+1,r);
	}
}

void print(int n)
{
	for(int i=0;i<n;i++){
		if(i==n-1) cout<<a[i]<<endl;
	    else cout<<a[i]<<" ";
	}
}

int main()
{
	int n;
	cin>>n;
	for(int i=0;i<n;i++)
	{
		cin>>a[i];
	}
	q_sort(0,n-1);
	print(n);
	
	return 0;
}

二分查找和快排有一些相同的地方,二分查找把要查找的数与数列中中间值进行比较,从而每一次缩小一半。
注意二分查找的序列一定时有序的。

#include <iostream>

using namespace std;
const int N=1e7+10;
int a[N];

int search(int l,int r,int x){
	int mid;
	mid=(l+r)>>1;
	while(l<=r){
		if(a[mid]==x) return mid;
		else if(a[mid]>x) return search(l,mid-1,x);
		else if(a[mid]<x) return search(mid+1,r,x);
	}
	return -1;
}

int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	int q;
	int x;
	cin>>q;
	while(q--){
		cin>>x;
		cout<<search(1,n,x)<<endl;
	}
	return 0;
 } 
#include <iostream>
#include<bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
const int N = 1e5 + 10;
const int M = 111;
using namespace std;

int a[11];

///右移元素
void move_r(int l,int r){
    if(l>=r) return ;
    int t=a[r];
    for(int i=r;i>l;i--) a[i]=a[i-1];
    a[l]=t;
}

///左移元素
void move_l(int l,int r){
    if(l>=r) return ;
    int t=a[l];
    for(int i=l;i<r;i++) a[i]=a[i+1];
    a[r]=t;
}

///递归全排列
void perm(int l,int r){
    if(l==r){
        for(int i=1;i<=r;i++){
            if(i==r) cout<<a[i]<<endl;
            else cout<<a[i]<<",";
        }
    }
    else{
        for(int i=l;i<=r;i++){
            swap(a[l],a[i]);
            move_r(l+1,i);
            perm(l+1,r);
            move_l(l+1,i);
            swap(a[l],a[i]);
        }
    }
}

int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    perm(1,n);
    return 0;
}


提升:八皇后问题

  • 结语
    我意识到用书面语讲这些题非常的麻烦,而且大家可能也听不懂,所以我就详细的介绍了基本的知识,至于题目,我只是稍微讲了一点思路,加上我给出的代码,大家自己思考出来的效果肯定会更好。
  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值