数据结构系列--递归的实践
慧子和她的3D 慧子和她的3D 今天
“ 斐波拉契数列递归解法+Strlen递归解法+汉诺塔递归解法”
哪朵玫瑰 没有荆棘
最好的 报复是 美丽
最美的 盛开是 反击
别让谁去 改变了你
你是你 或是你 都行
会有人 全心的 爱你
绝地求生会激发更大的魅力
递归的数学思想
递归是一种数学上分而自治的思想。
递归将大型复杂问题转化为与原问题相同但规模较小的问题给进行处理
递归需要有边界条件:
当边界条件不满足时,递归继续进行
当边界条件满足时,递归停止
用递归解决问题首先要建立递归的模型
递归解法必修有边界条件,否则死循环
PART 1
—
01
—
斐波拉契数列递归解法
将求a(n)问题分解为求a(n-1)和a(n-2)。
#include <stdio.h>
int fibonacci(int n)
{
return fibonacci(n-1) + fibonacci(n-2);
else if(n == 1)
{
return 1;
}
else if(n == 0)
{
return 0;
}
}
int main()
{
int i= 0;
for(i=1;i<=10;i++)
{
printf("fibonacci(%d) = %d\n",i,fibonacci(i));
}
return 0;
}
02
—
Strlen递归解法
#include <stdio.h>
int strlen(const char* s)
{
if(s == NULL)
{
return -1;
}
else if(*s =='\0')
{
return 0;
}
else
{
return strlen(s+1)+1;
}
}
int main()
{
printf("strlen(\"12345\") = %d\n",strlen("12345"));
printf("strlen(NULL) = %d\n",strlen(NULL));
printf("strlen(\"\") = %d\n",strlen(""));
return 0;
}
03
—
汉诺塔递归解法
1 三个盘从a到c转化为将两个盘借助c到b
2 将a到c
3 两个b借助a移到c
#include <stdio.h>
void hanoi(int n,char a,char b,char c)
{
if(n =1)
{printf("%c->%c\n",a,c);}
else
{hanio(n-1,a,c,b);
printf("%c->%c\n",a,c,b);
hanio("%c->%c\n",b,a,c);}
}
int main()
{
hanio(8,'a','b','c');
return 0;
}
04
—
全排列递归解法
#include <stdio.h>
void permutation(char s,int b,int e)// 起始和终止
{
if(0 <= b) &&(b<=e)// 安全性保障
{
if(b = e)// 数组中只有自己,则全排列只有自己
{
printf("%s\n",s);
}
else
{
int i=0;
for(i = b;i<=e;i++)// 每个都有机会成为第一个
{// 将每一个轮流交换放到第一个不动
char c =s[b];
s[b] = s[i];
s[i] = c;
// 剩下的元素全排列
permutation(s,b+1,e);
// 交换回来添加第一个元素进行全排列
c =s[b];
s[b] = s[c];
s[c] = c;
}
}
}
}
int main()
{
char s[] = "a,b,c";
permutation(s,0,2);
return 0;
}
PART 2
—
递归与回溯
递归在程序设计中也常常用于需要回溯算法的场合(穷举搜索)
回溯算法的基本思想
从问题的某一种状态出发,搜索可以到达的所有状态,其中的某些状态是需要的;
当某个状态到达后,可提前回退,并继续搜索其他可达状态
当所有状态都达到后,回溯算法结束
程序设计中可利用函数的活动对象保存回溯算法的状态数据,因此可以利用递归完成回溯算法
八皇后问题
在一个8*8的国际棋盘上,有8个皇后,每个皇后占一格,要求皇后之间不会出现”攻击“现象,即不能有两个皇后出现在同一行,同一列,同一对角线上。
算法思路:
1 初始化:i=1;
2 初始化:j=1;
3 从第i行开始,恢复j的当前值,判断第j个位置
a 位置j可放入皇后:标记位置(i,j),i++,转至步骤2
b 位置j不可放入皇后:j++,转至步骤a
c 当j>8时,i--,转至步骤3
4 结束
第8行有位置可放入皇后
#include <stdio.h>
#define N 8;
// 偏移结构体
typedef struct _tag_Pos
{
int ios;
int jos;
}Pos;
// 模拟棋盘
static char board[N+2][N+2];
static Pos pos[] ={(-1,-1),(-1,0),(-1,1)};// 检测的三个方向组成数组
static int count = 0;
// 模拟棋盘
static char board[N+2][N+2];// 做一个边界
// 初始化棋盘
void init()
{
int i = 0;
int j = 0;
// 边界
for(i = 0;i<N+2;i++)
{
board[0][i] = '#';
board[N+1][i] = '#';
board[i][0] = '#';
board[i][N+1] = '#';
}
for(i=1;i<N;i++)
{
for(j=1;j<N;j++)
{
board[i][j] = ' ';// 空格表示可用
}
}
}
void display()
{
int i = 0;
int j = 0;
for(i=1;i<N+2;i++)
{
for(j=1;j<N+2;j++)
{
printf("%c",board[i][j]);// 二维数组逐个打印元素
}
printf("\n");// 每行打完空一行
}
}
int check(int i,int j)
{
int ret = 1;// 这个位置可以放皇后
int p = 0;
// 三个方向都没问题才可以放皇后
for (p = 0;p<3;p++)
{
int ni = i;
int nj = j;
while(ret && (board[ni][nj] ='#'))// 加上偏移量返回变量为真并且不为边界继续偏移
{
ni = ni + pos[p].ios;
nj = nj + pos[p].jos;
ret = ret && (board[ni][nj] !='*');// 没有皇后
}
}
return ret;
}
void find(int i)// 在函数调用的过程中局部变量全部保存在函数调用活动对象中
{
int j= 0;
if(i > N )// 大于边界,已经找完了
{
count++;// i > N表示第八行也可以放皇后,则这种状态可取
printf("solution : %d\n",count);
display();
getchar();// 执行暂停
}
else
{
for(j=1;j<N;j++)// 在第一行和第八行之间继续找
{
if(check(i,j))// 当前位置可以放
{
board[i][j] = '*' ;
find(i+1);// 放完找下一行
board[i][j] = ' ' ;// 找回递归调用之后不能放皇后清空
}
}
}
}
int main()
{
init();
find(1);
// display();
return 0 ;
}
小结:
回溯算法是递归应用的重要场合
利用函数调用的活动对象可以保存回溯算法中的重要变量信息
课后习题
1 permutation.c中的全排列·算法只能处理集合中的元素各不相同的情况。设计算法,使其能够对有重复的集合进行全排列。
tips:
2 编程查找所有迷宫从起点到终点的通路!
tips:与八皇后雷同,做四个方向偏移数组
可以用一个二维数组表示迷宫
元素1表示障碍
元素0表示可以通行
创建一个和迷宫等大小的二维数组作为标记数组
当标记数组位置为0且迷宫对应位置为0时,可通行
利用回溯法'探路'
当前路不可通行时,在标记数组对应位置标记1,并回溯