文库网
ImageVerifierCode 换一换
首页 文库网 > 资源分类 > PPTX文档下载
分享到微信 分享到微博 分享到QQ空间

初中语文谏太宗十思疏PPT课件.pptx

  • 资源ID:2181560       资源大小:1.36MB        全文页数:53页
  • 资源格式: PPTX        下载积分:15文币
微信登录下载
快捷下载 游客一键下载
账号登录下载
三方登录下载: QQ登录 微博登录
二维码
扫码关注公众号登录
下载资源需要15文币
邮箱/手机:
温馨提示:
快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。
如填写123,账号就是123,密码也是123。
支付方式: 支付宝    微信支付   
验证码:   换一换

加入VIP,免费下载
 
账号:
密码:
验证码:   换一换
  忘记密码?
    
友情提示
2、PDF文件下载后,可能会被浏览器默认打开,此种情况可以点击浏览器菜单,保存网页到桌面,就可以正常下载了。
3、本站不支持迅雷下载,请使用电脑自带的IE浏览器,或者360浏览器、谷歌浏览器下载即可。
4、本站资源下载后的文档和图纸-无水印,预览文档经过压缩,下载后原文更清晰。
5、试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。

初中语文谏太宗十思疏PPT课件.pptx

1、07|链表(下):如何轻松写出正确的链表代码? file:/F/temp/geektime/数据结构与算法之美/07链表(下):如何轻松写出正确的链表代码?.html2019/1/15 15:35:17 07|链表(下):如何轻松写出正确的链表代码? 上一节我讲了链表相关的基础知识。学完之后,我看到有人留言说,基础知识我都掌握了,但是写链表代码还是很费劲。哈哈,的确是这样的! 想要写好链表代码并不是容易的事儿,尤其是那些复杂的链表操作,比如链表反转、有序链表合并等,写的时候非常容易出错。从我上百场面试的经验来看,能 把“链表反转”这几行代码写对的人不足10%。 为什么链表代码这么难写?究竟怎样

2、才能比较轻松地写出正确的链表代码呢? 只要愿意投入时间,我觉得大多数人都是可以学会的。比如说,如果你真的能花上一个周末或者一整天的时间,就去写链表反转这一个代码,多写几遍,一直练 到能毫不费力地写出Bug free的代码。这个坎还会很难跨吗? 当然,自己有决心并且付出精力是成功的先决条件,除此之外,我们还需要一些方法和技巧。我根据自己的学习经历和工作经验,总结了几个写链表代码技巧。 如果你能熟练掌握这几个技巧,加上你的主动和坚持,轻松拿下链表代码完全没有问题。 技巧一:理解指针或引用的含义 事实上,看懂链表的结构并不是很难,但是一旦把它和指针混在一起,就很容易让人摸不着头脑。所以,要想写对链表

3、代码,首先就要理解好指针。 我们知道,有些语言有“指针”的概念,比如C语言;有些语言没有指针,取而代之的是“引用”,比如Java、Python。不管是“指针”还是“引用”,实际上,它们的意思 都是一样的,都是存储所指对象的内存地址。 接下来,我会拿C语言中的“指针”来讲解,如果你用的是Java或者其他没有指针的语言也没关系,你把它理解成“引用”就可以了。 实际上,对于指针的理解,你只需要记住下面这句话就可以了: 将某个变量赋值给指针,实际上就是将这个变量的地址赋值给指针,或者反过来说,指针中存储了这个变量的内存地址,指向了这个变量,通过指针就能找到这 个变量。 这句话听起来还挺拗口的,你可以先

4、记住。我们回到链表代码的编写过程中,我来慢慢给你解释。 在编写链表代码的时候,我们经常会有这样的代码:p-next=q。这行代码是说,p结点中的next指针存储了q结点的内存地址。 还有一个更复杂的,也是我们写链表代码经常会用到的:p-next=p-next-next。这行代码表示,p结点的next指针存储了p结点的下下一个结点的内存地址。 掌握了指针或引用的概念,你应该可以很轻松地看懂链表代码。恭喜你,已经离写出链表代码近了一步! 技巧二:警惕指针丢失和内存泄漏 不知道你有没有这样的感觉,写链表代码的时候,指针指来指去,一会儿就不知道指到哪里了。所以,我们在写的时候,一定注意不要弄丢了指针。

5、 指针往往都是怎么弄丢的呢?我拿单链表的插入操作为例来给你分析一下。 07|链表(下):如何轻松写出正确的链表代码? file:/F/temp/geektime/数据结构与算法之美/07链表(下):如何轻松写出正确的链表代码?.html2019/1/15 15:35:17 如图所示,我们希望在结点a和相邻的结点b之间插入结点x,假设当前指针p指向结点a。如果我们将代码实现变成下面这个样子,就会发生指针丢失和内存泄露。 p-next = x; / 将p的next指针指向x结点; x-next = p-next; / 将x的结点的next指针指向b结点; 初学者经常会在这儿犯错。p-next指针在

6、完成第一步操作之后,已经不再指向结点b了,而是指向结点x。第2行代码相当于将x赋值给x-next,自己指向自己。因 此,整个链表也就断成了两半,从结点b往后的所有结点都无法访问到了。 对于有些语言来说,比如C语言,内存管理是由程序员负责的,如果没有手动释放结点对应的内存空间,就会产生内存泄露。所以,我们插入结点时,一定要注意 操作的顺序,要先将结点x的next指针指向结点b,再把结点a的next指针指向结点x,这样才不会丢失指针,导致内存泄漏。所以,对于刚刚的插入代码,我们只需 要把第1行和第2行代码的顺序颠倒一下就可以了。 同理,删除链表结点时,也一定要记得手动释放内存空间,否则,也会出现内

7、存泄漏的问题。当然,对于像Java这种虚拟机自动管理内存的编程语言来说,就不需 要考虑这么多了。 技巧三:利用哨兵简化实现难度 07|链表(下):如何轻松写出正确的链表代码? file:/F/temp/geektime/数据结构与算法之美/07链表(下):如何轻松写出正确的链表代码?.html2019/1/15 15:35:17 首先,我们先来回顾一下单链表的插入和删除操作。如果我们在结点p后面插入一个新的结点,只需要下面两行代码就可以搞定。 new_node-next = p-next; p-next = new_node; 但是,当我们要向一个空链表中插入第一个结点,刚刚的逻辑就不能用了。

8、我们需要进行下面这样的特殊处理,其中head表示链表的头结点。所以,从这段代 码,我们可以发现,对于单链表的插入操作,第一个结点和其他结点的插入逻辑是不一样的。 if (head = null) head = new_node; 我们再来看单链表结点删除操作。如果要删除结点p的后继结点,我们只需要一行代码就可以搞定。 p-next = p-next-next; 但是,如果我们要删除链表中的最后一个结点,前面的删除代码就不work了。跟插入类似,我们也需要对于这种情况特殊处理。写成代码是这样子的: if (head-next = null) head = null; 从前面的一步一步分析,我们可

9、以看出,针对链表的插入、删除操作,需要对插入第一个结点和删除最后一个结点的情况进行特殊处理。这样代码实现起来就会 很繁琐,不简洁,而且也容易因为考虑不全而出错。如何来解决这个问题呢? 技巧三中提到的哨兵就要登场了。哨兵,解决的是国家之间的边界问题。同理,这里说的哨兵也是解决“边界问题”的,不直接参与业务逻辑。 还记得如何表示一个空链表吗?head=null表示链表中没有结点了。其中head表示头结点指针,指向链表中的第一个结点。 如果我们引入哨兵结点,在任何时候,不管链表是不是空,head指针都会一直指向这个哨兵结点。我们也把这种有哨兵结点的链表叫带头链表。相反,没有哨兵 结点的链表就叫作不带

10、头链表。 我画了一个带头链表,你可以发现,哨兵结点是不存储数据的。因为哨兵结点一直存在,所以插入第一个结点和插入其他结点,删除最后一个结点和删除其他结 点,都可以统一为相同的代码实现逻辑了。 07|链表(下):如何轻松写出正确的链表代码? file:/F/temp/geektime/数据结构与算法之美/07链表(下):如何轻松写出正确的链表代码?.html2019/1/15 15:35:17 实际上,这种利用哨兵简化编程难度的技巧,在很多代码实现中都有用到,比如插入排序、归并排序、动态规划等。这些内容我们后面才会讲,现在为了让你感 受更深,我再举一个非常简单的例子。代码我是用C语言实现的,不涉

11、及语言方面的高级语法,很容易看懂,你可以类比到你熟悉的语言。 代码一: / 在数组a中,查找key,返回key所在的位置 / 其中,n表示数组a的长度 int find(char* a, int n, char key) / 边界条件处理,如果a为空,或者nnext = pnextnext; 表示p节点的后继指针存储了p节点的下下个节点的内存地址。 二、警惕指针丢失和内存泄漏(单链表) 1.插入节点 在节点a和节点b之间插入节点x,b是a的下一节点,p指针指向节点a,则造成指针丢失和内存泄漏的代码:pnext = x;xnext = pnext; 显然这会导致 x节点的后继指针指向自身。 正确

12、的写法是2句代码交换顺序,即:xnext = pnext; pnext = x; 2.删除节点 在节点a和节点b之间删除节点b,b是a的下一节点,p指针指向节点a:pnext = pnextnext; 三、利用“哨兵”简化实现难度 1.什么是“哨兵”? 链表中的“哨兵”节点是解决边界问题的,不参与业务逻辑。如果我们引入“哨兵”节点,则不管链表是否为空,head指针都会指向这个“哨兵”节点。我们把这 种有“哨兵”节点的链表称为带头链表,相反,没有“哨兵”节点的链表就称为不带头链表。 2.未引入“哨兵”的情况 如果在p节点后插入一个节点,只需2行代码即可搞定: new_nodenext = pne

13、xt; pnext = new_node; 07|链表(下):如何轻松写出正确的链表代码? file:/F/temp/geektime/数据结构与算法之美/07链表(下):如何轻松写出正确的链表代码?.html2019/1/15 15:35:17 但,若向空链表中插入一个节点,则代码如下: if(head = null) head = new_node; 如果要删除节点p的后继节点,只需1行代码即可搞定: pnext = pnextnext; 但,若是删除链表的最有一个节点(链表中只剩下这个节点),则代码如下: if(headnext = null) head = null; 从上面的情况可以

14、看出,针对链表的插入、删除操作,需要对插入第一个节点和删除最后一个节点的情况进行特殊处理。这样代码就会显得很繁琐,所以引 入“哨兵”节点来解决这个问题。 3.引入“哨兵”的情况 “哨兵”节点不存储数据,无论链表是否为空,head指针都会指向它,作为链表的头结点始终存在。这样,插入第一个节点和插入其他节点,删除最后一个节 点和删除其他节点都可以统一为相同的代码实现逻辑了。 4.“哨兵”还有哪些应用场景? 这个知识有限,暂时想不出来呀!但总结起来,哨兵最大的作用就是简化边界条件的处理。 四、重点留意边界条件处理 经常用来检查链表是否正确的边界4个边界条件: 1.如果链表为空时,代码是否能正常工作?

15、 2.如果链表只包含一个节点时,代码是否能正常工作? 3.如果链表只包含两个节点时,代码是否能正常工作? 4.代码逻辑在处理头尾节点时是否能正常工作? 五、举例画图,辅助思考 核心思想:释放脑容量,留更多的给逻辑思考,这样就会感觉到思路清晰很多。 六、多写多练,没有捷径 5个常见的链表操作: 1.单链表反转 2.链表中环的检测 3. 07|链表(下):如何轻松写出正确的链表代码? file:/F/temp/geektime/数据结构与算法之美/07链表(下):如何轻松写出正确的链表代码?.html2019/1/15 15:35:17 两个有序链表合并 4.删除链表倒数第n个节点 5.求链表的中

16、间节点 67赞 Rain 2018-10-04 22:50:06 谢谢老师,这节课又学到了,写完留言我要去思考那几个问题了,一个都不会。 - 文中提到, 但是,如果我们要删除链表中的最后一个结点,前面的删除代码就不 work 了。跟插入类似,我们也需要对于这种情况特殊处理。写成代码是这样子的: if (head-next = null) head = null; - 感觉此处代码处理的是当链表中只有表头一个节点的删除情况,而不是“要删除链表中的最后一个结点“的情况。是不是head应该改成p? 31赞 optvxq 2018-10-10 07:20:24 哨兵可以理解为它可以减少特殊情况的判断,

17、比如判空,比如判越界,比如减少链表插入删除中对空链表的判断,比如例子中对i越界的判断。 空与越界可以认为是小概率情况,所以代码每一次操作都走一遍判断,在大部分情况下都会是多余的。 哨兵的巧妙就是提前将这种情况去除,比如给一个哨兵结点,以及将key赋值给数组末元素,让数组遍历不用判断越界也可以因为相等停下来。 使用哨兵的指导思想应该是将小概率需要的判断先提前扼杀,比如提前给他一个值让他不为null,或者提前预设值,或者多态的时候提前给个空实现,然后 在每一次操作中不必再判断以增加效率。 19赞 五岳寻仙 2018-10-07 00:21:18 老师您好!请教您一个问题。在学习了数组和链表之后,想

18、知道在现实应用中有没有将二者结合起来的情况。 比如,我想用数组存储数据,但数组大小提前无法知道,如果使用动态数组的话,中间涉及到数组拷贝;如果使用链表的话,每增加一个元素都要malloc一 07|链表(下):如何轻松写出正确的链表代码? file:/F/temp/geektime/数据结构与算法之美/07链表(下):如何轻松写出正确的链表代码?.html2019/1/15 15:35:17 次(频繁的malloc会不会影响效率并且导致内存碎片?)。 可不可以用链表将数组链接起来?也就是说链表里每个node存储了数组指针,这样每增加一个节点就可以多存放很多元素。如果可以的话,与直接使用动态 数组

19、或者直接使用链表比有没有什么优缺点,为何在网上搜索几乎找不到人这样用? 18赞 作者回复2018-10-08 00:41:47 思考的深入 你说的这个很像内存池 你可以百度一下看看是不是你想要的 zyzheng 2018-10-04 23:22:39 一直对手写链表代码有恐惧心理,这次硬着头皮也要迈过这个坎 17赞 来自地狱的勇士 2018-10-05 02:29:48 问题一:文中提到,指针丢失会导致内存泄露,老师能解释下如何导致的内存泄露吗? 问题二:讲哨兵那块的内容时,说代码二比代码一成功省掉了一次比较in,这句不大理解,代码二中,while的条件ai!=key也是在比较吧? 16赞 g

20、ogo 2018-10-05 02:08:40 c语言不熟悉 看起来有点吃力 8赞 作者回复2018-10-08 00:43:32 不好意思 我尽量写简单点 多加点注释 小喵喵 2018-10-05 08:18:22 学习了好几节数据结构和算法了,我是也CRUD业务代码的,感觉还是用不着啊? 7赞 作者回复2018-10-05 14:06:31 1. 建议再看下“为什么要学习数据结构和算法”那节课,包括里面的留言,有很多留言都写的很好,很多人都对这门课有比较清晰深刻的认识。 2. 你的疑问应该是:局限于你现在的工作,你觉得用不上对吧。这个是很有可能的。如果你做的项目都是很小的项目,也没有什么性

21、能压力,平时自己也不 去思考非功能性的需求,只是完成业务代码就ok了,那确实感觉用不到。但这是你个人的原因,并不代表就真用不到呢,兄弟! 3. 专栏里有很多贴近开发的内容,比如链表这一节,我就讲了LRU算法。数组这一节,我讲了容器和数组的选择。复杂度这一节,我讲了如何预判代码的性 能。这些都是很贴合开发的。 4. 我尽量将内容贴近实际的开发,但并不代表一定贴近你的CRUD开发。知识如何用到你的项目中,需要你自己根据我的文章举一反三的思考。 14|排序优化:如何实现一个通用的、高性能的排序函数? file:/F/temp/geektime/数据结构与算法之美/14排序优化:如何实现一个通用的、高

22、性能的排序函数?.html2019/1/15 15:35:33 14|排序优化:如何实现一个通用的、高性能的排序函数? 几乎所有的编程语言都会提供排序函数,比如C语言中qsort(),C+ STL中的sort()、stable_sort(),还有Java语言中的Collections.sort()。在平时的开发中,我们也都 是直接使用这些现成的函数来实现业务逻辑中的排序功能。那你知道这些排序函数是如何实现的吗?底层都利用了哪种排序算法呢? 基于这些问题,今天我们就来看排序这部分的最后一块内容:如何实现一个通用的、高性能的排序函数? 如何选择合适的排序算法? 如果要实现一个通用的、高效率的排序函

23、数,我们应该选择哪种排序算法?我们先回顾一下前面讲过的几种排序算法。 14|排序优化:如何实现一个通用的、高性能的排序函数? file:/F/temp/geektime/数据结构与算法之美/14排序优化:如何实现一个通用的、高性能的排序函数?.html2019/1/15 15:35:33 我们前面讲过,线性排序算法的时间复杂度比较低,适用场景比较特殊。所以如果要写一个通用的排序函数,不能选择线性排序算法。 如果对小规模数据进行排序,可以选择时间复杂度是O(n2)的算法;如果对大规模数据进行排序,时间复杂度是O(nlogn)的算法更加高效。所以,为了兼顾任意规 模数据的排序,一般都会首选时间复杂

24、度是O(nlogn)的排序算法来实现排序函数。 14|排序优化:如何实现一个通用的、高性能的排序函数? file:/F/temp/geektime/数据结构与算法之美/14排序优化:如何实现一个通用的、高性能的排序函数?.html2019/1/15 15:35:33 时间复杂度是O(nlogn)的排序算法不止一个,我们已经讲过的有归并排序、快速排序,后面讲堆的时候我们还会讲到堆排序。堆排序和快速排序都有比较多的应 用,比如Java语言采用堆排序实现排序函数,C语言使用快速排序实现排序函数。 不知道你有没有发现,使用归并排序的情况其实并不多。我们知道,快排在最坏情况下的时间复杂度是O(n2),而

25、归并排序可以做到平均情况、最坏情况下的时间 复杂度都是O(nlogn),从这点上看起来很诱人,那为什么它还是没能得到“宠信”呢? 还记得我们上一节讲的归并排序的空间复杂度吗?归并排序并不是原地排序算法,空间复杂度是O(n)。所以,粗略点、夸张点讲,如果要排序100MB的数据,除 了数据本身占用的内存之外,排序算法还要额外再占用100MB的内存空间,空间耗费就翻倍了。 前面我们讲到,快速排序比较适合来实现排序函数,但是,我们也知道,快速排序在最坏情况下的时间复杂度是O(n2),如何来解决这个“复杂度恶化”的问题呢? 如何优化快速排序? 我们先来看下,为什么最坏情况下快速排序的时间复杂度是O(n2

26、)呢?我们前面讲过,如果数据原来就是有序的或者接近有序的,每次分区点都选择最后一个数 据,那快速排序算法就会变得非常糟糕,时间复杂度就会退化为O(n2)。实际上,这种O(n2)时间复杂度出现的主要原因还是因为我们分区点选的不够合理。 那什么样的分区点是好的分区点呢?或者说如何来选择分区点呢? 最理想的分区点是:被分区点分开的两个分区中,数据的数量差不多。 如果很粗暴地直接选择第一个或者最后一个数据作为分区点,不考虑数据的特点,肯定会出现之前讲的那样,在某些情况下,排序的最坏情况时间复杂度 是O(n2)。为了提高排序算法的性能,我们也要尽可能地让每次分区都比较平均。 我这里介绍两个比较常用、比较

27、简单的分区算法,你可以直观地感受一下。 1.三数取中法 我们从区间的首、尾、中间,分别取出一个数,然后对比大小,取这3个数的中间值作为分区点。这样每间隔某个固定的长度,取数据出来比较,将中间值作为分 区点的分区算法,肯定要比单纯取某一个数据更好。但是,如果要排序的数组比较大,那“三数取中”可能就不够了,可能要“五数取中”或者“十数取中”。 2.随机法 随机法就是每次从要排序的区间中,随机选择一个元素作为分区点。这种方法并不能保证每次分区点都选的比较好,但是从概率的角度来看,也不大可能会出现 每次分区点都选的很差的情况,所以平均情况下,这样选的分区点是比较好的。时间复杂度退化为最糟糕的O(n2)

28、的情况,出现的可能性不大。 好了,我这里也只是抛砖引玉,如果想了解更多寻找分区点的方法,你可以自己课下深入去学习一下。 我们知道,快速排序是用递归来实现的。我们在递归那一节讲过,递归要警惕堆栈溢出。为了避免快速排序里,递归过深而堆栈过小,导致堆栈溢出,我们有两 种解决办法:第一种是限制递归深度。一旦递归过深,超过了我们事先设定的阈值,就停止递归。第二种是通过在堆上模拟实现一个函数调用栈,手动模拟递归 压栈、出栈的过程,这样就没有了系统栈大小的限制。 举例分析排序函数 14|排序优化:如何实现一个通用的、高性能的排序函数? file:/F/temp/geektime/数据结构与算法之美/14排序

29、优化:如何实现一个通用的、高性能的排序函数?.html2019/1/15 15:35:33 为了让你对如何实现一个排序函数有一个更直观的感受,我拿Glibc中的qsort()函数举例说明一下。虽说qsort()从名字上看,很像是基于快速排序算法实现的,实际 上它并不仅仅用了快排这一种算法。 如果你去看源码,你就会发现,qsort()会优先使用归并排序来排序输入数据,因为归并排序的空间复杂度是O(n),所以对于小数据量的排序,比如1KB、2KB等, 归并排序额外需要1KB、2KB的内存空间,这个问题不大。现在计算机的内存都挺大的,我们很多时候追求的是速度。还记得我们前面讲过的用空间换时间的技巧 吗?这就是一个典型的应用。 但如果数据量太大,就跟我们前面提到的,排序100MB的数据,这个时候我们再用归并排序就不合适了。所以,要排序的数据量比较大的时候,qsort()会改为用 快速排序算法来排序。 那qsort()是如何选择快速排序算法的分区点的呢?如果去看源码,你就会发现,qsort()选择分区点的方法就是“三数取中法”。是不是也并不复杂? 还有我们前面提到的递归太深会导致堆栈溢出的问题,qsort()是通过自己实现一个堆上


注意事项

本文(初中语文谏太宗十思疏PPT课件.pptx)为本站会员(唯美)主动上传,文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知文库网(点击联系客服),我们立即给予删除!




关于我们 - 网站声明 - 网站地图 - 资源地图 - 友情链接 - 网站客服 - 联系我们

文库网用户QQ群:731843829  微博官方号:文库网官方   知乎号:文库网

Copyright© 2025 文库网 wenkunet.com 网站版权所有世界地图

经营许可证编号:粤ICP备2021046453号   营业执照商标

1.png 2.png 3.png 4.png 5.png 6.png 7.png 8.png 9.png 10.png