栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Python

算法之一——递归

Python 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

算法之一——递归

递归是一种解决问题的方法,将问题分解为更小的子问题,直到得到一个足够小的问题可以被很简单的解决。通常递归涉及函数调用自身。递归允许我们编写优雅的解决方案,解决可能很难编程的问题。

例如,要求一个列表中所有数字的和,用循环可以写出以下代码:

def listsum(numList):
	theSum = 0
	for i in numList:
		theSum += i
	return theSum
print(listSum([1, 3, 5, 7, 9])

用循环解决的思路是((((1+3)+5)+7)+9),我们知道加法有结合律,所以也可以这样求:(1+(3+(5+(7+(9))))),这样做的目的是先将问题层层分解,直到分解成一个数字求和这样的简单问题,再从最内层将答案返回,拼凑出最初的问题的答案。递归方法的代码如下:

def listsum(numList):
    if len(numList) == 1:
        return numList[0]
    else:
        return numList[0]+listsum(numList[1:])
print(listsum([1, 3, 5, 7, 9]))

以上面这个问题为例,提出递归的三个定律:
1、 递归算法必须具有基本情况
基本情况是停止递归的条件,通常足够小以至于可以直接求解,比如列表求和问题中长度为1的列表。
2、递归算法必须改变其状态并向基本情况靠近
在调用函数的过程中,函数的使用的数据要向基本情况靠拢,在上述问题中基本情况是长度为1的列表,所以朝向基本情况的自然进展是缩短列表,使用numList[1:]生成一个更短的列表。
3、递归算的必须以递归方式调用自身
递归算法必须调用自身,这是由递归的定义决定的。

接下来就是用上面着三个原则解决经典的递归问题了,算法与语言关系不大,思路都是一样的,博主只会python和C++,就用这两种语言随机写了。

例1:将整数转换为任意进制(2-16)字符串
定义一个序列convString = ‘0123456789ABCDEF’
1、考虑基本情况,当将一个数a转化为b进制字符串,如果a 2、考虑将数字向基本情况靠近,即将数字减小到小于b,可以用减法和除法,一直给a除以b,记录下余数,余数肯定小于b,可以轻松转化,直到该数小于b,完成最后一次转化。将转化后的单个字符串逆序结合在一起就解决了原来的问题。

def toStr(n,base):
    converString = '0123456789ABCDEF'
    if(n 

例2:汉诺塔游戏
这是一个很经典的问题,所以问题描述就不写了,直接开始分析。原始塔称为A,中间塔称为B,目标塔称为C。
1、考虑基础情况,A塔只有一个盘,那么只需A->C一步就可以完成。
2、考虑向基本情况靠近,要想将n个盘子借助B从A移到C就需要先将n-1个盘子借助C从A移到B,再将A上剩的最后一个盘子移到C,最后再借助A将B上的n-1个盘子移到C。

按照上面的描述就可以写出程序,打印出移动盘子的顺序。

def moveTower(height, fromPole, toPole, withPole):
    if height >= 1:

        moveTower(height-1, fromPole, withPole, toPole)
        print("moving disk from", fromPole, "to", toPole)
        moveTower(height-1, withPole, toPole, fromPole)

moveTower(3, "A", "C", "B")
# include 
using namespace std;
void moveTower(int height, char fromPole, char toPole, char withPole){
    
    if (height>=1){
        moveTower(height-1, fromPole, withPole, toPole);
        cout << "moving disk from "< 

例3:n皇后问题
在国际象棋中,两个皇后若位于棋盘的同行、同列或同对角线上就会相互攻击。n皇后问题就是要求将n个皇后摆在n*n的棋盘上,且皇后之间不能相互攻击。

1、考虑基本情况,很明显,一行只能摆一个皇后,我们从第一行开始摆,当摆完第n行时就返回,这就是一个基本情况。
2、考虑向基本情况靠近,很简单,每摆好一行,行数就+1,直到行数=n。

N = 8    # 八皇后问题
queenPos = [0]*N    # 存放摆好的皇后的位置
def NQueen(k):      # 在0~k-1行的皇后摆好的情况下,摆第k行的皇后
    if k == N:    # 说明所有的皇后都已经摆完
        for i in range(N):
            print(queenPos[i]+1, end = ' ')    # 输出在每一行的第几列上摆皇后(从1开始)
        print("n")
        return       # 输出的每一行代表一种摆法,第i个数字为j表示在第i行第j个位置摆上皇后
    for i in range(N):      # 逐次尝试第k个皇后的位置
        chongtu = False
        for j in range(k):   # 逐次查看第i列的位置是否和前面的皇后冲突
            if queenPos[j] == i or abs(j-k)==abs(queenPos[j]-i):    # 如果和第j行的皇后冲突
                chongtu = True
                break     # 不用继续判断和其他的皇后冲不冲突了,直接检查下一个位置
        if chongtu==False:     # 如果没有冲突的
            queenPos[k] = i    # 确定位置
            NQueen(k+1)     # 再去摆下一行的皇后

NQueen(0)    # 从第0行开始摆皇后

例4:波兰(前缀)表达式求值
我们日常使用的表达式是中缀表达式,即运算符在数字的中间,这就需要运算具有优先级,并用小括号去改变运算顺序。波兰表达式就是前缀表达式,即运算符在要计算的数字前面,比如(2+3)x4的波兰表达式为x + 2 3 4,波兰表达式的计算没有优先级,也没有小括号,只需要遵循运算符在两个参与运算的数字前面(紧挨着)的规则就可以了。

1、思考基本情况,当波兰表达式只有一个数字时,直接就可以得到结果。
2、考虑向基本情况靠近,当遇到运算符号时,就表示有两个波兰表达式需要计算,再分别计算这两个波兰表达式,直到要计算的波兰表达式只有一个数字。

# include 
# include 
# include 
using namespace std;
double exp(){
    char s[20];
    cin>>s;
    switch (s[0])
    {
    case '+': return exp()+exp();
    case '-': return exp()-exp();
    case '*': return exp()*exp();
    case '/': return exp()/exp();
    default:  return atof(s);     // atof()将字符串转换成浮点型数
    break;
    }
}


int main(){
    printf("%1f", exp());
    return 0;
}

例5、中缀表达式求值
和波兰表达式求值相比,中缀表达式求值更加复杂,因为要考虑小括号和运算顺序。
1、基本情况也是只有一个数字的表达式。
2、拆解用运算符号连接的表达式,直到只剩一个数字。
需要注意的是,在中缀表达式中要先算乘除,所以设定表达式、项、因子,这三个概念方便理解。他们之间的关系如下:

# include 
# include 
# include 
using namespace std;

// 问题本身就是用递归定义的

int factor_value();  // 因子
int term_value();    // 项
int expression_value();  // 表达式

int main()
{
	cout << expression_value() << endl;
	return 0;
}

int expression_value() {	// 求一个表达式的值

	int result = term_value();   // 求第一项的值
	bool more = true;
	while (more) {
		char op = cin.peek();  // 看一个字符,不取走
		if (op == '+' || op == '-') {
			// 如果是+-,说明后面还有项
			cin.get(); // 从输入中取走一个字符
			int value = term_value();   // 计算项的值
			if (op == '+')    result += value;
			else  result -= value;
		}
		else more = false;
	}
	return result;
}


int term_value() {// 求一个项的值
	int result = factor_value(); // 求第一个因子的值
	while (true) {
		char op = cin.peek();
		if (op == '*' || op == '/') {
			cin.get();
			int value = factor_value();
			if (op == '*')  result *= value;
			else     result /= value;

		}
		else
			break;
	}
	return result;
}

int factor_value() {  // 求一个因子的值
	int result = 0;
	char c = cin.peek();
	if (c == '(') {
		cin.get();
		result = expression_value();
		cin.get();
	}
	else {
		while (isdigit(c)) {
			result = 10 * result + c - '0';
			cin.get();
			c = cin.peek();
		}
	}
	return result;
}

例6、爬楼梯
输入楼梯的级数n,每次可以爬一层或者两层,输出有几种不同的爬法。
1、考虑边界条件,n=0有1种爬法,n=1有1种爬法,n=2有2种爬法。
2、向边界条件靠近,n个台阶可以先爬一个,也可以先爬两个,f(n)=f(n-1)+f(n-2)。

# include 
using namespace std;
int N;
int stairs(int n){
    if (n==0)
    return 1;
    if(n==1)
    return 1;
    if(n==2)
    return 2;
    return stairs(n-1) + stairs(n-2);
}
int main(){
    while (cin>>N){
        cout< 

例7、摆苹果
把M个同样的苹果放到N个同样的盘子里,允许有空着的盘子,问有几种放法。5,1,1和1,5,1是同一种放法。
设把i个苹果放到k个盘子有f(i, k)种放法。
1、考虑边界情况,f(<=0, )=1,f( , <=0)=0。
2、向边界情况靠近:
(1)i>=k,苹果比盘子多(或者一样多),有两大种放法,第一种是每个盘子都有苹果的放法,先给每个盘子都放一个,有f(i-k, k)种放法。第二种是有空的盘子,有f(i, k-1)种放法。
(2)i

# include 
using namespace std;
int apple(int i, int k){
    if(i>i>>k){
        cout< 

例8、算24
输入四个小于10的正整数,输出这四个数是否能够通过加减乘除得到结果24,可以使用小括号。
1、边界条件,若是一个数计算24,只需要判断这个数是不是24就可以了。
2、向边界情况靠近,n个数算24,总有两个数要先参与运算,剩下n-2个数,这样就变成了n-1个数算24。

# include 
# include 
using namespace std;
double a[5];
# define EPS 1e-6
bool isZero(double x){
    return fabs(x)<=EPS; 
}
bool suan24(double a[], int n){  //用数组里的n个数计算24
    if(n==1){
        if(isZero(a[0]-24))
            return true;
        else
            return false;
    }
    double b[5];
    for(int i=0;i>a[i];
    if(suan24(a, 4))
        cout<<"yes"< 

总结

递归有三大作用:
(1)替代多种循环。本文的引例计算列表中的数字之和,例1转换进制就是这种情况,这种问题可以用递归解决也可以用单层循环解决,比较简单。例3的n皇后问题也可以用循环解决,但是需要n重循环,当n比较大的时候就很不方便,这时候就需要用递归代替多重循环。
(2)解决本来就是用递归形式定义的问题。本文的例4前缀表达式和例5中缀表达式的计算就是用递归形式定义的问题。
(3)将问题分解为规模更小的子问题进行求解。这种情况是最多的,可以将很多问题简单化,当面对一个复杂问题时,不妨考虑将其拆解,套用递归的两个特点,看是否能用递归解决。本文中的例2汉诺塔、例6爬楼梯、例7摆苹果、例8算24都是这种情况。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/269426.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号