master
/ 4.5 递归.ipynb

4.5 递归.ipynb @master

341084b
 
 
 
 
 
 
 
 
 
 
 
 
d487d71
341084b
 
 
 
 
 
d487d71
341084b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d487d71
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
341084b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d487d71
341084b
d487d71
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
341084b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d487d71
341084b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d487d71
341084b
d487d71
 
 
 
 
 
 
 
 
341084b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
  1
  2
  3
  4
  5
  6
  7
  8
  9
 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
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 4.6 递归"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<font size=\"6\">1.斐波那契数列前n项</font>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<font size=\"6\">0,1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,……</font>\n",
    "<img src = \"images/ch4/4.gif\">\n",
    "斐波那契数列又称黄金分割数列,这个数列从第3项开始,后一项的值总是与他前面两项的和的值相等。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在数学上,以递推的方法定义:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```python\n",
    "F(1)=0\n",
    "F(2)=1 \n",
    "F(n)=F(n - 1)+F(n - 2)    (n ≥ 2,n ∈ N*)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在数学上,也可以给出归纳方法定义:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```python\n",
    "f(n) = 0  (n=0)\n",
    "f(n) = 1  (n=1)\n",
    "f(n) = f(n - 1) + f(n - 2)    (n ≥ 2,n ∈ N*)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "很明显这是一个分段函数,可以用二分支来实现:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdin",
     "output_type": "stream",
     "text": [
      " 5\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "5\n"
     ]
    }
   ],
   "source": [
    "def fibonacci(n):\n",
    "    \"\"\"定义递归函数,接收非负整数n,返回斐波那契数列的第n项\"\"\"\n",
    "    if n == 0:  # 第一项为1\n",
    "        return 0\n",
    "    elif n == 1:  # 第二项为1\n",
    "        return 1\n",
    "    else:\n",
    "        return fibonacci(n - 1) + fibonacci(n - 2)  # 其他项等于前两项之和\n",
    "\n",
    "\n",
    "num = int(input())     # 输入非负整数\n",
    "print(fibonacci(num))  # 调用递归函数求斐波那契数列的第n项并输出"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "需要注意的是,在else分支下面,程序的返回值中再次调用了函数fibonacci(),只是参数值变小了。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "修改一下描述:\n",
    "```python\n",
    "f(n) = n  (n≤1)\n",
    "f(n) = f(n - 1) + f(n - 2)    (n ≥ 2,n ∈ N*)\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdin",
     "output_type": "stream",
     "text": [
      " -7\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "-7\n"
     ]
    }
   ],
   "source": [
    "def fibonacci(n):\n",
    "    \"\"\"定义递归函数,接收非负整数n,返回斐波那契数列的第n项\"\"\"\n",
    "    if n <= 1:  # 前两项为0或1\n",
    "        return n\n",
    "    else:\n",
    "        return fibonacci(n - 1) + fibonacci(n - 2)  # 其他项等于前两项之和\n",
    "\n",
    "\n",
    "num = int(input())     # 输入非负整数\n",
    "print(fibonacci(num))  # 调用递归函数求斐波那契数列的第n项并输出"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "类似的问题还有:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "2.阶乘计算:递归也常用于计算阶乘。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def factorial(n):\n",
    "    if n == 1:\n",
    "        return 1\n",
    "    else:\n",
    "        return n * factorial(n-1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "3.汉诺塔问题:汉诺塔问题是一个经典的递归问题。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "def hanoi(n, source, target, auxiliary):\n",
    "    if n > 0:\n",
    "        hanoi(n - 1, source, auxiliary, target)\n",
    "        print('Move disk from {} to {}'.format(source, target))\n",
    "        hanoi(n - 1, auxiliary, target, source)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "4.\"五人分鱼\"。问题描述如下:五个人在夜间捕到一堆鱼,约定第二天分鱼。第二天早上,第一个人醒来,将鱼分为五份,发现多了一条,就扔掉这条,拿走自己的一份。后来第二个、第三个、第四个、第五个人都是这样分鱼的。问他们至少捕到多少条鱼?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "def fish(n, total):\n",
    "    # n是还未分鱼的人数,total是当前的鱼数\n",
    "    if n == 0:  # 所有的人都已经分过鱼了\n",
    "        return total  # 返回最后的鱼数\n",
    "    elif total % 5 == 1:  # 当前的鱼数可以被5整除,并且还剩一条鱼\n",
    "        # 递归调用fish函数,n减1,total更新为剩下的鱼数\n",
    "        return fish(n - 1, (total - 1) * 4 / 5)\n",
    "    else:  # 当前的鱼数不能被5整除,或者不剩一条鱼\n",
    "        return False  # 返回False,表示这个鱼数不符合条件\n",
    "\n",
    "total = 1  # 从1开始尝试\n",
    "while True:  # 不断尝试,直到找到一个符合条件的鱼数\n",
    "    if fish(5, total):  # 如果这个鱼数符合条件\n",
    "        print(f\"五人至少合伙捕到{total}条鱼\")  # 输出这个鱼数\n",
    "        break  # 结束循环\n",
    "    total += 1  # 尝试下一个鱼数"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "5.有一座八层宝塔,每一层都有一些琉璃灯,每一层的灯数都是上一层的二倍,已知共有765盏琉璃灯,求解每层各有多少。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[3, 6, 12, 24, 48, 96, 192, 384]\n"
     ]
    }
   ],
   "source": [
    "def lamps(n, total):\n",
    "    if n == 1:  # 如果只有一层\n",
    "        return [total]  # 返回总灯数\n",
    "    else:\n",
    "        # 第一层的灯数是总灯数除以(2^n - 1)\n",
    "        first = total // (2 ** n - 1)\n",
    "        # 返回第一层的灯数和剩下层的灯数\n",
    "        return [first] + lamps(n - 1, total - first)\n",
    "\n",
    "\n",
    "print(lamps(8, 765))  # 输出每层的灯数\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "6.猴子吃桃问题猴子摘下了n个桃子第一天吃了一半又多吃了一个第二天也是吃了剩下的一半又多吃了一个到了第十天想再吃时发现只剩下一个桃子了问猴子第一天共摘了多少个桃子"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def peaches(day):\n",
    "    if day == 10:  # 第10天只剩下一个桃子\n",
    "        return 1\n",
    "    else:\n",
    "        # 第day天的桃子数是第day+1天的桃子数加一后的两倍\n",
    "        return (peaches(day + 1) + 1) * 2\n",
    "\n",
    "print(peaches(1))  # 输出第一天的桃子数"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<font color=Red>__递归Recursion__</font>是指在函数的定义中<font color=Red>__调用函数自身__</font>的方法。\n",
    "\n",
    "递归的基本思想是把规模大的问题转化为规模小的相似的子问题来解决在函数实现时因为解决大问题的方法和解决小问题的方法往往是同一个所以就产生了函数调用它自身的情况当然这样的函数必须有明显的结束条件<font color=Red>__以避免产生无限递归的问题__</font>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "函数内部可以调用其他函数如果一个函数内部<font color=Red>__调用函数本身__</font>这个函数就是<font color=Red>__递归函数__</font>。\n",
    "\n",
    "递归的解题思路十分擅长解决除了规模大小不同其他完全一样的问题比如阶乘Fibonacci数列等问题用递归方法来解决可以用较少的代码来完成 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "def fact(n):\n",
    "    '''定义递归函数,接收非负整数n,返回n的阶乘'''\n",
    "    if n == 0:      # 必须有一个终止条件\n",
    "        return 1    # 此时返回值确定值\n",
    "    else:\n",
    "        return n * fact(n - 1)  # 调用fact()自身即当前数n的阶乘等于n与前一个数n-1的阶乘之积\n",
    "\n",
    "num = int(input())  #输入非负整数\n",
    "print(fact(num))    #调用递归函数求阶乘并输出"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "函数fact(5)开始调用时传入参数为5返回值为5 * fact(4)即调用fact()函数自身传入值为4返回值为4 * fact(3)....以此类推函数每调用一次问题的<font color=Red>__规模减小1__</font>直至遇到函数的<font color=Red>__结束条件__</font>n == 0终止函数的调用<font color=Red>__逐层返回__</font>逆向运算得到结果。\n",
    "\n",
    "下图说明了递归求解的过程"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"images/ch4/5.png\">"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<font color=Red>__递归函数必须满足如下特性__</font>\n",
    "1. 必须有一个明确的递归终止条件。\n",
    "递归在有限次调用后要进行回溯才能得到最终的结果那么必然应该有一个明确的临界点程序一旦到达了这个临界点就不用继续函数的调用而开始回溯该临界点可以防止无限递归。\n",
    "2. 给出递归终止时的处理办法。\n",
    "在递归的临界点应该直接给出问题的解决方案。\n",
    "3. 每次进入更深一层递归时问题规模相比上次递归都应有所减少或更接近于解"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<font color=Red>__能用递归解决的问题__</font>必须可以分解为若干个规模较小与原问题形式相同的子问题这些子问题可以用相同的解题思路来解决从程序实现的角度而言需要抽象出一个简单的重复的逻辑以便使用相同的方式解决子问题"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<font color=Red>__递归函数的优点__</font>是定义简单逻辑清晰理论上所有的递归函数都可以写成循环的方式但循环的逻辑不如递归清晰<font color=Red>__递归的缺点__</font>是效率不高而且使用递归函数需要注意防止栈溢出"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在计算机中函数调用是通过<font color=Red>__栈stack__</font>这种数据结构实现的每当进入一个函数调用栈就会加一层栈帧每当函数返回栈就会减一层栈帧由于栈的大小不是无限的所以递归调用的次数过多会导致<font color=Red>__栈溢出stack overflow__</font>一般默认递归长度在1000左右使用Python写的递归程序如果递归太深, 那么极有可能因为超过系统默认的递归深度限制而出现错误 \n",
    "\n",
    "<font face='楷体' color='blue' size=2> RuntimeError: maximum recursion depth exceeded in comparison </font>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "解决这个问题有三种方法"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1. 人为设置递归深度方法如下"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys\n",
    "\n",
    "\n",
    "sys.setrecursionlimit(3000)   #括号中的值为递归深度"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "递归深度同时受操作系统栈的深度限制不同系统环境下支持的最大递归深度不同在64位windows 10环境下最大递归深度约为3900次左右"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "2. 递归改成非递归用循环的方法实现"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "3. 尾递归优化"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "尾递归优化是解决递归栈溢出的一种方法。\n",
    "所谓尾递归是指在函数返回的时候调用自身并且return语句不能包含表达式这样编译器或者解释器就可以把尾递归做优化使递归本身无论调用多少次都只占用一个栈帧不会出现栈溢出的情况。\n",
    "\n",
    "在传统的递归中典型的模式是执行第一个递归调用然后接着调用下一个递归来计算结果这种方式中途是得不到计算结果直到所有的递归调用都返回这样虽然很大程度上简洁了代码编写但是随着递归的深入之前的一些变量需要分配堆栈来保存会导致效率降低。\n",
    "\n",
    "相对传统递归尾递归是一种特例在尾递归中先执行某部分的计算然后开始调用递归所以可以得到当前的计算结果而这个结果也将作为参数传入下一次递归这也就是说函数调用出现在调用者函数的尾部所以其有一个优越于传统递归之处就是无需去保存任何局部变量减少内存消耗提高性能当前时刻的计算值作为第二个参数传入下一个递归使得系统不再需要保留之前计算结果理论上讲没有产生中间变量来保存状态的尾递归完全可以复用同一个栈帧来实现所有的递归函数操作这种形式的代码优化就叫做尾递归优化但是遗憾的是Python本身不支持尾递归没有对尾递归做优化),而且对递归的次数有限制当递归深度超过1000时同样会抛出异常"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}