写指数函数,只是无数解决方案的一种,还有其它方案。
用不一样顺序写不一样语句,也能获得同样的结果,不一样的是“算法”,意思是:解决问题的具体步骤。
即便结果一致,有些算法会更好。
通常来讲,所需步骤越少越好。不过有时候也会关心其余因素,好比占多少内存。node
“算法”一词来自 阿尔●花拉子密,1000多年前的代数之父之一。
如何想出高效算法,是早在计算机出现以前就有的问题,诞生了专门研究计算的领域,而后发展成一门现代学科,计算机科学。程序员
选择排序
记载最多的算法之一是“排序”。好比给名字,数字排序。
排序处处都是,找最便宜的机票,按最新的时间排邮件,按姓氏排联系人等等,这些都要排序。
计算机科学家花了数十年发明了各类排序算法,还起了酷酷的名字,“冒泡排序”, “意面排序”,“选择排序”算法
好比:
一堆机票价格,都飞往目的地。把价格一组数据存放到一个数组结构。
先找到最小数,从最上面开始,而后和第一个比较,因此看最小的数是否变化,移动位置。重复循环比较,持续移动位置。
意味着,若是要排N个东西,要循环N次,每次循环中再循环N次,共N*N。编程
算法的输入规模和运行步数之间的关系,叫算法的复杂度。
表示运行速度的量级。
计算机科学家们把算法复杂度叫:大O表示法(big O notation)。数组
选择排序的算法复杂度O(n^2)效率不高。数据结构
归并排序
第一件事是检查数组大小是否>1,若是是,就把数组分红两半。再检查数组大小是否>1,若是是,继续分组,若是不是开始“归并”。
从前两个数组开始,读第一个(也是惟一一个)值,而后开始比较,若是更小,排在以前。重复这个过程,按序排列。而后数组大小增长,再次归并。
一样,取前二个数组,比较第一个数,而后再比较第一个数组的第一个数,第二个数组的第二个数。而后合并成更大有序的数组。
直到排序完整。编程语言
归并排序的算法复杂度O(n * logn),n是须要比较+合并的次数和数组大小成正比,logn
是合并步骤的次数。函数
图搜索
“图”是用线链接起来的一堆“节点”。能够想成地图,每一个节点是一个城市,线是公路。
一个城市到另一个城市,花的时间不一样,能够用成本(cost)或权重(weight)来代称,表明要几个星期。
假设想找“高庭”到“凛冬城”的最快路线。
最简单的方法是尝试每一条路,计算总成本。这是“蛮力办法”。测试
假设用蛮力方法,来排序数组,尝试每一种组合,看是否排好序。
这样的时间复杂度是: O(n!)
,n
是节点数,n!
是阶乘。比O(n^2)
还糟糕。人工智能
从“高庭”开始,此时成本为0,把0标记在节点里,其它城市标记成问号,由于不知道成本多少。Dijkstra 算法
老是从成本最低的节点开始,目前只知道一个节点“高庭”,因此从这里开始,跑到全部相邻节点,记录成本,完成一轮算法。
可是还未到“凛冬城”,因此再跑一次Dijkstra 算法
。
下一个成本最低的节点,是“君临城”,记录相邻节点的成本,到“三叉戟河”,然而想记录的是,从“高庭”到这里的成本,因此“三叉戟河”的总成本8+5=13。如今走另一条路到“奔流城”,成本高达25,总成本33,但“奔流城”中最低成本是10,因此无视新数字,保留以前的成本10。如今看了“君临城”的每一条路还没到“凛冬城”因此继续。
下一个成本最低的节点,是“奔流城”,要10周。先看到“三叉戟河”成本,10+2=12,比以前的13好一点,因此更新“三叉戟河”为12,“奔流城”到“派克城”成本是3。10+3=13,以前是14,因此更新“派克城”为13。
“奔流城”出发的全部路径都走了遍,还没到终点,因此继续。
下个成本最低的节点,是“三叉戟河”。从“三叉戟河”出发,惟一没看过的路,通往“凛冬城”。成本是10加上“三叉戟河”的成本12,总成本是22。再看最后一条路,“派克城”到“凛冬城”,总成本是31。
因此最佳线路是:“高庭” -> “奔流城” -> “三叉戟河” -> “凛冬城”。
Dijkstra
的语录:
“有效的程序员不该该浪费不少时间用于程序调试,他们应该一开始就不要把故障引入。”
“程序测试是代表存在故障的很是有效的方法,但对于证实没有故障,调试是很无能为力的。”
Dijkstra 算法
算法复杂度是:O(n^2)
通过改造的Dijkstra 算法
复杂度减低为:O(n * logn + l) n是节点数,l是多少条线
算法处理的数据,存在内存里的格式是什么。
但愿数据是结构化,方便读取,所以计算机科学家发明了“数据结构”。
数组
数组Array,也叫列表(List),或向量(Vector)。有一些区别,在不一样语言中基本相似。
数组的值一个个连续存在内存里。能够把多个值存在数据变量里,为了拿出数组中的某个值,要指定一个下标(index)
大多数编程语言中,数组下标都从0开始。
用方括号[]表明访问数组。
j = [5, 3, 7, 21, 82, 4, 19] a = j[0] + j[5]
数组存在内存里的方式,十分易懂。
为了简单,假设编译器从内存地址1000开始存数组,数组有7个数字,像上图同样按顺序存。
写j[0]
,会去内存地址1000,加0个偏移,获得地址1000,拿值: 5。
写j[5]
,会去内存地址1000,加5个偏移,获得地址1005,拿值: 4。
数组的用途普遍,因此几乎全部的编程语言,都自带了不少函数来处理数组。
字符串
数组的亲戚是字符串String,其实就是字母,数字,标点符号等,组成的数组。
计算机怎么存储字符?
经过字符对应的ASCII表
写代码时,用引号括起来就好了: j = "STAN ROCKS"
虽然长的不像数组,但的确是数组。
注意:字符串在内存里以0结尾,不是“字符0”,是二进制“0”,这叫字符“null”,表示字符串结尾。
这个字符很是重要,若是调用print()
函数,会从开始位置,逐个显示到屏幕,可是得知道何时中止下来。不然会把内存里全部东西都显示出来。0告知函数什么时候停下。
由于计算机常常处理字符串,因此有不少函数专门处理字符串。
矩阵
能够用数组作一维列表,但有时想操做二维数据,好比电子表格,或屏幕上的像素。那么须要 矩阵Matrix
。
能够把矩阵当作数组的数组,内存中的表示:
j = [[10, 15, 12], [8, 7, 42], [18, 12, 7]]
为了拿一个值,须要两个下标,好比j[2][1]
,告知计算机在数组2里,位置是1的元素。
结构体
把几个有关系的变量存在一块儿,会颇有用。多个变量打包在一块儿叫 结构体(Struct)
多个不一样类型数据,能够放在一块儿。
这些数据在内存里,会自动打包在一块儿,若是写j[0]
,能拿到j[0]
里的结构体,而后拿具体数据。
存结构体的数组,和其它数组同样,建立时就有固定大小,不能动态增长大小。还有,数组在内存中,按顺序存储。在中间插入一个值很困难。但结构体能够创造更复杂的数据结构,消除这些限制。
节点和链表
一个结构体,叫节点(Node)。它存一个变量,一个指针(pointer)
“指针”是一种特殊的变量,指向一个内存地址,所以得名。
用节点能够作链表(linked list),链表是一种灵活数据结构,能存不少个节点(node), 灵活性是经过每一个节点指向,下一个节点实现的。
假设有三个节点,在内存地址1000,1002,1008。
内存地址隔开,多是建立时间不一样,它们之间有其它数据,能够看到第一个节点,值是7,指向地址1008,表明下一个节点,位于内存地址1008,来到下一个节点,值是112,指向地址1002,往下一个节点,地址1002的值是14的节点,这个节点,指回地址1000,也就是第一个节点,这个叫循环链表。
但链表也能够是非循环的,最后一个指针是0,null
,表明链表的尽头。
当程序员用链表时,不多看指针具体指向哪里,而是用链表的抽象模型。
数组大小须要预先定好,链表大小能够动态增减,能够建立一个新节点,经过改变指针值,把新节点插入链表中。
链表也很容易从新排序,两端缩减,分割,倒序等。
由于灵活,不少复杂的数据结构都用链表。
最出名的是 队列(queue)和栈(stack)。
队列和栈
“队列”就像邮局排队,谁先来就排在前面。先进先出(FIFO)。
有个指针,指向链表的第一个节点,第一个节点是Hank
,服务完Hank
以后,读取Hank
的指针,把指针指向下一我的,这样就把Hank
“出队”(dequeue)了。
若是想加到队列里,“入队”(enqueue)。
要遍历整个链表到结尾,而后把结尾的指针,指向新人(Nick)。
只要稍做修改,就能用链表作栈,栈是后进先出(LIFO)。能够把“栈”想成一堆松饼,作好一个新松饼,就堆在以前的上面,吃的时候,是从最上面开始吃的。栈出入叫“入栈(push)”,“出栈(pop)”。
树
若是节点改一下,改为2个指针,就能作成树(Tree)
不少算法用了树,这种数据结构,一样程序员不多看指针的具体值,而是把树抽象成:
其中有一个特例,节点就2个,叫“二叉树(Binary Tree)”。
节点能够用链表存全部的子节点。
“树”的一个重要性质是(无论是现实仍是数据结构中):“根”到“叶”都是单向的。
若是数据随意链接,包括循环,能够用“图”表示。这种结构,能够用有多个指针的节点表示,所以没有根,叶,子节点,父节点这些概念。能够随意指向节点。
不一样数据结构使用于不一样场景,选择正确数据结构会让工做简单。
计算机科学之父,阿兰●图灵。
于1912年出生伦敦,从小就表现出惊人的数学和科学能力。
对计算机科学的建树始于1935年,他开始解决德国数学家提出的问题:可断定性问题,是否存在一种算法,输入正式的逻辑语句,输出准确的“是”或“否”答案?
美国数学家阿隆佐●丘奇于1935年首先提出解决方法,开发了一个叫“Lambda 算子”的数学表达系统,证实了这样算法不存在。
虽然“Lambda 算子”能表示任何计算,但它使用的数学技巧难以理解和使用。
图灵想出了本身办法来解决“可断定性问题”,提出了一种假想的计算机,如今叫“图灵机”。
图灵机提供了简单又强大的数学计算模型,虽然用的数学不同,但图灵机的计算能力和Lambda 算子同样。同时由于图灵机更简单,因此在新兴的计算机领域更受欢迎。
图灵机是一台理论计算设备,有无限长的纸带,纸带能够存储符号,能够读取和写入纸带上的符号,还有一个状态变量,保存当前状态,还有一组规则,描述机器作什么。规则是根据:当前状态+读写头看到的符号,决定机器作什么。结果多是在纸带写入一个符号,或改变状态,或把读写头移动一格,或执行这些动做的组合。
简单例子:
让图灵机读一个以0结尾的字符串,并计算1个出现次数,是否是偶数。若是是,在纸带上写一个1,若是不是,在纸带上写一个0。
首先要定义“图灵机”的规则:
定义好了 起始状态+规则,就像写好了程序,如今能够输入了,假设把“1 1 0”放在纸带上,有两个1,是偶数。
规则只让读写头向右移动,其它部分可有可无,为了简单留空。
图灵机开始运行:
若是有足够时间和内存,能够执行任何计算,它是一台通用计算机。
只要有足够的规则,状态和纸带,能够创造任何东西。
因此,图灵机是很强大的计算模型。
事实上,就可计算和不可计算而言,没有计算机比图灵机更强大,和图灵机同样强大的,叫“图灵完备”。
每一个现代计算系统,好比笔记本电脑,智能手机,甚至微波炉和恒温机内部的小电脑,都是“图灵完备”。
停机问题
为了回答可断定性问题,把图灵机用于一个有趣计算的问题:停机问题。
简单说就是:给定图灵机描述和输入纸带,是否有算法能够肯定,机器会永远算下去仍是会到某一点会停机?
有没有办法在不执行的状况,弄清会不会停机呢?一些程序可能要运行好几年,因此在运行前知道,会不会出结果颇有用。不然就要一直等啊等,忧虑到底会不会出结果。图灵经过一个巧妙的逻辑矛盾,证实了停机问题是没法解决的。
想象有一个假想图灵机,输入: 问题的描述 + 纸带的数据,输出“yes”表明会停机,输出“no”表明不会停机。
推理:若是有个程序,H(halt的第一个字母)没法判断是否会“停机”,意味着“停机问题”没法解决。
为了找到这样的程序,图灵用H设计了另外一个图灵机,若是H说程序会“停机”,那么新机器会永远运行(即不会停机),若是H的结果为No,表明不会停机,那么让新机器输出No,而后“停机”。
图灵机1(H): 输出yes->停机, 输出no->不会停机
图灵机2:H(yes) -> 不会停机,永远运行, H(no)-> 机器停机,输出no
实质上是一台和H输出相反的机器,若是程序不停机,就机器停机。若是程序停机,就机器永远运行下去。
还须要在机器前面加一个分离器,让机器只接收一个输入,这个输入既是程序,也是输入。把这台新机器叫“Bizzaro(DC漫画的一名反派角色,他的能力和超人相反)”
若是把“Bizzaro”的描述,做为自己的输入会怎样,意味着在问H,当“Bizzaro”的输入是本身时,会怎样。
但若是H说“Bizzaro”会停机,那么“Bizzaro”会进入无限循环,所以不会停机。
若是H说“Bizzaro”不会停机,那么“Bizzaro”会输出No而后停机。
因此H不能正确断定,停机问题,由于没有答案,这是一个悖论,意味着“停机问题”不能用图灵机解决。
图灵证实了图灵机能够实现任何计算。
可是,“停机问题”证实了,不是全部问题都能用计算解决。
丘奇和图灵证实了计算机的能力有极限。
不管多少时间或内存,有些问题,是计算机没法解决的,计算是有极限的,起步了可计算性理论,称之为:“丘奇-图灵论题”。
图灵测试
当时是1936年,图灵只有24岁,在1939年,二次世界大战,图灵的才能很快被投入战争,事实上,在战争开始前一年,已经在英国政府的密码破译研究。工做之一,是破解德国的通讯加密,特别是“英格玛机(Enigma)”加密信息。
简单说,英格玛机会加密明文,若是输入字母H-E-L-L-O
,机器输出X-W-D-B-J
,这个过程叫“加密”。
文字不是随便打乱的,加密由“英格玛机”顶部的齿轮组合决定。每一个齿轮有26个可能位置,机器前面还有插板,能够将两个字母互换。总共有上十亿种可能。
知道正确的齿轮和插头的设置,输入 X-W-D-B-J
就会输出hello
,解密了这条信息。
有数十亿的组合,根本无法手工尝试全部组合。英格玛机和操做员不是完美的,一个大缺陷是:字母加密后毫不会是本身。H加密后绝对不是H。
图灵在前人的基础上,设计了一个机电计算机,叫: Bombe
。利用了这个缺陷,它对加密消息尝试多种组合,若是发现字母解密后和原先同样,就知道英格玛机不会这样子作,这个组合会被跳过,接着试另外一个组合,Bombe
大幅度减小了搜索量,让破译人员把精力花在更有可能的组合,好比在解码文本中找到经常使用的德语单词。
德国人时不时会怀疑有人在破解,而后升级英格玛机,好比加一个齿轮,创造更多可能组合,甚至还作了全新的加密机,整个战争期间,图灵和同事都在努力破解加密,解密到德国情报,为盟军赢得了不少优点,有些史学家认为他们把战争缩短好几年,战后,图灵回到学术界,为许多早期计算机工做做出贡献。
好比曼切斯特1号,一个早期有影响力的存储程序计算机。但他最有名的战后贡献是“人工智能”。这个领域很新,直到1956年才有名字。
1950年,图灵设想了将来的计算机,拥有和 人类同样的智力,或至少难以区分。
图灵提出若是计算机能欺骗人类相信它是人类,才算是智能。这成了智能测试的基础,现在叫“图灵测试”
想象在和两我的沟通,不用嘴或面对面,而是来回发消息,能够问任何问题,而后会收到回答,但其中一个是计算机,若是分不出哪一个是人类,哪一个是计算机,那么计算机就经过了图灵测试。
这个测试的现代版叫:“公开全自动图灵测试,用于区分计算机和人类”,简称,验证码,防止机器人发垃圾信息等。
图灵于1954年服毒,年仅41岁。
因为图灵对计算机科学贡献巨大,许多东西以他命名,其中最著名的是“图灵奖”(计算机领域的最高奖项)。