Unity3D教程:游戏开发算法(二)

四、递归

    递归是设计和描述算法的一种有力的工具,由于它在复杂算法的描述中被经常采用,为此在进一步介绍其他算法设计方法之前先讨论它。

    能采用递归描述的算法通常有这样的特征:为求解规模为N的问题,设法将它分解成规模较小的问题,然后从这些小问题的解方便地构造出大问题的解,并且这些规模较小的问题也能采用同样的分解和综合方法,分解成规模更小的问题,并从这些更小问题的解构造出规模较大问题的解。特别地,当规模N=1时,能直接得解。

    【问题】 编写计算斐波那契(Fibonacci)数列的第n项函数fib(n)。

    斐波那契数列为:0、1、1、2、3、……,即:

[AppleScript]  纯文本查看 复制代码
?
1
2
3
4
5
6
7
fib ( 0 ) = 0 ;
 
  
  fib ( 1 ) = 1 ;
 
  
  fib ( n ) = fib ( n -1 ) + fib ( n -2 ) (当n > 1 时)。


    写成递归函数有:

[AppleScript]  纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
int fib ( int n )
 
  
   { if ( n = = 0 ) return 0 ;
 
  
   if ( n = = 1 ) return 1 ;
 
  
   if ( n > 1 ) return fib ( n -1 ) + fib ( n -2 ) ;
 
  
   }


    递归算法的执行过程分递推和回归两个阶段。在递推阶段,把较复杂的问题(规模为n)的求解推到比原问题简单一些的问题(规模小于n)的求解。例如上例中,求解fib(n),把它推到求解fib(n-1)和fib(n-2)。也就是说,为计算fib(n),必须先计算fib(n-1)和fib(n-2),而计算fib(n-1)和fib(n-2),又必须先计算fib(n-3)和fib(n-4)。依次类推,直至计算fib(1)和fib(0),分别能立即得到结果1和0。在递推阶段,必须要有终止递归的情况。例如在函数fib中,当n为1和0的情况。

    在回归阶段,当获得最简单情况的解后,逐级返回,依次得到稍复杂问题的解,例如得到fib(1)和fib(0)后,返回得到fib(2)的结果,在得到了fib(n-1)和fib(n-2)的结果后,返回得到fib(n)的结果。

    在编写递归函数时要注意,函数中的局部变量和参数知识局限于当前调用层,当递推进入“简单问题”层时,原来层次上的参数和局部变量便被隐蔽起来。在一系列“简单问题”层,它们各有自己的参数和局部变量。

    由于递归引起一系列的函数调用,并且可能会有一系列的重复计算,递归算法的执行效率相对较低。当某个递归算法能较方便地转换成递推算法时,通常按递推算法编写程序。例如上例计算斐波那契数列的第n项的函数fib(n)应采用递推算法,即从斐波那契数列的前两项出发,逐次由前两项计算出下一项,直至计算出要求的第n项。

    【问题】 组合问题

    问题描述:找出从自然数1、2、……、n中任取r个数的所有组合。例如n=5,r=3的所有组合为: (1)5、4、3 (2)5、4、2 (3)5、4、1

    (4)5、3、2 (5)5、3、1 (6)5、2、1

    (7)4、3、2 (8)4、3、1 (9)4、2、1

    (10)3、2、1

    分析所列的10个组合,可以采用这样的递归思想来考虑求组合函数的算法。设函数为void comb(int m,int k)为找出从自然数1、2、……、m中任取k个数的所有组合。当组合的第一个数字选定时,其后的数字是从余下的m-1个数中取k-1数的组合。这就将求m个数中取k个数的组合问题转化成求m-1个数中取k-1个数的组合问题。设函数引入工作数组a[ ]存放求出的组合的数字,约定函数将确定的k个数字组合的第一个数字放在a[k]中,当一个组合求出后,才将a[ ]中的一个组合输出。第一个数可以是m、m-1、……、k,函数将确定组合的第一个数字放入数组后,有两种可能的选择,因还未去顶组合的其余元素,继续递归去确定;或因已确定了组合的全部元素,输出这个组合。细节见以下程序中的函数comb。

    【程序】
[AppleScript]  纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# include
 
 
# define MAXN 100
 
 
int a[MAXN];
 
 
void comb ( int m , int k )
 
 
{ int i , j;
 
 
for ( i = m;i > = k;i --)
 
 
{ a[k] = i;
 
 
if ( k > 1 )
 
 
comb ( i -1 , k -1 ) ;
 
 
else
 
 
{ for ( j = a[ 0 ];j > 0 ;j --)
 
 
printf ( “% 4 d” , a[j] ) ;
 
 
printf ( “\n” ) ;
 
 
}
 
 
}
 
 
}
 
 
void main ( )
 
 
{ a[ 0 ] = 3 ;
 
 
comb ( 5 , 3 ) ;
 
 
}


    【问题】 背包问题

    问题描述:有不同价值、不同重量的物品n件,求从这n件物品中选取一部分物品的选择方案,使选中物品的总重量不超过指定的限制重量,但选中物品的价值之和最大。

    设n件物品的重量分别为w0、w1、…、wn-1,物品的价值分别为v0、v1、…、vn-1。采用递归寻找物品的选择方案。设前面已有了多种选择的方案,并保留了其中总价值最大的方案于数组option[ ],该方案的总价值存于变量maxv。当前正在考察新方案,其物品选择情况保存于数组cop[ ]。假定当前方案已考虑了前i-1件物品,现在要考虑第i件物品;当前方案已包含的物品的重量之和为tw;至此,若其余物品都选择是可能的话,本方案能达到的总价值的期望值为tv。算法引入tv是当一旦当前方案的总价值的期望值也小于前面方案的总价值maxv时,继续考察当前方案变成无意义的工作,应终止当前方案,立即去考察下一个方案。因为当方案的总价值不比maxv大时,该方案不会被再考察,这同时保证函数后找到的方案一定会比前面的方案更好。

    对于第i件物品的选择考虑有两种可能:

    (1) 考虑物品i被选择,这种可能性仅当包含它不会超过方案总重量限制时才是可行的。选中后,继续递归去考虑其余物品的选择。

    (2) 考虑物品i不被选择,这种可能性仅当不包含物品i也有可能会找到价值更大的方案的情况。

    按以上思想写出递归算法如下:

[AppleScript]  纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
try ( 物品i,当前选择已达到的重量和,本方案可能达到的总价值tv )
 
 
{ / * 考虑物品i包含在当前方案中的可能性 * /
 
 
if ( 包含物品i是可以接受的 )
 
 
{ 将物品i包含在当前方案中;
 
 
if ( i   try ( i + 1 , tw + 物品i的重量 , tv ) ;
 
 
else
 
 
/ * 又一个完整方案,因为它比前面的方案好,以它作为最佳方案 * /
 
 
以当前方案作为临时最佳方案保存;
 
 
恢复物品i不包含状态;
 
 
}
 
 
/ * 考虑物品i不包含在当前方案中的可能性 * /
 
 
if ( 不包含物品i仅是可男考虑的 )
 
 
if ( i   try ( i + 1 , tw , tv - 物品i的价值 )
 
 
else
 
 
/ * 又一个完整方案,因它比前面的方案好,以它作为最佳方案 * /
 
 
以当前方案作为临时最佳方案保存;
 
 
}


    为了理解上述算法,特举以下实例。设有4件物品,它们的重量和价值见表:

    物品 0 1 2 3

    重量 5 3 2 1

    价值 4 4 3 1

    并设限制重量为7。则按以上算法,下图表示找解过程。由图知,一旦找到一个解,算法就进一步找更好的佳。如能判定某个查找分支不会找到更好的解,算法不会在该分支继续查找,而是立即终止该分支,并去考察下一个分支。

    按上述算法编写函数和程序如下:

    【程序】

[AppleScript]  纯文本查看 复制代码
?
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# include
 
 
# define N 100
 
 
double limitW , totV , maxV;
 
 
int option[N] , cop[N];
 
 
struct { double weight;
 
 
double value ;
 
 
} a[N];
 
 
int n;
 
 
void find ( int i , double tw , double tv )
 
 
{ int k;
 
 
/ * 考虑物品i包含在当前方案中的可能性 * /
 
 
if ( tw + a[i].weight < = limitW )
 
 
{ cop[i] = 1 ;
 
 
[ / i][ / i] if ( i   else
 
 
{ for ( k = 0 ;k   option[k] = cop[k];
 
 
maxv = tv;
 
 
}
 
 
cop = 0 ;
 
 
}
 
 
/ * 考虑物品i不包含在当前方案中的可能性 * /
 
 
if ( tv - a. value > maxV )
 
 
if ( i   else
 
 
{ for ( k = 0 ;k   option[k] = cop[k];
 
 
maxv = tv - a. value ;
 
 
}
 
 
}
 
 
void main ( )
 
 
{ int k;
 
 
double w , v;
 
 
printf ( “输入物品种数\n” ) ;
 
 
scanf ( ( “%d” , & n ) ;
 
 
printf ( “输入各物品的重量和价值\n” ) ;
 
 
for ( totv = 0.0 , k = 0 ;k   { scanf ( “% 1 f% 1 f” , & w , & v ) ;
 
 
a[k].weight = w;
 
 
a[k]. value = v;
 
 
totV + = V;
 
 
}
 
 
printf ( “输入限制重量\n” ) ;
 
 
scanf ( “% 1 f” , & limitV ) ;
 
 
maxv = 0.0 ;
 
 
for ( k = 0 ;k   find ( 0 , 0.0 , totV ) ;
 
 
for ( k = 0 ;k   if ( option[k] ) printf ( “% 4 d” , k + 1 ) ;
 
 
printf ( “\n总价值为%. 2 f\n” , maxv ) ;
 
 
}


    作为对比,下面以同样的解题思想,考虑非递归的程序解。为了提高找解速度,程序不是简单地逐一生成所有候选解,而是从每个物品对候选解的影响来形成值得进一步考虑的候选解,一个候选解是通过依次考察每个物品形成的。对物品i的考察有这样几种情况:当该物品被包含在候选解中依旧满足解的总重量的限制,该物品被包含在候选解中是应该继续考虑的;反之,该物品不应该包括在当前正在形成的候选解中。同样地,仅当物品不被包括在候选解中,还是有可能找到比目前临时最佳解更好的候选解时,才去考虑该物品不被包括在候选解中;反之,该物品不包括在当前候选解中的方案也不应继续考虑。对于任一值得继续考虑的方案,程序就去进一步考虑下一个物品。

    【程序】

[AppleScript]  纯文本查看 复制代码
?
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# include
 
 
  # define N 100
 
 
 double limitW;
 
 
 int cop[N];
 
 
 struct ele { double weight;
 
 
 double value ;
 
 
  } a[N];
 
 
 int k , n;
 
 
 struct { int ;
 
 
 double tw;
 
 
 double tv;
 
 
  } twv[N];
 
 
 void next ( int i , double tw , double tv )
 
 
  { twv. = 1 ;
 
 
 twv.tw = tw;
 
 
 twv.tv = tv;
 
 
  }
 
 
 double find ( struct ele * a , int n )
 
 
  { int i , k , f;
 
 
 double maxv , tw , tv , totv;
 
 
 maxv = 0 ;
 
 
  for ( totv = 0.0 , k = 0 ;k   totv + = a[k]. value ;
 
 
 next ( 0 , 0.0 , totv ) ;
 
 
 i = 0 ;
 
 
 While ( i > = 0 )
 
 
  { f = twv.;
 
 
 tw = twv.tw;
 
 
 tv = twv.tv;
 
 
 switch ( f )
 
 
  { case 1 : twv. + + ;
 
 
  if ( tw + a.weight < = limitW )
 
 
  if ( i   { next ( i + 1 , tw + a.weight , tv ) ;
 
 
 i + + ;
 
 
  }
 
 
  else
 
 
  { maxv = tv;
 
 
  for ( k = 0 ;k   cop[k] = twv[k].! = 0 ;
 
 
  }
 
 
 break;
 
 
  case 0 : i --;
 
 
 break;
 
 
 default : twv. = 0 ;
 
 
  if ( tv - a. value > maxv )
 
 
  if ( i   { next ( i + 1 , tw , tv - a. value ) ;
 
 
 i + + ;
 
 
  }
 
 
  else
 
 
  { maxv = tv - a. value ;
 
 
  for ( k = 0 ;k   cop[k] = twv[k].! = 0 ;
 
 
  }
 
 
 break;
 
 
  }
 
 
  }
 
 
  return maxv;
 
 
  }
 
 
 void main ( )
 
 
  { double maxv;
 
 
 printf ( “输入物品种数\n” ) ;
 
 
 scanf ( ( “%d” , & n ) ;
 
 
 printf ( “输入限制重量\n” ) ;
 
 
 scanf ( “% 1 f” , & limitW ) ;
 
 
 printf ( “输入各物品的重量和价值\n” ) ;
 
 
  for ( k = 0 ;k   scanf ( “% 1 f% 1 f” , & a[k].weight , & a[k]. value ) ;
 
 
 maxv = find ( a , n ) ;
 
 
 printf ( “\n选中的物品为\n” ) ;
 
 
  for ( k = 0 ;k   if ( option[k] ) printf ( “% 4 d” , k + 1 ) ;
 
 
 printf ( “\n总价值为%. 2 f\n” , maxv ) ;
 
 
  }


    五、回溯法

    回溯法也称为试探法,该方法首先暂时放弃关于问题规模大小的限制,并将问题的候选解按某种顺序逐一枚举和检验。当发现当前候选解不可能是解时,就选择下一个候选解;倘若当前候选解除了还不满足问题规模要求外,满足所有其他要求时,继续扩大当前候选解的规模,并继续试探。如果当前候选解满足包括问题规模在内的所有要求时,该候选解就是问题的一个解。在回溯法中,放弃当前候选解,寻找下一个候选解的过程称为回溯。扩大当前候选解的规模,以继续试探的过程称为向前试探。

    1、回溯法的一般描述

    可用回溯法求解的问题P,通常要能表达为:对于已知的由n元组(x1,x2,…,xn)组成的一个状态空间E={(x1,x2,…,xn)∣xi∈Si ,i=1,2,…,n},给定关于n元组中的一个分量的一个约束集D,要求E中满足D的全部约束条件的所有n元组。其中Si是分量xi的定义域,且 |Si| 有限,i=1,2,…,n。我们称E中满足D的全部约束条件的任一n元组为问题P的一个解。

    解问题P的最朴素的方法就是枚举法,即对E中的所有n元组逐一地检测其是否满足D的全部约束,若满足,则为问题P的一个解。但显然,其计算量是相当大的。

    我们发现,对于许多问题,所给定的约束集D具有完备性,即i元组(x1,x2,…,xi)满足D中仅涉及到x1,x2,…,xi的所有约束意味着j(jj。因此,对于约束集D具有完备性的问题P,一旦检测断定某个j元组(x1,x2,…,xj)违反D中仅涉及x1,x2,…,xj的一个约束,就可以肯定,以(x1,x2,…,xj)为前缀的任何n元组(x1,x2,…,xj,xj+1,…,xn)都不会是问题P的解,因而就不必去搜索它们、检测它们。回溯法正是针对这类问题,利用这类问题的上述性质而提出来的比枚举法效率更高的算法。

    回溯法首先将问题P的n元组的状态空间E表示成一棵高为n的带权有序树T,把在E中求问题P的所有解转化为在T中搜索问题P的所有解。树T类似于检索树,它可以这样构造:

    设Si中的元素可排成xi(1) ,xi(2) ,…,xi(mi-1) ,|Si| =mi,i=1,2,…,n。从根开始,让T的第I层的每一个结点都有mi个儿子。这mi个儿子到它们的双亲的边,按从左到右的次序,分别带权xi+1(1) ,xi+1(2) ,…,xi+1(mi) ,i=0,1,2,…,n-1。照这种构造方式,E中的一个n元组(x1,x2,…,xn)对应于T中的一个叶子结点,T的根到这个叶子结点的路径上依次的n条边的权分别为x1,x2,…,xn,反之亦然。另外,对于任意的0≤i≤n-1,E中n元组(x1,x2,…,xn)的一个前缀I元组(x1,x2,…,xi)对应于T中的一个非叶子结点,T的根到这个非叶子结点的路径上依次的I条边的权分别为x1,x2,…,xi,反之亦然。特别,E中的任意一个n元组的空前缀(),对应于T的根。

    因而,在E中寻找问题P的一个解等价于在T中搜索一个叶子结点,要求从T的根到该叶子结点的路径上依次的n条边相应带的n个权x1,x2,…,xn满足约束集D的全部约束。在T中搜索所要求的叶子结点,很自然的一种方式是从根出发,按深度优先的策略逐步深入,即依次搜索满足约束条件的前缀1元组(x1i)、前缀2元组(x1,x2)、…,前缀I元组(x1,x2,…,xi),…,直到i=n为止。

    在回溯法中,上述引入的树被称为问题P的状态空间树;树T上任意一个结点被称为问题P的状态结点;树T上的任意一个叶子结点被称为问题P的一个解状态结点;树T上满足约束集D的全部约束的任意一个叶子结点被称为问题P的一个回答状态结点,它对应于问题P的一个解。

    【问题】 组合问题

    问题描述:找出从自然数1、2、……、n中任取r个数的所有组合。

    例如n=5,r=3的所有组合为:

    (1)1、2、3 (2)1、2、4 (3)1、2、5

    (4)1、3、4 (5)1、3、5 (6)1、4、5

    (7)2、3、4 (8)2、3、5 (9)2、4、5

    (10)3、4、5

    则该问题的状态空间为:

    E={(x1,x2,x3)∣xi∈S ,i=1,2,3 } 其中:S={1,2,3,4,5}

    约束集为: x1   显然该约束集具有完备性。

    问题的状态空间树T:

    2、回溯法的方法

    对于具有完备约束集D的一般问题P及其相应的状态空间树T,利用T的层次结构和D的完备性,在T中搜索问题P的所有解的回溯法可以形象地描述为:

    从T的根出发,按深度优先的策略,系统地搜索以其为根的子树中可能包含着回答结点的所有状态结点,而跳过对肯定不含回答结点的所有子树的搜索,以提高搜索效率。具体地说,当搜索按深度优先策略到达一个满足D中所有有关约束的状态结点时,即“激活”该状态结点,以便继续往深层搜索;否则跳过对以该状态结点为根的子树的搜索,而一边逐层地向该状态结点的祖先结点回溯,一边“杀死”其儿子结点已被搜索遍的祖先结点,直到遇到其儿子结点未被搜索遍的祖先结点,即转向其未被搜索的一个儿子结点继续搜索。

    在搜索过程中,只要所激活的状态结点又满足终结条件,那么它就是回答结点,应该把它输出或保存。由于在回溯法求解问题时,一般要求出问题的所有解,因此在得到回答结点后,同时也要进行回溯,以便得到问题的其他解,直至回溯到T的根且根的所有儿子结点均已被搜索过为止。

    例如在组合问题中,从T的根出发深度优先遍历该树。当遍历到结点(1,2)时,虽然它满足约束条件,但还不是回答结点,则应继续深度遍历;当遍历到叶子结点(1,2,5)时,由于它已是一个回答结点,则保存(或输出)该结点,并回溯到其双亲结点,继续深度遍历;当遍历到结点(1,5)时,由于它已是叶子结点,但不满足约束条件,故也需回溯。

    3、回溯法的一般流程和技术

    在用回溯法求解有关问题的过程中,一般是一边建树,一边遍历该树。在回溯法中我们一般采用非递归方法。下面,我们给出回溯法的非递归算法的一般流程:

    在用回溯法求解问题,也即在遍历状态空间树的过程中,如果采用非递归方法,则我们一般要用到栈的数据结构。这时,不仅可以用栈来表示正在遍历的树的结点,而且可以很方便地表示建立孩子结点和回溯过程。

    例如在组合问题中,我们用一个一维数组Stack[ ]表示栈。开始栈空,则表示了树的根结点。如果元素1进栈,则表示建立并遍历(1)结点;这时如果元素2进栈,则表示建立并遍历(1,2)结点;元素3再进栈,则表示建立并遍历(1,2,3)结点。这时可以判断它满足所有约束条件,是问题的一个解,输出(或保存)。这时只要栈顶元素(3)出栈,即表示从结点(1,2,3)回溯到结点(1,2)。

    【问题】 组合问题

    问题描述:找出从自然数1,2,…,n中任取r个数的所有组合。

    采用回溯法找问题的解,将找到的组合以从小到大顺序存于a[0],a[1],…,a[r-1]中,组合的元素满足以下性质:

    (1) a[i+1]>a,后一个数字比前一个大;

    (2) a-i<=n-r+1。

    按回溯法的思想,找解过程可以叙述如下:

    首先放弃组合数个数为r的条件,候选组合从只有一个数字1开始。因该候选解满足除问题规模之外的全部条件,扩大其规模,并使其满足上述条件(1),候选组合改为1,2。继续这一过程,得到候选组合1,2,3。该候选解满足包括问题规模在内的全部条件,因而是一个解。在该解的基础上,选下一个候选解,因a[2]上的3调整为4,以及以后调整为5都满足问题的全部要求,得到解1,2,4和1,2,5。由于对5不能再作调整,就要从a[2]回溯到a[1],这时,a[1]=2,可以调整为3,并向前试探,得到解1,3,4。重复上述向前试探和向后回溯,直至要从a[0]再回溯时,说明已经找完问题的全部解。按上述思想写成程序如下:

    【程序】

[AppleScript]  纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# define MAXN 100
 
 
 int a[MAXN];
 
 
 void comb ( int m , int r )
 
 
  { int i , j;
 
 
 i = 0 ;
 
 
 a = 1 ;
 
 
 do {
 
 
  if ( a - i < = m - r + 1
 
 
  { if ( i = = r -1 )
 
 
  { for ( j = 0 ;j   printf ( “% 4 d” , a[j] ) ;
 
 
 printf ( “\n” ) ;
 
 
  }
 
 
 a + + ;
 
 
  continue ;
 
 
  }
 
 
  else
 
 
  { if ( i = = 0 )
 
 
  return ;
 
 
 a[ --i]++;
 
 
  }
 
 
  } while ( 1 )
 
 
  }
 
 
  main ( )
 
 
  { comb ( 5 , 3 ) ;
 
 
  }


    【问题】 填字游戏

    问题描述:在3×3个方格的方阵中要填入数字1到N(N≥10)内的某9个数字,每个方格填一个整数,似的所有相邻两个方格内的两个整数之和为质数。试求出所有满足这个要求的各种数字填法。

    可用试探发找到问题的解,即从第一个方格开始,为当前方格寻找一个合理的整数填入,并在当前位置正确填入后,为下一方格寻找可填入的合理整数。如不能为当前方格找到一个合理的可填证书,就要回退到前一方格,调整前一方格的填入数。当第九个方格也填入合理的整数后,就找到了一个解,将该解输出,并调整第九个的填入的整数,寻找下一个解。// unity 3d 教程手册: www.unitymanual.com

    为找到一个满足要求的9个数的填法,从还未填一个数开始,按某种顺序(如从小到大的顺序)每次在当前位置填入一个整数,然后检查当前填入的整数是否能满足要求。在满足要求的情况下,继续用同样的方法为下一方格填入整数。如果最近填入的整数不能满足要求,就改变填入的整数。如对当前方格试尽所有可能的整数,都不能满足要求,就得回退到前一方格,并调整前一方格填入的整数。如此重复执行扩展、检查或调整、检查,直到找到一个满足问题要求的解,将解输出。

    回溯法找一个解的算法:

[AppleScript]  纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{ int m = 0 , ok = 1 ;
 
  
  int n = 8 ;
 
  
  do {
 
  
   if ( ok ) 扩展;
 
  
   else 调整;
 
  
  ok = 检查前m个整数填放的合理性;
 
  
   } while ( ( !ok||m! = n ) & & ( m! = 0 ) )
 
  
   if ( m! = 0 ) 输出解;
 
  
   else 输出无解报告;
 
  
   }


    如果程序要找全部解,则在将找到的解输出后,应继续调整最后位置上填放的整数,试图去找下一个解。相应的算法如下:

    回溯法找全部解的算法:

[AppleScript]  纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
{ int m = 0 , ok = 1 ;
 
  
  int n = 8 ;
 
  
  do {
 
  
   if ( ok )
 
  
   { if ( m = = n )
 
  
   { 输出解;
 
  
  调整;
 
  
   }
 
  
   else 扩展;
 
  
   }
 
  
   else 调整;
 
  
  ok = 检查前m个整数填放的合理性;
 
  
   } while ( m! = 0 ) ;
 
  
   }
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值