递归和快速排序

文章目录

  • 递归
    • 问题描述
    • 基线条件和递归条件
      • 调用栈
      • 递归调用栈
    • 小结
  • 快速排序
    • 示例1
      • 问题描述
      • 欧几里得算法
      • 使用D&C解决问题的两个步骤:
    • 示例2
  • 快速排序
    • 工作原理
    • 代码
  • 小结

递归

问题描述

假设你在祖母的阁楼中翻箱倒柜,发现了一个上锁的神秘手提箱。祖母告诉你,钥匙很可能在下面这个盒子里,这个盒子里有盒子,而盒子里的盒子又有盒子。钥匙就在某个盒子中。为找到钥匙,你将使用什么算法?
方法一:

  1. 创建一个要查找的盒子堆。
  2. 从盒子堆取出一个盒子,在里面找。
  3. 如果找到的是盒子,就将其加入盒子堆中,以便以后再查找。
  4. 如果找到钥匙,则大功告成!
  5. 回到第二步。

方法二:

  1. 检查盒子中的每样东西。
  2. 如果是盒子,就回到第一步。
  3. 如果是钥匙,就大功告成!

第一种方法使用的是while循环:只要盒子堆不空,就从中取一个盒子,并在其中仔细查找。

def look_for_key(main_box):pile =  main_box.make_a_pile_to_look_through()while pile is not empty:box = pile.grab_a_box()for item in box:if item.is_a_box():pile.append(item)elif item.is_a_key():print("found the key!")

第二种方法使用递归——函数调用自己,这种方法的伪代码如下。

def look_for_key(box):for item in box:if item.is_a_box():look_for_key(item)  #递归elif item.is_a_key():print("found the key!)

递归只是让解决方案更清晰,并没有性能上的优势。实际上,在有些情况下,使用循环的性能更好。

基线条件和递归条件

由于递归函数调用自己,因此编写这样的函数时很容易出错,进而导致无限循环。例如,下面这样的倒计时函数。

def countdown(i):print(i)countdown(i-1)
print(countdown(3))

运行上述代码,将发现一个问题:这个函数运行起来没完没了!(要让脚本停止运行,可按Ctrl+C。)

编写递归函数时,必须告诉它何时停止递归。正因为如此,每个递归函数都有两部分:基线条件(base case)和递归条件(recursive case)。递归条件指的是函数调用自己,而基线条件则指的是函数不再调用自己,从而避免形成无限循环。

def countdown(i)print(i)if i <= 0:	#基线条件returnelse:		#递归条件countdown(i - 1)

调用栈(call stack)只有两种操作:压入(插入)和弹出(删除并读取)。

调用栈

def greet(name):print("hello, " + name + "!")greet2(name)print("getting ready to say bye...")bye()
def greet2(name):print("how are you, " + name + "?")
def bye():print("ok bye!")
greet("maggie")

假设你调用greet(“maggie”),计算机将首先为该函数调用分配一块内存。

我们来使用这些内存。变量name被设置为maggie,这需要存储到内存中。

每当你调用函数时,计算机都像这样将函数调用涉及的所有变量的值存储到内存中。接下来,你打印hello, maggie!,再调用greet2(“maggie”)。同样,计算机也为这个函数调用分配一块内存。

计算机使用一个栈来表示这些内存块,其中第二个内存块位于第一个内存块上面。你打印how are you, maggie?,然后从函数调用返回。此时,栈顶的内存块被弹出。

现在,栈顶的内存块是函数greet的,这意味着你返回到了函数greet。

这是本节的一个重要概念:调用另一个函数时,当前函数暂停并处于未完成状态。该函数的所有变量的值都还在内存中。执行完函数greet2后,你回到函数greet,并从离开的地方开始接着往下执行:首先打印getting ready to say bye…,再调用函数bye。

在栈顶添加了函数bye的内存块。然后,你打印ok bye!,并从这个函数返回。

现在你又回到了函数greet。由于没有别的事情要做,你就从函数greet返回。这个栈用于存储多个函数的变量,被称为调用栈。

递归调用栈

递归函数也使用调用栈!如计算阶乘的递归函数factorial:

def fact(x):if x == 1:return 1else:return x * fact(x - 1)
print(fact(5))

注意,每个fact调用都有自己的x变量。在一个函数调用中不能访问另一个的x变量。

栈在递归中扮演着重要角色。在本章开头的示例中,有两种寻找钥匙的方法。对于第一种方法,你创建一个待查找的盒子堆,因此你始终知道还有哪些盒子需要查找。

但使用递归方法时,没有盒子堆。既然没有盒子堆,那算法怎么知道还有哪些盒子需要查找呢?

原来“盒子堆”存储在了栈中!这个栈包含未完成的函数调用,每个函数调用都包含还未检查完的盒子。使用栈很方便,因为你无需自己跟踪盒子堆——栈替你这样做了。

使用栈虽然很方便,但是也要付出代价:存储详尽的信息可能占用大量的内存。每个函数调用都要占用一定的内存,如果栈很高,就意味着计算机存储了大量函数调用的信息。在这种情况下,你有两种选择。

  • 重新编写代码,转而使用循环。
  • 使用尾递归。这是一个高级递归主题,不在本书的讨论范围内。另外,并非所有的语言都支持尾递归。

小结

  • 递归指的是调用自己的函数。
  • 每个递归函数都有两个条件:基线条件和递归条件。
  • 栈有两种操作:压入和弹出。
  • 所有函数调用都进入调用栈。
  • 调用栈可能很长,这将占用大量的内存

快速排序

分而治之(divide and conquer,D&C)——一种著名的递归式问题解决方法。

只能解决一种问题的算法毕竟用处有限,而D&C提供了解决问题的思路,是另一个可供你使用的工具。面对新问题时,你不再束手无策,而是自问:“使用分而治之能解决吗?”

示例1

问题描述

讲一块长方形的土地,均匀地分成方块,且分出的方块要尽可能大。即找出长和宽的最大公约数

欧几里得算法

用于查找(A,B)最大公约数(GCD)的欧几里得算法如下:

  1. 如果A = 0,则GCD(A,B)= B,因为GCD(0,B)= B,我们可以停下来。
  2. 如果B = 0,则GCD(A,B)= A,因为GCD(A,0)= A,我们可以停下来。
  3. 用余数形式写A(A =B⋅Q+ R),其中Q是除数,R是余数。
  4. 因为GCD(A,B)= GCD(B,R),所以使用欧几里得算法找到GCD(B,R)

使用D&C解决问题的两个步骤:

  1. 找出基线条件,这种条件必须尽可能简单。
  2. 不断将问题分解(或者说缩小规模),直到符合基线条件。

D&C并非可用于解决问题的算法,而是一种解决问题的思路

示例2

求数字数组之和:

  1. 循环方法
def sum(arr): total = 0 for x in arr: total += x return total print(sum([1, 2, 3, 4]))
  1. 递归函数
def sum(xlist):if len(xlist) == 0:	#找出基准条件return 0else:return xlist.pop(len(xlist) - 1) + sum(xlist)#缩小问题规模,每次递归调用都必须离空数组更进一步
print(sum([1, 2, 3, 4, 5]))

编写涉及数组的递归函数时,基线条件通常是数组为空或只包含一个元素。陷入困境时,请检查基线条件是不是这样的。

相比较用循环解决问题,函数式编程语言没有循环,只能使用递归来编写。

快速排序

工作原理

  1. 从数组中随机选择一个元素,这个元素被称为基准值(pivot)。
  2. 找出比基准值小的元素以及比基准值大的元素,这被称为分区(partitioning)。
  3. 对这两个子数组进行快速排序,再合并结果,就能得到一个有序数组。

代码

def  quicksort(array): if len(array) < 2:return array	#基线条件:为空或只包含一个元素的数组是“有序”的else:pivot = array[0]less = [i for i in array[1:] if i < pivot]greater = [i for i in array[1:] if i > pivot]return quicksort(less) + [pivot] + quicksort(greater)
print(quicksort([10, 5, 2, 3]))

快速排序的时间复杂度为O(n∗logn)O(n*logn)O(nlogn),这里同样也省去了系数,而且是平均运行时间。

快速排序是最快的排序算法之一,也是D&C典范。

小结

  • D&C将问题逐步分解。使用D&C处理列表时,基线条件很可能是空数组或只包含一个元素的数组。
  • 实现快速排序时,请随机地选择用作基准值的元素。快速排序的平均运行时间为O(n∗logn)O(n*log n)O(nlogn)
  • 大O表示法中的常量有时候事关重大,这就是快速排序比合并排序快的原因所在。
  • 比较简单查找和二分查找时,常量几乎无关紧要,因为列表很长时O(logn)O(log n)O(logn)比O(n)快得多。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/492531.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spire.Pdf 的各种操作总结

Spire.Pdf 的各种操作总结简介 试验新产品总是给我带来许多挑战&#xff0c;当然这也是一个引进创新技术的好方法。在这里我要跟大家分享的是使用Spire.Pdf的过程&#xff0c;它是来自E-iceblue公司的轻便PDF程序库。 设计情节我以前经常没事就搞搞PDF。Spire.Pdf是用C# 写的PD…

从观望到行动:全球工业物联网市场生态全景观察

来源&#xff1a;资本实验室在当前&#xff0c;诸如5G&#xff0c;物联网、边缘计算、人工智能、机器人、区块链、增材制造和虚拟现实/增强现实等技术正在加速融合到工业物联网&#xff08;Industrial Internet of Things&#xff0c;IIoT&#xff09;的肥沃土壤中&#xff0c;…

OC语言知识6

本文目录 一、异步POST请求二、NSURLConnection的其他请求方法上一讲介绍了iOS中的异步GET请求&#xff0c;这讲来看看异步POST请求。 回到顶部一、异步POST请求 假如请求路径是http://192.168.1.102:8080/MJServer/login&#xff0c;请求参数有2个&#xff1a; username &…

散列表(字典)

文章目录问题散列函数应用案例将散列表用于查找防止重复将散列表用作缓存冲突性能装填因子良好的散列函数小结问题 你在一家杂货店上班。有顾客来买东西时&#xff0c;你得在一个本子中查找价格。 如果本子的内容不是按字母顺序排列的&#xff0c;你可能为查找苹果&#xff0…

谁能引领国内人工智能芯片产业突围?

来源&#xff1a;国金证券摘要&#xff1a;我们认为中国在处理器/芯片领域的投资有加速的迹象&#xff0c;AI芯片的创业企业目前已达到40家左右。未来因人工智能边缘运算推理端和云端推理(Inferencing)芯片及设备成本,性能&#xff0c;耗电,效率的考量,以及各种处理器的特性不同…

OC语言知识12

本文目录 一、添加一个简单的图层二、添加一个显示图片的图层三、为什么CALayer中使用CGColorRef和CGImageRef这2种数据类型&#xff0c;而不用UIColor和UIImage&#xff1f;四、UIView和CALayer的选择五、UIView和CALayer的其他关系* 上一讲已经说过&#xff0c;UIView内部默认…

计算机视觉介绍

1.为什么要学习图像处理和计算机视觉 计算机视觉市场需求大&#xff0c;其是人工智能的重要分支。 计算机视觉岗位占所有AI岗位的40%。 但是&#xff0c;中国高校目前尚未设置计算机视觉学科己专业&#xff1b;学习者众多&#xff0c;学习分散&#xff0c;缺少统一教学体系&a…

认识工业互联网

来源&#xff1a;智汇工业摘要&#xff1a;工业互联网是实现智能制造的抓手&#xff0c;推动工业互联网是长期的工作。工业互联网分为广义的工业互联网和狭义的工业互联网。广义的工业互联网就是第四次工业革命的代名词。和德国工业4.0以及中国制造2025类同&#xff0c;都是工业…

模式识别:绪论

模式识别(pattern recognition)&#xff1a;输入原始数据并根据其类别采取相应行为的能力。 具体实例&#xff1a;人脸识别、语音识别、文字识别、指纹识别、DNA序列分析。 要区分不同类别的个体&#xff0c;需要利用其一些物理特性上的差异&#xff0c;成为模式分类的特征。…

A16Z内部万字报告:人类与AI结合的最佳形态

来源&#xff1a;36Kr摘要&#xff1a;人工智能技术正在快速发展&#xff0c;将会给我们什么样的变化&#xff1f;人们应该如何与人工智能相处&#xff1f;近日&#xff0c;A16Z博客上放出了一篇内部报告&#xff0c;介绍了人工智能将会给我们的社会带来的变化&#xff0c;以及…

照明与图像

光通量&#xff1a; 人眼所能感受到的辐射功率&#xff0c;等于单位时间内某一波段的辐射能量和该波段的相对视见率的乘积。单位是lm(流明)1流明 0.00146瓦 辐照度 投射到一平表面上的辐射通量密度。指到达一表面上&#xff0c;单位时间&#xff0c;单位面积上的辐射能。以…

BZOJ3434 [Wc2014]时空穿梭

摔电脑摔电脑&#xff01;JZP业界毒瘤&#xff01; 400题纪念~哇终于上400了的说&#xff01;&#xff01;&#xff01;好不容易欸&#xff01; 题解什么的还是Orz iwtwiioi 我求组合数的方法明明是O(n)的&#xff0c;为什么这么慢&#xff01;&#xff01;&#xff01;令人报警…

图像平滑滤波

卷积与滤波概念 离散卷积 丢两个骰子&#xff0c;求点数加起来为 ttt 的概率是多少&#xff1f; 两个骰子加起来为4的概率&#xff1a; f(1)g(3)f(2)g(2)f(3)g(1)f(1)g(3) f(2)g(2) f(3)g(1)f(1)g(3)f(2)g(2)f(3)g(1) 写成卷积标准形式为&#xff1a; (f∗g)(4)∑i13f(i)g(…

fast-json.jar的用法

fast-json.jar 解析json数据&#xff1a;一种json数据解析方式是这种&#xff0c;点击这里下载jsonfast.jarfastjsonAPI文档 [{"id": 6378,"title": "test","img": "http://image.jxvdy.com/2014/0929/5428d91c9e6dc8f78fd99_0.p…

从全球制造业的迁移史,看中国制造业未来会怎么走?

来源&#xff1a;挖数&#xff08;ID&#xff1a;washu66&#xff09;摘要&#xff1a;中国制造后续如何发展&#xff1f;翻开全球制造业的迁移史&#xff0c;看是否能从中看出一点端倪。1/ 全球制造业的迁移史1/ 第一次大迁移第一次制造业大迁移发生在20世纪初&#xff0c;由美…

应用|5G时代10大应用场景!

来源&#xff1a;数字化企业摘要&#xff1a;5G商用日益临近&#xff0c;大家可曾想过5G技术未来有哪些具体的应用场景呢&#xff1f;作为5G领跑者的华为公司&#xff0c;早在2年前就出了一份白皮书&#xff0c;这份报告探讨了最能体现5G能力的十大应用场景。简要列表如下1.云V…

标准控件(二)——Calendar

Calendar 日程控件 属性 Borderstyle 边框样式 DayNameFormat 日标头的文本格式 FirstDayOfWeek NextPrevFormat 月导航按钮的格式 方法 DayRender() 在呈现日时激发 protected void Calendar1_DayRender(object sender,…

央行发布论文:区块链能做什么,不能做什么?

来源&#xff1a;悟空智能科央行发布工作论文《区块链能做什么、不能做什么&#xff1f;》&#xff0c;论文称&#xff0c;不要夸大或迷信区块链的功能。区块链应用要立足实际情况。目前区块链投融资领域泡沫明显。论文从经济学角度研究了区块链的功能。首先&#xff0c;在给出…

证明积分

证明积分&#xff1a;$$\int_{-\pi/2}^{\pi/2} (\sin(x))^n dx \frac{n-1}{n}\int_{-\pi/2}^{\pi/2} (\sin(x))^{n-2} dx$$ 证明&#xff1a; \begin{align}\int_{-\pi/2}^{\pi/2} \sin^nx \, dx& -\sin^{n-1}x \cos x\bigg|_{-\pi/2}^{\pi/2} \int_{-\pi/2}^{\pi/2} \co…

图像变换

图像变换有什么用&#xff1f; 图像变换意义&#xff1a; 图像的特征更为突出原来无法直接观测的特征直接显现出来需要提取图像中的特征&#xff0c;便于后续处理及图像理解 常见图像变换&#xff1a; 几何变换&#xff1a;图像放缩、图像平移、图像旋转、图像镜像、图像翻…