首先来看算法的概念与特征
算法的基本概念
其实在第一讲的时候已经谈到过了
什么叫算法呀
算法就是解决的问题方法和步骤
有一个问题放在那个地方
编程实现它
编程实现的时候
你要给出解决问题的方法和步骤
第一步应该做什么
第二步应该做什么
第三步应该做什么
把它描述清楚了就叫算法
对于编程来讲
要不仅仅给出算法的步骤
最终还要编程实现
从算法的特征角度来讲
算法实际上包括了五个基本特征
第一个叫有穷性
算法在任何一种情况下边
都可以在有限步内终止
是一个非常重要的要求
第二个叫确定性
算法的每一个步骤
都必须要有一个明确的意义
它不能够有二义性
这是算法的确定性
第三个叫输入
算法可以有0个或多个输入
一个算法有输入当然没有问题
比如Add函数
一个算法也可以没有输入
如果所有的数据都是隐含的
已经存在了 比如九九乘法表
九九乘法表里面只不过是打印1乘9
2乘9、3乘9、一直到9乘9它们的积
用一个二维的表格打印出来
所有的细节信息都已经隐含了
所以不需要输入一系列的数据
但是不管怎么样
这个算法一定是要有输出的
必须带回来一个结果
你写了一个程序最终的目的
是为了产生一个新的信息
可能就会有辩论 我前面写了函数
返回值是一个void
它的输出体现在当它事情做完了以后
计算机内部的状态发生了变化
比如讲Swap函数
x和y两个形式参数的值发生了变化啊
这个就是输出 隐含着了
并没有在函数的返回值里面体现出来
第五个叫有效性
算法中的所有的操作
都具有明确的意义
并且能够在有限的时间内完成
有穷性表示步骤是有限的
有效性表示时间是有限的
你说如果我要给它一个期限的话
我希望是一万年 你别觉得很长
写了一个程序
跑一万年才能出来一个结果
这是长得受不了
一秒钟、两秒种结果就出来了
这样的程序能够忍受
如果程序慢了 一分钟才能出来结果
在那个地方等啊等就已经不耐烦了
但如果要一万年才能出来结果
那黄花菜都凉了
事实上 压根都不是这么一种情况
因为有一些算法比如讲一个解密算法
如果没有找到一个很好的解决方案
只能穷举 一个接着一个地去做
当密码很复杂的时候 暴力去破解
最后一万年压根都解决不了问题
弄不好得要地球都毁灭了
几十亿年才能够把问题解决掉
这样的算法是无效的
算法一定要在有限的时间内能够完成
同学们要特别记住
正确性是必须要通过数学去证明的
写了一个算法 得证明算法是正确的
如果你不知道怎么证明
那么你就必须测试你的程序
测试 你的程序是正确的 这样才行
简单的算法
正确性实际上是不言自明的
有的时候没有办法证明的话怎么办呢
只能去测试
还有一种算法就完全不一样了
没有正确性的证明算法你压根就写不出来
好多科学家啊
最后为了实现很有效的算法
为此都得发好多篇论文才能够写出代码
有了这些定理之后
才能够完成算法的设计
看一个例子 弄一个3乘3的幻方
想法其实也蛮简单的
要把9个整数放在3乘3的九宫格里
然后让它横、竖、斜线
最后和是相同的 都是15
这个幻方啊 如果同学们看金庸同志写的
《射雕英雄传》你就记得了
黄蓉就解决了这个问题
当年是有个口诀的 我们这是机械的
你必须给我一个
解决问题的步骤和一个逻辑来
那么逻辑是什么 这就给了你提示
1放在第一行的正中间
这就是我们的算法
你看看它怎么实现
2写在1的右肩
你写上去 就超出了幻方的范围
然后把它平移下来
最右下角3写在2的右肩
又超出范围了 平移
三个数写完了
把4写在3的下面
5写在4的右肩
6写在5的右肩
又3个数写完了
把7写在6的下边
把8写在7的右肩 平移
9写在8的右肩 平移
这是3乘3的幻方
刚才描述是不是很有规律
找到这样的规律就可以编程序实现
这就是幻方的填充步骤
步骤1应该干什么
步骤2干什么 步骤3干什么
很明确地用文字把它写出来
就这样完成了
第二例子来看查英文单词
英语字典和汉语字典不一样
汉语字典有很丰富的索引
有拼音索引 有笔画索引 偏旁部首索引
以前的字典还有四角号码索引
很容易去找
可是英语字典就是一个顺序的排列
这个排列称为辞典序
怎么查英文单词呢
翻开辞典任意一页去找
看要找那个单词
是不是在第一个单词的前边
如果是在前边 说明翻多了 再翻前面去
然后比较单词是不是在
那一页最后一个单词的后面
如果在后面 说明翻少了 朝后再继续翻
如果单词在这一页的第一个单词
和最后一个单词的中间
那我们就要从第一个单词
到最后一个单词一个一个去比较它
看看是我们所要的
找着了那就是我们找到了这个单词
找不着就说明
这个字典没包括这个单词
或者这个单词压根就不存在
这个就是我们查英文单词的过程
同学们仔细思考一下这个逻辑
你看是不是满足算法特征
那肯定是满足的
有穷性、确定性、输入、输出、有效性
不都是完全满足吗
那么它同样就是一个算法
但是在这个地方
我要特别跟同学们说一下
就是如果你真地这样查单词
这个效率是不会高的
那问题在哪里
就是你翻开词典任意一页的动作
最快速的动作应该折半
在C++的编程语言里面这样的查找方式
我们称它为Binary Search 叫折半查找
我有一个词典放在这个地方
我“咔”从中间分
单词在前半部分呢 后半部分呢
如果在前半部分那不就意味着
整个后半部分全部都砍掉了吗
然后在前半部分呢
如法炮制啊 再砍一半
看在前半部分呢 后半部分呢
每次我都能够扔掉内容的一半
这样的话查找效率是非常非常高的
因为每次都扔一半
下次扔一半的一半
再下次保留一半的一半的一半
三次下去就变成八分之一了呀
所以实际上它是一个以对数的形式
迅速递减的查找方式
这是log以2为底的计算
这就叫折半查找
可能很多同学就会说啊
那我真实翻字典的时候
压根就不是这么干的
为啥啊 比如说我要查一个单词
它是以A开头的
比如说深渊Abyss
查这个单词 Abyss
它是以字母A开头
字典1000多页让你砍了一半
最后在500、600页去找
那符合逻辑吗 你肯定脑子里面一转
这压根就不对
怎么字母A会在那个地方呢
不可能啊
肯定是在头百十来页那个位置嘛
你会砍一半在500、600页去找吗 不会的
你立即就会在50、60页前面翻
这个是你的智力的启发式
编程的时候除非你使用了启发式
否则这个事情是做不了的
它不像你的头脑中可以做到这件事
我们编程机械语言
不是那么容易做到的
我们能够做到的最快的那种方案
就是折半 除非你启发式
你一开始就说了
A在哪一个附近 B在哪一个附近
C在哪一个附近
完全告诉它 这样一种情况
既然首字母是A
那直接去查A那一部分就完了
B以后部分全砍掉
这就叫启发式了
你的算法可以这么设计
但是如果没有它
折半查找效率是最快的