首页 » 算法神探:一部谷歌首席工程师写的CS小说 » 算法神探:一部谷歌首席工程师写的CS小说全文在线阅读

《算法神探:一部谷歌首席工程师写的CS小说》12 餐厅中的栈和队列

关灯直达底部

Frank俯下身子,快速地向门的方向跑去,他试着推门、拉门、踢门,但除了制造出巨大的声响外,一无所获。

Frank转向Socks,希望这位年轻的巫师知道可以让铁门扭曲的咒语。鉴于目前他们的处境,他相信Socks对使用开锁咒语应该没有意见。不过当Frank看到燃烧着的羊皮纸和向天花板上飘的烟雾时,他一时呆住了。他的脑海中浮现出了一个烟雾缭绕的厨房的景象,这让他回想起了在警察学院的第一年,他仿佛听到厨房中的厨师在对他大喊大叫。Frank用力地闭上了眼睛,试图将这个景象从脑海中抹去。

在警察学院的最初两个月,Frank利用课余时间在学校的餐厅打工。他干的工作并不怎么光鲜。新来的人都不会被安排着洗盘子,更不要说做饭了。Frank要做的是将一大堆洗干净的托盘、盘子和餐具从厨房拿到餐厅内的桌上,每个星期要花费15个小时。

即使工作很枯燥,Frank依然干得很开心。“看!我在给你们帮倒忙。我是你们这些勤杂工的天敌!”他会对那三个负责收拾桌子和脏乱餐具的勤杂工这样叫道。他试图去打破学院里两分钟内运送盘子数最多的纪录,但失败了。他还发明了一个可以在餐厅里玩的新游戏,叫扔勺子。但直到有一天,当他在餐厅内遇到Heappens教授的时候,他才学到了一些真正的知识。

“咳。有些数据结构就不应该存在于餐厅中。”Heappens教授边研究今天餐厅有哪些食物,边这样说道。

当时是下午两点半。吃午饭的人群高峰期已经过了。Frank Runtime正在努力将一堆碗运到放汤的地方。尽管教授那句话并不是对他说的,但他还是抬起了头,问道:“什么数据结构?”

“栈,”Heappens教授抬起头看着Frank说道,“栈不应该用在餐厅里。”

“不,它们用在这正好,”Frank用一种不知者无畏的语气说道,“你看,这些碗、盘和煎饼都是以栈的形式放着的。”

Heappens不屑地挥了挥手,准备走开,说道:“算了,你对数据结构又知道些什么?”

“不然你还能怎么摆它们?”Frank问道,“要是摊开来摆的话也太占空间了。”

教授停下来,十分担忧地看着Frank。这样看了快一分钟后,他说道:“你知道栈和队列的区别吗?”

Frank摇了摇头。他还没有上过警察数据结构这门课。

“栈是后进先出的数据结构,”教授解释道,“它支持两种操作:将一个东西放入顶端,以及从顶端拿出一个东西来。”

他指了指面前那一叠盘子,“就像这叠盘子一样。你可以把一个盘子放上去。”

他将手里拿的空盘子放了上去。

“也可以从顶端拿一个盘子下来。”他将自己的盘子拿了回来。

“每次你从栈里面拿出一个东西的时候,你拿到的都是最近才放上去的。最先被放进去的东西会一直待在栈的最底部,直到你把它上面的所有其他东西都拿出来。”

“所以呢?”Frank问道,“这有什么问题?”

“如果你正确使用它的话,后进先出的数据结构没什么问题。在你写深度优先算法的时候,栈有用极了。你只用每次将新的搜索选项放到栈的最顶端,然后在倒退的时候把它们从顶端拿出来就好了。但餐厅已经错误地用栈好几十年了。

“就比如这一叠盘子。你知道最下面那一个盘子已经在这待了多久了吗?”

Frank试图回忆他上次看到盘子这里空着是什么时候,但他根本想象不出来这个画面。

“五年!”Heappens教授叫道,“我知道,因为我对这个盘子标记过。五年来,你们这样的学生不断地把新的盘子放在最顶端,而最下面这个盘子从来没有被用过。它的边缘一直在那沾灰。

“但这并不是最可怕的。看看他们是怎么处理这些土豆泥的。”

Frank眼睛扫了扫旁边那一大木碗的土豆泥。一个厨师正在往里面加刚出炉的土豆泥。那个厨师手里拿着一个大锅,正愉快地用一个长柄勺把土豆泥从锅中揽到碗中。过了一会儿Frank才意识到厨师只是将原来的土豆泥埋在了最下面。他的胃开始不舒服了。

“最下面的土豆泥放了多久了?”Frank问道,其实Frank并不想知道答案。

“别担心。他们至少每周会将这些木碗洗一次。所以再久也不过一周。”

Frank并没有感到欣慰。事实上,他感到非常不舒服。快速扫了一圈,Frank看到餐厅内几乎每个地方都在用后进先出的数据结构。当他看到装沙拉酱的大桶时,他不敢再看下去了,他感到既慌张又恶心。

“我们能做些什么?”Frank问道。

“用队列,”教授回答道,“队列几乎可以说是为餐厅量身定制的。”

“队列?”Frank问道。

“先进先出的数据结构,”Heappens教授解释道,“像栈一样,它们同样是用来储存东西的,并且也支持两种操作。你可以将一个东西放到队列的最后,也可以从最前面拿出一个东西来。这样,你拿出来的永远都是最先放进去的东西。”

Frank想象了一下每次都从一叠盘子的最底下拿盘子,多麻烦,于是问道:“但如何做呢?”

“这正是数据结构如何运作的问题。看看等三明治的人所排的队,那就是一个队列。现在队里面有四个人,而最前面的人已经等待的时间最长。”

正当Heappens教授说着,又有一个人开始排队了。“看,他走到了队列的最后。”教授说道。

Frank和教授看着那条队伍,直到排在第一位的人拿着自己的三明治走了。

“看,有人从队伍最前面离开了,”教授开心地说道,“这个餐厅需要更多的队列。所有餐厅都需要更多的队列。”

Frank想到了之前看到的土豆泥,意识到教授说的没错。数据的储存方式可以很大程度上影响到它被存取的方式。对于土豆泥的这种情况,存取的顺序是很重要的。

即便意识到这点很容易,Frank花了好几天才把队列这种数据结构用到了餐厅里。改变盘子和碗存取的方式相对简单一些,他只需要把原来的那堆盘子或碗拎起来,然后把新的放在最底下就好了。说服厨师让他们改变加菜的方式就困难多了,厨师们非常享受将一大勺一大勺的土豆泥揽到碗里的这个过程。最终,Frank提议让他们用两个碗,每次将那碗旧的土豆泥揽到新的土豆泥的碗里。虽然严格来说这还不是一个队列,但这样做既让厨师们依然可以享受到揽土豆泥的乐趣,也避免了旧的食物被埋在新的食物下面。

有一天,Frank需要顶替一个生病了的面包师。Frank坚持说后进先出地将面包装入烤箱对放在最后的面包是不公平的。所以他提出,每过25秒钟就要拿出烤箱中最后一块面包,并将其他面包往后推,然后在最前面放入一片新的面包。

如果烤箱有前后两个门的话,这将会是一个非常好的计划。不幸的是,餐厅用的烤箱只有一个门。这让Frank的计划实施起来变得非常困难。这样时不时地改变面包的位置的确能让每一块面包受热更加均匀。但Frank意识到自己加面包的速度根本赶不上烘烤进度,很快就有面包烤糊了,浓浓的黑烟从烤箱中飘了出来。

当其他厨师都在忙着拿水桶去救火时,Frank只是麻木地看着那烤糊了的面包。当他意识到队列也许不是对餐厅里的所有问题都适用时,他感到了一丝困惑和绝望。看来关于数据结构他需要学的还有很多。

警用算法导论:栈和队列Ⅰ

节选自Drecker教授讲义

栈和队列是用来存放数据的两种简单的数据结构。表面上看,它们和一个列表没有什么区别,其实它们和列表的区别主要体现在添加和删除数据的方式上。

栈是后入先出的数据结构,就像我们对办公桌上的一叠公文的操作顺序那样。新的元素会被加入(推入)到栈的最上方,而删除(弹出)元素的时候也会从最上方删除。如果1、2、3、4、5依次被推入了一个栈,那么它们会以5→4→3→2→1的顺序被弹出。当然,在现实生活中,如果你真把桌上的公文全都处理完了,警长会马上给你安排更多的工作。

你可以用一个数组(A)和一个记录当前栈顶位置的变量(Top)来实现一个栈。当推入一个新的元素时,就会把它加到下一个空的位置里,也就是A[Top+1]里。同时,你也需要把Top的值加上1。

当从栈里面弹出一个元素时,可以用Top来找到应该弹出的元素(即A[top])。同时,你也需要将Top的值减去1。

当然,如果你的数组大小是固定的,那么在推入新的元素时也要注意检查数组中还有没有剩余空间。

队列是一个先入先出的数据结构,就像排成一列等待被询问的犯罪嫌疑人一样。新的元素会被加到队列的最后,而删除元素时会从队列最前面删除。如果1、2、3、4、5依次被加入到了队列,它们会以同样的顺序从队列中出来。

队列也可以用数组来实现。你需要两个变量来记录目前队列中的第一个元素(Front)和最后一个元素(Back)在哪。当加入一个新元素时,我们把它放到目前队列尾部之后的一个格子(A[Back+1]),并把Back加1。

相反,在弹出一个元素时,我们把目前处于队列最前端的元素(A[Front])弹出,并将Front加1。

如果使用一个固定大小的数组来实现队列,在不断向队列中添加元素和从中弹出元素时,数组的前端会逐渐出现一段空白,如果队列用完了数组后面的所有空间,可以让它绕到前面来利用这段空白空间。但如果这样做,一定要细心地处理好在添加和弹出元素时Back可能绕到数组最前面的情况。