第一部分算法基础之插入排序

Posted 算法导论精华版笔记

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第一部分算法基础之插入排序相关的知识,希望对你有一定的参考价值。

  上一期问题参考。

  

  1对于一个算法的优劣来说,你认为有什么比性能和效率更重要的考量标准吗。

  

  安全,稳定,可扩展性,成本,用户友好。

  

  很多人会认为性能和效率才应该是最重要的。那怎么解释性能和其他度量之间的关系呢?我们这里将性能可以看成“货币”,我们可以通过消耗性能,兑换安全,稳定或者其他度量。举例说明,人们采用java。是消耗了三倍相对于c的程序执行效率,但是换来了更多的功能,异常处理,面向对象的机制等。

  

  2选择一种你一直的数据结构,并讨论起优势和局限?

  

  ////////////////////////////////////////////////

  接下来我们正式开始今天的内容:

       算法基础之插入排序。

  

  所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。排序的算法有很多,对空间的要求及其时间效率也不尽相同,其中基础的插入排序和冒泡排序又被称作简单排序,他们对空间的要求不高,但是时间效率却不稳定(少量元素和多元素时执行时间差距明显)。

  

  排序是一个学习算法的古老问题,其中包含了很多算法基础。那么我们就从最简单的算法入手,接下来的若干课时都是以排序为主。

  

  注意:插入算法对于很多有基础的程序员来说是小菜一碟,但是还希望大家仔细看当前的文章,因为作为第一个算法,这里给出了本书中伪代码的一些约定。这些符号需要大家都牢牢记住。以后学习中都用到这些伪代码。

  

  A

  

  要求:

  

  我们这里使用最简单的一种算法,叫做插入排序。

       首先介绍插入排序,对于少量元素,这是一种有效的算法。其实人们对插入排序都应当是熟悉的,因为其工作方式很像是排序一手扑克牌。开始时,我们左手为空,并且桌面上是随机顺序的牌落。然后右手每次从桌面上拿走一张牌,并插入到自己左手中正确的位置。为了找到正确的位置,我们可以在左手牌中从右向左与右手中的牌进行大小比较,保证拿在左手上的牌总是排序好的。

  

  注意,接下来的文字中,提到桌面上的无序牌,就表示的是需要排序的数组A,提到数组A的时候你也要联想起桌面上一摞牌;提到从桌面的牌上起一张新牌,你就需要联想起从未排序的数组A中取得一个元素,准备对它进行排序,反之同理;提到左手上的已经排序的牌,就表示从数组A挨个 取元素之后插入到一个新的元素中,并且对其进行排序。

  

  

第一部分算法基础(1)之插入排序

  

  B

    插入排序的伪代码:

  

  

第一部分算法基础(1)之插入排序

  

  C

《算法导论》中是通过伪代码的方式,所以我们这里首先重点介绍一下本书中的伪代码的约定。

  

  1缩进表示块结构

  

  2while,for,repeat-until等循环结构以及if-else等条件结构与c,c++,java中的结构具有类似的解释。在循环退出后,循环计数器会保留其值。比如上面的代码中 for  j=2 to A.length ,j就是我们的循环计数器,又叫做循环变量。循环结束后j=A.length。因为循环计数器最终的值是是第一个超过for循环临界值的那个值。to表示每次循环计数器加一。如果想要每次减一使用downto关键字。

  

  3//表示注释

  

  4 i=j=e :多重赋值表达式,表示将e的值赋值给j和i。等价于“  j=e;i=j  ”;

  

  5变量若无特殊指定,都表示局部变量。

  

  6数组元素通过来“[ ]”进行访问。比如 "A[i]",表示访问数组A中第i个位置的元素。中括号中还可以使用符号".."用于取数组中的一个范围,比如A[1..i]表示数组A中的一个子集,包含A[1],A[2],A[3]...A[i]。

  

  7复合数据或者对象在对其访问的时候使用符号“.”,比如A.length表示数组A的长度属性。

  

  8我们把一个数组或者对象变量表示一个指向数组或者对象数据的指针。对于某个x对象所有的属性f,若复制y=x。y和x都是对象,导致y.f=x.f。再进一步赋值x.f=3,则赋值后不仅仅是x.f=3,而且y.f也等于3。

  

  9NIL表示空指针

  

  10函数中的参数伪代码是值传递,调用函数时将实际参数复制一份传递到函数中。

  

  11return 返回到调用点,大多数return语句用于将值传递给调用者。本书伪代码中允许单个return返回多个值。

  

  12布尔运算都是短路的(相当于java中的&&和||)

  

  13关键词error表示调用过程中出现错误,调用这负责处理该错误。(相当于java中的上抛异常)

  

  以上是本书中的伪代码语法约定,接下来我们要根据约定解读伪代码




那怎么验证一个算法的正确性呢?

  

  

D

本书中使用的是“循环不变式”来帮助我们理解算法的正确性。

  

  解释:

  

  比如现有数组A=(5,2,4,6,1,3),将A带入该算法之后的运作流程:下标j指出插入到左手的当前牌,for循环开始迭代,A[1..j-1]构成了当前左手已经排序后的牌,剩余的A[j+1..n]的元素对应还在桌面上的牌,也就是剩余为排序的元素。A[1..j-1]其实也是原来的A[1..j-1]元素集合,只是现在已经按照我们的插入排序进行了排列。我们将A[1..j-1],也就是在每次循环中都保证已经正确排序,不需要再次改变顺序的部分集合,相对于整个集合来说称之为“循环不变式”。

  

  循环不变式的三条必须证明的性质。

  

  1初始化 时候的特性:循环在第一次迭代之前,它为真

  

  2保持:如果循环在某一次迭代之前它为真,那么下一次迭代它还为真。

  

  3终止:在循环终止时,不变式为我们提供一个有用的性质,该性质有助于算法的是否正确性。

  

  为真的意思就是“循环不变式成立”。简单点说也就是拥有三条特性才称之为循环不变式。我们要在开始循环之前,循环过程中和循环结束的时候检查循环不变式是否正确。也就是已经排序的部分是否排序成功。

  

  第三条特性是最重要的。当然这里不少小伙伴已经有些吃力了,可能会认为这些是因为翻译,或者国内外术语,语法上的差异,造成难以理解。那么我负责任的告诉你,就算完美翻译对于我们来说也是晦涩难懂的,因为知识本身的难度就如此。但是有挑战才会有乐趣,才会有成就感,加油。接下来我们举一个列子来理解循环不变式。

  

  E

插入排序中的循环不变式。

  

  初始化:第一次循环之前,也就是j=2的时候,循环不等式成立。 j=2,那么循环不等式为A[1..2-1]也就是单独拥有第一条数据的一个数组。A[1],他自己是没有顺序的,也可以理解为自己和自己已经排序好了。

  

  这里需要说明的是为什么j从2开始,我们一直强调插入排序的流程与打牌类似。右手从桌子上起牌,在自己的左手上进行排序,起第一张牌就直接放到左手中,是不需要排序的,而是从第二张牌的时候才开始进行排序。

  

  保持:也就是每次循环保持循环不变式中已经排序。先说我们这里有两层循环。第一层循环,也就是伪代码中的for循环,表示依次的从桌面上起牌(从需要排序的数组A中取出一个元素)。总共循环次数是总牌数(数组A的长度)减一次。第二层循环,也就是伪代码中的while循环,表示拿着当前起到的牌和循环不变式中的元素进行比较找到位置进行插入。那么也就是说,在拿着新牌插入之前左手上的位置之前,左手上的牌已经是排序的。并且要求新牌插入到左手之后的新的循环不变式也是已经正确排序的。

  

  终止:也就是所有的循环结束之后发生了什么,我们经过两层循环之后最终得到的是所有元素组成的循环不变式,查看他的顺序,必须是已经正确排序的。那么次算法就是正确的。

  

  F

现有序列A=(5,2,4,6,1,3),插入排序的执行流程图为:

  

我们用文字结合图片和伪代码复述一下插入排序的流程:

我们设计一个算法,名字叫做INSERTION-SORT,输入一个无序序列,返回一个升序的序列。此算法支持参数A,类型为一个序列数组。当前A=(5,2,4,6,1,3);首先我们取出序列中的第二个元素,从它开始,依次找到每一个元素应该存在的正确位置,之所以从第二个元素开始的原因是因为第一个元素我们认为他自己已经排序,也就是是说(5)就是初始化时候的“循环不变式”。

第一次迭代从第二个元素开始,值为2,需要插入到循环不变式中,并且循环不变式要求此次迭代之前和之后循环不变式都为真,也就是2插入之后已经排序的部分数据需要为(2,5)。我们要将第二个元素和第一个元素比较,当前j=2,第一个元素的下标为i=j-1。如果A[i]>A[j]则两个元素位置调换。依次类推,到元素三的时候需要拿着元素3与已经排序的两个元素进行比较,直到找到自己的位置。其中的循环不变式是A[1..j-1]。比如j是5,那么保证A[1..5]之间的数据都已经排序正确。

代码:

A[i+1]=A[i] // 将上一个下标对应的值赋值给当前下标的元素;

i=i-1  

A[i+1]=key // 将当前下标的元素的值赋值给上一个下标的元素。


其实这里6,7,8行代码我们有些自己的想法,觉得还可以这么写;

A[j]=A[i]

A[i]=key


能力也有限,我已经尽量的将《算法导论》中讲解顺序进行了重排;晦涩的词汇进行了自己的描述;过于专业但是抛弃又不影响当前学习进度的知识点进行了排除。希望对各位的学习有所帮助,硬着头皮熬过前几个章节,往后会越来越。。。。更难熬的~,但是这个过程也是一个学习能力的提高与训练,不是么。

  

  G

  

  以上是这节课的主要内容,这节课主要内容不是插入排序本身,因为插入排序已经是一个足够简单的算法,我们基于他讲解了今后课程中需要使用到的基础知识。

  

  术语总结:1伪代码的约束和语法(注意和自己熟悉的编程语言的习惯是否有不同的地方);2什么是循环不变式和它的特性;3序列,数组;4循环计数器,循环变量;5过程,函数,方法

  //////////////////////////////////////////////////////

  H

  

  思考题:

  

  1说明A=(31,41,59,26,41,58)在插入排序中的执行流程。

  

  2重写INSERTION-SORT过程(可以理解是函数,方法),使降序排序

  

  3 考虑一下查找问题并给出一个算法:

  

  输入:n个数字的一个序列A=(a1,a2,a3,...,an)和一个值u.

  

  输出:下标i使A[i]=u,若u不在A中有对应的,则返回NIL.

  

  并且使用循环不变式验证自己的算法,确保三个特性正确。

  

  (其实就是给出一个算法计算u在A数组中的下标)


以上是关于第一部分算法基础之插入排序的主要内容,如果未能解决你的问题,请参考以下文章

排序算法之插入排序

重温基础算法内部排序之简单插入排序法

插入排序算法初学算法之排序--直接插入排序

基础算法系列之排序算法-3. 直接插入排序

重温基础算法内部排序之插入排序法

java基础算法之插入排序