SEx1p参考思路

7-1 正整数拆分


输入一个正整数n(2<=n<=10) , 求该数的所有不同拆分组合,要求拆分组合不能重复,例如下实例的拆分中2+4和4+2视为重复

类型1:输出划分的所有情况
本题目难度在于正整数拆分过程的输出。大部分同学都尝试了在拆分的过程中直接输出整数n,试想是否可以换成一种思路:用一个数组记录每次拆分的结果,在拆分过程中不需考虑输出,在单次拆分结束后统一输出。例如:

10=4+3+2+1  先用数组记录每次拆分的结果:array=[4,3,2,1]
10=1+1+1+1+1+1+1+1+1+1  每次拆分的结果:array=[1,1,1,1,1,1,1,1,1,1]

因此,问题就可以转换为如下两部分:

  1. 递归求取正整数拆分的数组表示。
  2. 对于每次求取的数组,进行格式化输出。

正向考虑正整数n的拆分有些困难,可以逆向思考:
假设不考虑n的大小,我们可以列举实现数组array=[n-1,n-1,…n-1,n-1,n-1]、[1,1,1,…,1,1,1]对应所有元素的和sum,判断sumn的大小即可,大小关系分为以下三种情况:

  1. sum>n:数组之和大于该正整数,例如n=6,且array=[3,2,2],说明该样例不符,直接退出。
  2. sum==n:数组之和等于该正整数,例如n=6,且array=[3,2,1],说明该样例符合,需要进行格式化输出。
  3. sum:数组之和小于该正整数,例如n=6,且array=[3,2],说明该样例数据较少,需要进行进一步递归增加样例。

针对以上分析,array和n可以设置为全局变量,确定递归函数divisionInteger()参数有以下几个:

  • array_len: 当前已经拆分的整数的个数,即当前记录的已经划分的数量
  • sum: 当前已经划分整数的总和
  • next_max_value: 根据样例输出可以看出,其划分的每项整数均不大于其前面一项(单调递减),因此需要一个参数记录并确定下一次递归时数组的最大值,该最大值不超过数组的最大值。

整理得,伪代码大致可以如下:

variable array[ARRAY_LENGTH];
integer n;
void divisionInteger(array_len, sum, next_max_value)
{
	if sum>n:
		return
	else if  sum==n:
		格式化输出n及数组
	else:
		for i in range(next_max_value, 1):
			array[array_len] = i		//记录第array_len个数为i
			divisionInteger(array_len+1, sum+i, i)	//由于array添加了一个数,因此sum也需要加上,由于该数组是单调递减的,因此下一个数值需要限定为不大于i
}

针对以上代码, 可以进一步尝试剪枝,不详细介绍,同学们可以自行尝试。

参考代码1:输出划分的所有情况

int mark[256];//记录每一种划分情况
int n;//输入数据
/*
outputDivision主要记录所有划分情况并输出
假设存在一个递增的整数数组array[]={0,1,2,...n}
sum:记录index总和
k:记录当前已经记录划分的数量
prio:从后到前记录已经遍历过的array对应的index
*/
void outputDivision(int sum, int k, int prio) {
	if(sum > n) {//退出条件
		return;
	} else if(sum == n) {//当sum(所有划分结果的总和)等于n时,需输出划分结果
		int i;
		if (mark[0]!=sum) {
                printf("%d=",n);
            for( i = 0; i < k-1; i++) {
                printf("%d+",mark[i]);
            }
            printf("%d\n",mark[i]);
		}
	} else {//sum小于n时,继续向mark数组中存储结果。
		for(int j = prio; j > 0; j--) {
			mark[k] = j;
			sum += j;
			outputDivision(sum,k+1,j);//对于每次遍历,需将sum增加对应的数值,以保证进行下一步递归,同时标记划分数量+1,prio参数从n->1进行
			sum -= j;//回复本递归信息
		}
	}
}

类型2:递归求整数划分个数
所谓整数划分:指把一个正整数n写成如下形式:

  • n=m1+m2+…+mi;(mi为正整数,并且1<=mi<=n),则{ m1,m2,…mi}为n的一个划分。
  • 如果{ m1,m2,…mi}中的最大值不超过m,即max(m1,m2,…,i)<=m,则称它属于n的一个m划分。这里我们记n的m划分的个数为f(n,m);
  • 例如n=4,它有4个划分,{3,1},{2,2},{2,1,1},{1,1,1,1};

该问题是求出n的所有划分个数,即f(n,n-1)。

下面考虑f(n,m)的方法:
根据n和m的关系,考虑有一下几种情况:

1.当n=2时,不论m的值为多少(m>0),只有一种划分:{1,1};

2.当m=1时,不论n的值为多少,也只有一种划分:n个1{1,1,1,1,1,1,…1};

3.当m=n-1时,可以分为两种情况:
(1)划分中包含n-1的情况:只有一种划分,即{n-1,1};
(2)划分中不包含n-1的情况,这时划分中最大的数字也比n-1小,即n的所有(n-1)划分,因此f(n,n-1)=1+f(n,n-2);

4.当m>n-1时,由于划分中不可能出现负数,因此相当于f(n,n-1);

5.当m (1)划分中包含m的情况,即{m,{x1,x2,…xi}},其中{x1,x2,…xi}的和为n-m;因此为f(n-m,m);
(2)划分中不包含m的情况,划分中所有值都比m小,即n的(m-1)划分,个数为f(n,m-1)
即f(n,m)=f(n-m,m)+f(n,m-1);

综上所述:

n=2 or m=1 时, f(n,m) = 1;
m>=n-1 时, f(n,n-1)
m

参考代码2:递归求整数划分个数

//整数划分的递归算法
int integerDivision(int n, int m) {
	if ((n<2) || (m<1)) return 0;
	if ((n == 2) || (m == 1)) return 1;
	if (m>=n-1) return integerDivision(n, n-1);
	return integerDivision(n - m, m) + integerDivision(n, m - 1);
}

7-2 求迷宫最短通道


递归求解迷宫最短通道的总步长。输入一个迷宫,求从入口通向出口的可行路径中最短的路径长度。

参考思路:
本题利用深度优先遍历(DFS),寻找到能到达终点的所有可能路径对应的长度。然后记录最短路径并输出。
DFS分析:主要考虑当前应该怎么做,每到一个点下一步有上下左右四种情况,我们按下左上右四个方向来进行计算,到新的一个点判断是否走过以及有没有障碍物,如果答案是确定的就可以继续DFS。

可编译参考代码:

#include
// 迷宫坐标位置类型
struct PosType
{
	int x; // 行值
	int y; // 列值
};
#define MAXLENGTH 10 // 设迷宫的最大行列为10
typedef int MazeType[MAXLENGTH][MAXLENGTH]; // [行][列]
// 全局变量
struct PosType end;	// 迷宫终点位置
MazeType m;			// 迷宫数组
int n;			// 迷宫行数,列数
int min_value = 10000;
// {行增量,列增量}
struct PosType direc[4]={{0,1},{1,0},{0,-1},{-1,0}};

// 由当前位置cur、当前步骤curstep试探下一点
//curstep表示当前已经尝试的步数
void Try(struct PosType cur,int curstep)
{
	int i;
	struct PosType next; // 下一个位置	
	// 移动方向,依次为东南西北
	for(i=0;i<=3;i++) // 依次试探下左上右四个方向
	{
		next.x=cur.x+direc[i].x;
		next.y=cur.y+direc[i].y;

		if(m[next.x][next.y] == 0) // 是通路
		{
			m[next.x][next.y]=++curstep;
			if(next.x != end.x || next.y != end.y) // 没到终点
				Try(next,curstep); // 试探下一点(递归调用)
			else
				min_value = min_value>curstep?curstep:min_value;
			m[next.x][next.y]=0; // 恢复为通路,试探下一条路
			curstep--;
		}
	}
}
int main()
{
	struct PosType begin;	//起点
	int i,j;
	while(scanf("%d",&n)!=EOF)
	  	for(i=0;i<n;i++)
	          for(j=0;j<n;j++)
	              scanf("%d",*(m+i)+j);//二维数组的指针调用
	  
	begin.x = 1,begin.y=1;
	end.x = n-2,end.y= n-2;
	m[begin.x][begin.y]=2;
	Try(begin,0); // 由第一步起点试探起
	if(min_value == 10000)
        printf("No solution");
	else
        printf("%d",min_value);
	return 0;
}

广度优先遍历(BFS)其实更适用于最短路径的搜索,DFS和BFS的区别请参考DFS与BFS的区别

参考非递归形式的BFS代码(暂不适用于本题目,仅供思路参考)


#include 
char a[100][100];//根据题意自己设定数据量 
int book[100][100];//标记某点是否到达 
int sx,sy,gx,gy;//起始点坐标 
struct node{
    int x;
    int y;
    int s;//s表示步数 
};
node q[10000];
int main(){
int head,tail;head=tail=1; //一开始清空队列     
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            scanf("%c",*(a+i)+j);
            if(a[i][j]=='S'){
                sx=i;sy=j;
            }
            if(a[i][j]=='G'){
                gx=i;gy=j;
            }
        }
    }
    int next[4][2]={{1,0},{0,1},{-1,0},{0,-1}};
    q[tail].x=sx;q[tail].y=sy;q[tail].s=0;//将起点数据压入队列
    tail++; book[sx][sy]=1;
    int flag=0;//flag是为了找到最短路径后跳出while循环用的 
    while(head<tail){
        for(int k=0;k<4;k++){
            int tx=q[head].x+next[k][0],ty=q[head].y+next[k][1];
            //判断是否越界
            if(tx<1||tx>n||ty<1||ty>m)
            continue;
            if(book[tx][ty]==0&&a[tx][ty]!='#'){
                q[tail].x=tx;q[tail].y=ty;q[tail].s=q[head].s+1;
                book[tx][ty]=1;tail++;
            }
            if(tx==gx&&ty==gy){
                flag=1;
                break;
            } 
        }
        if(flag)    break;
        head++;
    }
    printf("%d\n", q[tail-1].s);
    return 0;
}

你可能感兴趣的:(专题C,专题C)