Unity3D教程:遊戲開發算法(二)

4、遞歸

    遞歸是設計和描述算法的一種有力的工具,因爲它在複雜算法的描述中被常常採用,爲此在進一步介紹其餘算法設計方法以前先討論它。

    能採用遞歸描述的算法一般有這樣的特徵:爲求解規模爲N的問題,設法將它分解成規模較小的問題,而後從這些小問題的解方便地構造出大問題的解,而且這些規模較小的問題也能採用一樣的分解和綜合方法,分解成規模更小的問題,並從這些更小問題的解構造出規模較大問題的解。特別地,當規模N=1時,能直接得解。

    【問題】 編寫計算斐波那契(Fibonacci)數列的第n項函數fib(n)。

    斐波那契數列爲:0、一、一、二、三、……,即:

[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項。

    【問題】 組合問題

    問題描述:找出從天然數一、二、……、n中任取r個數的全部組合。例如n=5,r=3的全部組合爲: (1)五、四、3 (2)五、四、2 (3)五、四、1

    (4)五、三、2 (5)五、三、1 (6)五、二、1

    (7)四、三、2 (8)四、三、1 (9)四、二、1

    (10)三、二、1

    分析所列的10個組合,能夠採用這樣的遞歸思想來考慮求組合函數的算法。設函數爲void comb(int m,int k)爲找出從天然數一、二、……、m中任取k個數的全部組合。當組合的第一個數字選定時,其後的數字是從餘下的m-1個數中取k-1數的組合。這就將求m個數中取k個數的組合問題轉化成求m-1個數中取k-1個數的組合問題。設函數引入工做數組a[ ]存放求出的組合的數字,約定函數將肯定的k個數字組合的第一個數字放在a[k]中,當一個組合求出後,纔將a[ ]中的一個組合輸出。第一個數能夠是m、m-一、……、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、w一、…、wn-1,物品的價值分別爲v0、v一、…、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;
相關文章
相關標籤/搜索