利用for循环调用插入方法批量插入 一条失败_算法与数据结构(1):基础部分——以插入排序为例...

25eb9c64829b86823d1fdd6b4da1f74b.png

本文将会以插入排序为例,介绍算法与数据结构的基础部分。

插入排序

排序可以说是整个算法中最为基础,最为重要的一部分,而插入排序正是排序算法中最简单的一种解决办法。

什么是排序问题?

输入:n个数的一个序列

输出:输入序列的一个排列
,同时满足

而插入排序的思路和我们平时打扑克时整理排序的思路非常类似。开始时,我们手中没有牌,然后我们每次从桌上拿走一张牌,并将其插入到手中正确的位置,直到我们将牌整理完成。何为正确的位置?用规范一点的描述,可以认为,将第 n 张牌插入到已经排好序的 n-1 张牌中,并保证插入后依然保持有序。这就是插入排序的核心理念。

从感性上来说,我们需要整理多少张牌,就对应着需要插入几次,当然第一次插入时,因为手中没有牌,所以无需考虑什么地方是正确的位置。

如下图是一个插入排序的展示:

ddab06df5d5d48ac8793178ac860e3eb.gif

我们可以使用如下代码实现:

#include 

一个算法最为重要的要求是正确性,在正确性的前提下尽量降低时间复杂度和空间消耗。

正确性分析

通常来说,一个值得我们研究的算法的核心部分是循环或者递归(因为如果不包含循环和递归,那么就意味着这个算法是线性的,那么关于这个算法也就没有性能上的区别了,那么就可以说这是一个不值得我们研究的算法),为了能够论证这个算法的循环部分(递归也是可以利用循环实现的)是正确的,我们通常会使用一种叫做循环不变式的概念。

利用循环不变式论证算法的正确性,主要分为三个步骤:

初始化:循环的第一次迭代之前,它为真。保持:如果循环的某次迭代之前它为真,那么下次迭代之前它仍为真。终止:循环终止时,不变式为我们提供了一个有用的性质,该性质有助于证明此算法是正确的。

看到这里,相比很多读者已经意识到,这就是数学中的“数学归纳法”。那么将这三步用于上文中的插入排序,效果会是如何呢?

初始化:在第一次迭代之前,即当 i = 1 时,循环不变式成立,因为子数组此时仅由1个元素组成,即array[0]。此数组必然是有序的。保持当前 i 个元素组成的子数组已经有序后,我们准备插入第 i+1 个元素,代码的第21-24行的部分,保证了所有大于第 i+1 个元素的数据均在此元素右方,所有小于等于第 i+1 个元素的数据均在此元素左方,则当插入此元素后,前 i+1 个元素组成的子数组有序终止:循环终止的条件是 i == array_length,即前 array_length 个元素组成的子数组有序,即整个数组有序

通过这三步分析,我们证明了这个算法的正确性,以后的文章我们提到的所有算法,均可以通过此方法分析其正确性。虽然我们在后期可能会更加强调一个算法的运行效率,对于一些显而易见的正确性可能会略过不讲,但这并不意味着正确性分析不重要,仅凭作者的经验来看,很多学习算法的同学容易陷入一个怪圈,只着眼于运行效率而忽略了算法本身正确性。

运行效率分析

对于一个算法的运行效率最直接的判断方式是,将其写为一个程序并运行测速。但在大部分情况下,这是不合理的。例如同一个算法由不同的程序员编写,用不同的语言编写,用不同的运行环境来测试,或者用不同的硬件设备来运行,都会有着巨大的差异,如果有的算法非常复杂或者数据量很大,需要几个小时甚至几天才能运行完成,我们需要一直等它运行完成,才能评判这个算法的运行效率吗?显然用实际运行并计时的方式来评判一个算法的运行效率在大部分情况下是不合理的。

对此,我们引入了一种思想模型——随机访问机(RAM)来评估一个算法的运行效率。

RAM模型包含真实计算机中常见的指令:算术指令(如加法,减法,乘法,除法,取余、向下取整,向上取整),数据移动指令(装入、存储、复制)和控制指令(条件跳转,无条件跳转,子程序调用与返回),上述这些指令所需时间均为常量,即这些指令运行时间不变)。同时,在实际中,计算机包含多级存储,但为了简化问题,我们一般不考虑高速缓存,虚拟内存等多级存储,简化为只有硬盘与内存的区别。再说了,现在技术变的这么快,说不定以后就没cache了呢,考虑啥呀真的是。这一段没有看懂不要急,下面会举一个例子,看了例子后相信大部分读者就能明白了。

有了模型,现在就需要考虑如何进行评估。对于一个算法,输入规模能够很大程度的影响运行时间,例如插入排序中对10个数进行排序和对10亿个数进行排序。对此,我们引入了输入规模与运行时间的概念。

输入规模:输入规模很大程度上依赖于具体问题来进行分析。例如排序中,输入规模一般认为是需要排序的数据个数;如果分析两个整数相乘,则输入规模一般认为是两个数的总位数;如果需要研究图论问题,那么输入规模一般认为是图中的顶点数与边数。什么是输入规模没有一个具体的规定,但一般而言就是指这个影响这个算法运行时间的最重要的那一个属性,这个需要读者们多学习积累经验,才能迅速的分析出一个问题的关键所在。运行时间:这个运行时间不是指实际运行时间,而是在RAM模型中需要执行的基本操作步数。什么是基本操作步数?也就是我们上文中提到的RAM模型中的常见指令,运行一条指令就是一步。虽然不同指令的运行时间不同,但他们之间的差异都是常量级的,例如加法指令可能需要1ms运行完成,乘法指令需要10ms运行完成,他们的比例是1:10,运行100条加法指令与运行100条乘法指令所需时间比例依然是1:10,这个常数时间的差异在我们后面面对的动辄 指数级 差异的比较之下,就可以忽略不计了。

为什么我们会忽略运行时间的常数差异呢?一个很简单的道理,如果我们的计算规模很小,例如排序十个数,不同算法之间差距可能只有不到1ms,这点时间与你点击运行,电脑突然卡了一下等事情需要花费的时间相比,简直微不足道,甚至都没必要去优化算法了。而如果计算规模很大,例如要排序全国人民的身份证号,差一点的算法(插入排序同学,说的就是你,别东张西望了)可能需要几年,而好一点的算法可能只需要几个小时,不同算法之间的运行时间差异甚至是指数级的,这一点常数差异真的没有什么影响,除非两个指令之间运行时间比例能达到百万级之类的,那你干脆把差距这么大的指令也当做算法来研究研究优化一下了吧。等学习深入以后,读者们也会发现,我们大部分时候只考虑运行时间输入规模之间的关系,如是指数关系还是线性关系等,同时评价一个算法好不好,大部分情况下也是考虑输入规模较大的时候的增长趋势

运行效率分析案例

现在我们以上文中的插入排序代码为例,进行一下运行效率的分析,我们此次分析会将常数差异考虑进去,以印证我们前文所说的,当输入规模较大时,常数差异不重要的结论。

e83b2af8ba9c2967ce8cdfdad92bdd20.png

表格中 n 表示输入规模,即array_length,

表示执行while循环的次数,由于 } 并不占用运行时间,故运行一次所需代价为0。

那么我们将上述所有运行时间相加,所得结果为

现在我们又面临一个新的问题,对于同一规模的输入,如果输入不同,其运行时间也是不同的。例如在此例中,如果输入的数组就是已经升序的,那么

化简一下,

可以将此记为

即在插入排序的最好情况下,最后的运行时间与输入规模之间是线性关系

如果输入的数组是降序的,那么

化简一下,

可以将此记为

即在插入排序的最坏情况下,最后的运行时间与输入规模之间是二次函数关系

运行效率分析总结

在我们上面的例子中,我们既研究了最佳情况,也研究了最坏情况,但一般而言,实际中我们的分析主要在于最坏情况,我们以后的文章也将会主要着眼于最坏情况的分析。对此,我们有以下理由:

  • 一个算法的最坏情况给出了任何输入的运行时间的一个上界,我们可以保证该算法不需要更长的时间,无需有更多担心。
  • 在实际中,最坏情况经常出现,例如我们编写一个网页,用户登录时,我们需要将登陆数据与数据库中的数据进行对比,在很多时候,由于用户的错误输入或者恶意输入,数据库中是没有这条记录的,所以需要检索整个数据库,也就是最坏情况。我们不能让这个最坏情况太差,因为如果只是让用户最快登陆时间从0.1秒增加到0.2秒是可以接受的,但当一个用户错误登陆时,需要所有用户一起卡一天,建议以“蓄意破坏计算机信息系统罪”立即去公安机关自首或者直接击毙。
  • 一般而言,平均情况与最坏情况差距不大,可能只有一个常数的差异。如插入排序中,如果插入第n个数时,平均情况下是在第n/2个数时插入,最坏情况是在第n个数插入,只有一个2倍关系,所以平均情况的运行时间只是将最坏情况的运行时间除以2而已,依然是二次函数关系,并没有本质上的区别。
  • 当然我觉得还有另外一个原因:最坏情况很好算!!!而平均情况很多时候真的很难算的!!!为了一个只需要花5分钟写完的算法,我花了10分钟去分析它的平均情况,我也很难的好吧,这么多时间我拿去拯救世界不香吗。

增长量级

虽然说我们可以具体计算出一个算法的最坏情况,但也挺花时间的。同时我们也注意到,对于一个二次函数关系,当n足够大时,只有二次项才是影响其大小的关键,这也就引出了数学中的渐进分析的概念,也就是我们的增长量级,即大家常说的那个讨人厌的大O符号,小O符号。具体含义我们将在下一篇文章中介绍,在这里我们只需要先知道一个结论,在实际的算法效率分析中,我们真正感兴趣的是运行时间的增长率或者增长量级,即公式中最为重要的项,例如插入排序中的二次项。因为当n真的很大的时候,低阶项相对来说不太重要,而常数项在n逐渐变大的过程中,也逐渐不太重要了(在实际中,我们往往只关心数量级,例如百万级的数据,亿级的数据,在这种情况下,常数项是1还是10就没有必要关心了,毕竟这种时候它带来的影响连低阶项都比不上了)

结语

这是算法与数据结构系列的第一篇文章,下一章我将会以归并排序为例介绍时间复杂度的概念,再后面就是具体的算法与数据结构的分析了,从最基础的时间复杂度、空间复杂度开始,一步一步深入,比如搜索,排序,基本数据结构,各种神奇的树,图,矩阵等等,后面应该也会介绍一些动态规划,字符串处理等常用算法,以及一些简单的数论算法和数学知识吧。如果有兴趣的话我大家也可以关注我一波,没有意外情况的话,我应该可能或许说不定不会鸽吧。

原文链接:

albertcode.info​albertcode.info

个人博客:

albert的个人网站 - bug工厂​albertcode.info
2a29c695533f2769edfc26b5b6c8454d.png

微信公众号:AlbertCodeInfo

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

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

相关文章

MD5计算,一个扩展类,哪里都能用

最近有同学问到如何计算一个字节数组的MD5值,现在分享一个扩展类,有了它,MD5计算再也不用其他了。先看示例:string s "hello world";var s_md5 s.ToMD5();string f "C:\Windows\explorer.exe";var f_md5 f…

字符串之翻转字符串

题目: 给定一个字符类型的数组chas,请在单词间做逆序调整,只要做到单词顺序即可,对空格的位置没有特别要求。 例如: 如果把chas看作字符串为“dog loves pig”,调整成“pig loves dog”. 如果把chas看作字符串为“Im a student.”,调整成“student. a Im”. 代码: pac…

noi 4982 踩方格

题目链接:http://noi.openjudge.cn/ch0206/4982/ 深搜很好写。 DP:O(n) d[i] 为走 I 不的方案数, l[i],r[i],u[i]为第一步走 左,右,上,共走 i 步的方案数。(u[i] d[i-1]) d[i] l[i] r[i] u[i] l[i-1] …

VB6之GIF分解

原文链接:http://hi.baidu.com/coo_boi/item/1264a64172fe8dec1f19bc08 还是找了个C的翻译下,原文链接:http://www.360doc.com/content/05/1122/15/1894_34348.shtml GDI的函数声明 我就不放上了,网上有个做好的GDI公共模块。 Pri…

MindFusion Pack for ASP.NET发布v2013.R2

在MindFusion.Diagramming for WebForms中:导入OpenOffice Draw文件新的DrawImporter类允许你通过OpenOffice Draw Vector图形编辑器导入后缀为*.odg的文件。只需添加一个引用到MindFusion.Diagramming.Import.Draw.dll组即可。你还可以使用导入法的多种重载将OpenO…

python中的常量可以修改吗_python实现不可修改的常量

因为种种原因,Python并未提供如C/C/Java一样的const修饰符,换言之,python中没有常量,至少截止2015年年末,还没有这个打算。Python程序一般通过约定俗成的变量名全大写的形式来表示这是一个常量,但是这终究不…

字符串之数组中两个字符串的最小距离

题目: 给定一个字符串数组strs, 再给定两个字符串str1和str2,返回在strs中str1和str2的最小距离,如果str1和str2为null,或者不再strs中,都返回-1 列如: strs = {"1","3","3","2","3","1","3"} ,…

【翻译】C#表达式中的动态查询

当您使用LINQ来处理数据库时,这种体验是一种神奇的体验,对吗?你把数据库实体像一个普通的收集,使用Linq中像Where,Select或者 Take,这些简单的使用就能让代码可用了。但是,让我们考虑一下这里是…

SVD++:推荐系统的基于矩阵分解的协同过滤算法的提高

1.背景知识 在讲SVD之前,我还是想先回到基于物品相似的协同过滤算法。这个算法基本思想是找出一个用户有过正反馈的物品的相似的物品来给其作为推荐。其公式为: 其中 rui 表示预测用户u对物品i的喜爱程度。wij 是物品i,j之间的相似度&#xf…

资源文件(.RES)的应用

资源档有什麽用处呢?最重要的有两个地方1.国际发行:我们将Application中所有的文字从Resource用读取,那麽,只要更动 Resource档的内容,就可以用不同语言的方式来显示。2.管理资源:例如说&#x…

Linux下Tomcat设置自动启动

在linux系统下,设置某个服务自启动的话,需要在/etc/rcX.d下挂载,还要在/etc/init.d/下写启动脚本的。 1、我们在/etc/init.d/下新建一个文件tomcat(需要在root权限下操作) vi /etc/init.d/tomcat#!/bin/sh # chkconfig…

Android插件化开发之动态加载的类型

https://segmentfault.com/a/1190000005113493 基本信息 Author:kaedea GitHub:android-dynamical-loading 现在网络上有许多关于动态加载的介绍的文章,谈及的关键词汇有动态加载、插件化、热部署、热修复等,对于一些刚接触这方…

c# 爬虫 -ChromeDriver+HtmlAgilityPack爬取比赛实时比分

背景最近NBA总决赛吸引了不少球迷,但是因为时差的关系,人家在比赛,我们在上班,有时候上班又不好意思光明正大的看比赛,那有什么办法 可以光明正大的看又不被发现呢。有,自己动手丰衣足食,Chrome…

Codevs2157 配对

题目描述 Description给出2个序列A{a[1],a[2],…,a[n]},B{b[1],b[2],…,b[n]},从A、B中各选出n个元素进行一一配对(可以不按照原来在序列中的顺序)&#xff0…

UITableView的优化原理

2019独角兽企业重金招聘Python工程师标准>>> 当我们下啦一个 UITableView时,如果没有做优化,只是简单的实现功能代码如下,这样当我们有上百条tableviewcell的时候,我们滑动的非常快时会非常费内存,当然苹果…

深入浅出Mybatis系列(一)---Mybatis入门[转]

最近两年 springmvc mybatis 的在这种搭配还是蛮火的,楼主我呢,也从来没真正去接触过mybatis, 趁近日得闲, 就去学习一下mybatis吧。 本次拟根据自己的学习进度,做一次关于mybatis 的一系列教程, 记录自己的学习历程&…

字符串之括号的有效性

题目: 给定一个字符串str,判断是不是整体有效的括号字符串 举例: str = "()" return true; stre = "()()" return true; str = "())" return false; str = "()a()" return false; 代码: package com.chenyu.string.cn;public class…