举个例子
有一道题叫过河问题:
一个农夫带着一只狼、一只羊、一颗菜要过河。
过河需要农夫划船,而船上空间有限,农夫一次只能带狼、羊、菜其中之一,农夫不在时,狼要吃羊,羊要吃菜。问该怎么办?
最初看到这道题,我只是一步步的试,就像解象棋残局一样。后来发现可以用数学来建模,用4位数来分别代表农夫、狼、羊、菜,这样初始状态0000,过河完成是1111。用数据结构里的树
来表示就是这样:
代码在这里,得出步骤为:
0000 -> 1010 -> 0010 -> 1011 -> 0001 -> 1101 -> 0101 -> 1111
这道题可以有其他的延伸,比如一家人过河、野人过河,也可以用图来展示。但只需要根据用数字来表示状态的思路,就可以得出解法。
除了树,数据结构里还有数组、堆、栈、队列、链表、图也可以用来表示实际的问题。比如说如何对2048-AI程序算法分析。
所以,在读过书/学习过/解决过问题的人眼里,简单抽象的东西有了的不同的含义。
实际运用
当然,实际解决问题的过程中,需要考虑很多边际条件。比如leetcode的第1题:
给定一个整数数组和一个目标值,找出数组中和为目标值的两个数。
你可以假设每个输入只对应一种答案,且同样的元素不能被重复利用。
示例:给定 nums = [2, 7, 11, 15], target = 9 因为 nums[0] + nums[1] = 2 + 7 = 9 所以返回 [0, 1]
在题目里已经设定了每个输入只对应一种答案,且同样的元素不能被重复利用,这就是已经对数据进行过预处理了。当然可能有的数据确实具备这样的特性,比如这篇关于马蜂窝评论数据的文章就提到:
我刚刚工作的时候,在书上看到的一个例子让我印象深刻。天上的飞机需要知道航向,所以Plane对象有个成员变量是heading。通常我们会用一个整型数值表示它,但是那本书上提到这是不对的!因为航向只能在0度到360度之间,所以你用整型数值时一定需要给setter加上一段逻辑,确保这个值只能设置在0到360之间。否则,难保其它系统读到heading时不会出错。
“只有加上了对应的约束,变量才不再是原始普通的数字,而变成了能承载业务价值的数据。”...
在设计软件系统时,领域知识往往是最重要的,而领域知识的一大部分就在于识别领域中的各种约束。这些约束很难由产品经理巨细靡遗地穷举出来,而必须由参与开发的所有人达成共识,在系统里实现它。但是无论如何,这些约束都是必不可少的,没有它们,系统就很可能出现各种稀奇古怪的现象。
所以一个软件的代码可能只有20%的代码是在实现业务功能,其他80%都是在处理各种异常情况——这些异常情况很多都是输入数据不满足约束而发生的。
迁移一下,假如现在要发布一个新的版本,稳妥起见,可能会进行上线前的AB测试、并行运行。正式切换时还需考虑到可能的异常以及解决方案,如异常不能解决,如何回退等。
有的面试官喜欢问基础,有的面试官喜欢问思路,我听说有的面试官在问思路之后,还要求给出几个测试用例。这就是在考察知识、思路之外,对问题是否有全局的、详细的理解。
每个程序员面对的具体问题都是不同的,需要根据问题的实际情况来处理。比如淘宝的架构变化,最初业务量不大时,单数据库就足够了;后来因为业务量变大,逐步演变成数据库集群与缓存、搜索引擎等相配合的架构。
运用之妙,存乎一心
现在的新技术层出不穷,这篇2016年里做前端是怎样一种体验就结结实实的对各种新名词吐槽了一番。
虽然技术很多,但很多技术都可以用已有的思想去理解,这一个回答里提到:
作为一个表现一般的排序算法,冒泡排序本身出场率就不高;何况还有各种提供了泛化的sort算法的库:如果仅仅记下了这个,那么你一辈子都不会遇到”必须重写冒泡算法“的场合。但,如果你把冒泡算法记成:
就好象水中的气泡一样,每次只执行“相邻的元素比较密度(或其它特征),密度小的上浮,密度大的下沉”这个局部物理过程;多次进行后,局部有序就会变成(相关特征上的)整体有序。甚至:
模仿各种会导致整体有序现象的局部过程去处理数据,可以使得数据整体上满足类似的排布。
甚至:
考察任何自然规律,看它会产生什么有趣的后果;那么当需要达到类似的效果时,不妨尝试用程序模拟出这个规律,很可能就已经得到了想要的效果。那,你这一生,可就受用不尽了。
上面这个回答里没提到具体运用,除了经典的TopK问题可以使用冒泡排序之外,我设想了一个场景:下载或者直播,因为网络的原因,必然会出现数据包乱序收到,但大多情况下都只是发生在局部,这个时候刚好可以采用冒泡排序。
所谓运用之妙,存乎一心,高手要么就像这位用数学降维打击的大神,要么就像这篇文章里说的,改进寻找附近单车的算法。
最笨的方法大概就是这样遍历了:
for(obj in objs){
// 判断obj与target的距离
if( distance(target, obj) < 1公里){
...
}
}
GeoHash
算法基于Z阶曲线,把二维平面划分成2*2的格子,Z阶曲线就是一条按0、1、2、3遍历空间的曲线:
这个时候我们可以记
f(0,0) = 0; f(0,1) = 1; f(1,0) = 2; f(1,1) = 3;
随着格子划分越来越细,Z形曲线就会在原基础上自相似的变化,来填充更多的空间,它的极限是填满整个空间。
GeoHash
算法按照如下规则,把数据在一维和二维之间相互转换:
1. (x,y)转换为二进制,如(3,2)->(11,10)
2. 转换之后按“从右到左,奇数位放x,偶数位放y”的方式组合,(11,10)->(1101)
3. 再次转化为十进制,(1101)->13
也就是说f(3,2)=14
下面这张图列出了8*8的格子下(x,y) -> z的值:
可以看到,每个格子都由上层划分而来,而相同来源的格子前缀相同,比如x∈[4,5],y∈[0,1]的四个格子,前缀都是0100。
这样,把每一辆共享单车的坐标转换成字符串或者二进制,就可以根据前缀建立索引,只取相同前缀的记录进行计算,效率可以说非常高了。
我对上述算法只能是膜拜,目前还停留在“快速排序、二分查找、Fork/Join计算等等都可以用分治法的思想去理解;中间件的设计思想也就是生产者-消费者模型;Java并发编程里锁的概念和数据库的事务隔离可以类比...”的阶段,但这样也足以使我在学新东西的时候不觉得太枯燥了,也就是我所理解的,简单抽象的东西有了的不同的含义。
归纳一下
表达的意思是3点:
- 建立模型,比如根据业务需要,在数据库里定义表、定义表之间的关系
- 定义计算,很多时候,只要定义好了数据模型,计算方法自然就有了。用0/1指代过河的状态后,推出0000->1111的计算规则就是水到渠成的事情。
-
优化方法,建立模型和优化方法的关系,就像牛顿提出三大定律和万有引力,而航天飞机的总设计师把飞机给造出来一样。孔子老子韩非提出了理论,但真正实践理论的另有其人,何夕的《伤心者》里有这么一段话
古希腊几何学家阿波洛尼乌斯总结了圆锥曲线理论,一千八百年后由德国天文学家开普勒将其应用于行星轨道理论。
数学家伽罗华公元1831年创立群论,一百余年后获得物理应用。
公元1860年创立的矩阵理论在六十年后应用量子力学。
数学J.H莱姆伯脱,高斯,黎曼,罗马切夫斯基等人提出并发展了非欧几何。高斯一生都在探索非欧几何的实际应用,但他抱憾而终。非欧几何诞生一百七十年后,这种在当时毫无用处的理论以及由之发展而来的张量分析理论成为爱因斯坦广义相对论的核心基础。上面这一段纯属我的个人趣味,大多数计算机相关的理论在计算机发明之前就已经出现了,我们大概只能不断在实际项目中进行优化,这也应该是想靠技术立身者前进的方向。
整体的思考就是目前这些,下面一段时间,还是多写一点具体技术的东西,就这样吧。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。