一、引入
如果我们需要查找一门课的学分(如《计算机算法设计与分析》(简称《算法》)这门课),如果教务做得很烂,所有课程都不是按照一定的顺序排列的,那么我们需要浏览每一行直到找到这门课,这将耗费我们O(n)的时间。而如果教务系统的学分排序是按照汉语拼音排序的,我们可以使用二分查找来找到《算法》这门课,这只需要耗费我们O(nlogn)的时间。
而我们如果想要更快的速度,最好是查找一门课后马上就能得知这门课的学分,如果我们定义一个数组,这个数组的每个元素包含两项内容:课程名词和学分。如果将这个数组按课程名称顺序排序,我们就可使用二分查找在其中查找商品的价格。这样查找学分的时间将为O(logn)。
而我们想要获得学分的时间降为O(1),速度比二分查找都快,有没有可能呢?
二、散列表的思路
散列表可以实现这种要求,散列函数是这样的函数:即无论你给它什么数据,它都还你一个数字(即散列函数“将输入映射到数字"),但其实散列函数并不是随机输出数字,它必须满足一些要求:
①一致性。即输入《算法》这门课后得到的学分为3,那么下次输出还是3学分,下下次输出也是3学分,不会变成别的数字。如果不是这样,散列表将毫无用处。
②它应将不同的输入映射到不同的数字。如果一个散列函数不管输入师门都返回3,那么它就不是一个好的散列函数。最理想的情况是,将不同的输入映射到不同的数字。
那么,现在来创建我们的散列表。例如,我们的《算法》课有3个学分,《数电》课有2个学分,《创业实践》课有1个学分。我们先创建一个空数组,数组的下标分别为0、1、2、3,我们将课程名称根据学分填写进去。
现在,我们需要查找《数电》课的学分。这时,我们不再需要查找数组,而是直接将《数电》作为输入告诉散列函数,它将告诉你《数电》课的学分,并输出学分2。
散列函数准确地指出了学分的存储位置,大大降低了人力开销。而它之所以能够这样,具体原因如下:
①散列函数总是将同样的输入映射到相同的索引。每次输入《数电》得到的都是同一个数字。因此,我们可以首先定义《数电》存放于数组的位置,之后再从这个位置取出《数电》对应的值。
②散列函数将不同的输入映射到不同的索引。它将《数电》映射到索引2,将《算法》映射到索引3。每门课程都映射到数组的不同位置,让你能够将其学分存储到这里。
③散列函数知道数组有多大,只返回有效的索引。如果数组包含5个元素,散列函数就不会 返回无效索引100。
而且,我们根本不需要自己去实现散列表!Python提供的 散列表实现为字典,你可使用函数dict来创建散列表。
xuefen = dict()
创建散列表xuefen后,在其中添加一些课程的学分。
>>> xuefen["Algorithm"] = 3 >>> xufen["digital circuit"] = 2 >>> xuefen["Entrepreneurship"] = 1 >>> print xuefen {'Algorithm': 3, 'digital circuit': 2, 'Entrepreneurship': 1}
#输入课程 print xuefen["digital circuit"] #输出结果 2
散列表由键和值组成。在前面的散列表xuefen中,键为课程名称,值为学分。散列表将键映射到值。
三、散列表的应用其一
我们的手机都内置了方便的电话簿,其中每个姓名都有对应的电话号码。假设你要创建一个类似这样的电话簿,将姓名映射到电话号码。该电话簿需要兼顾以下功能:
①添加联系人及其电话号码。
②通过输入联系人来获悉其电话号码。
这非常适合使用散列表来实现。在需要满足创建映射、查找时,使用散列表是很不错的选择。
#创建电话簿非常容易。首先,新建一个散列表。 >>> phone_book = dict()
#Python提供了一种创建散列表的快捷方式——使用一对大括号。 >>> phone_book = {} #这与phone_book = dict()等效
#下面在这个电话簿中添加一些联系人的电话号码。 >>> phone_book["jenny"] = 8675309 >>> phone_book["emergency"] = 911
#现在,假设你要查找Jenny的电话号码,为此只需向散列表传入相应的键。 >>> print phone_book["jenny"] #输出结果 8675309
四、散列表的应用其二
假设你负责管理一个投票统计。显然,每人只能投一票,但如何避免重复投票呢?有人来投票时,你询问他的全名,并将其与已投票者名单进行比对。如果名字在名单中,就说明这个人投过票了,因此将他拒之门外。否则,就将他的姓名加入到已投票的人员名单中,并让他投票。现在假设有很多人来投过了票,因此名单非常长。
每次有人来投票时,你都得浏览这个长长的名单,以确定他是否投过票。可以预见,这种方法非常耗费时间。但有一种更好的办法,那就是使用散列表。
为此,首先创建一个散列表,用于记录已投票的人。
>>> voted = {}
有人来投票时,检查他是否在散列表中。
>>> value = voted.get("tom") #如果“tom”在散列表中,函数get将返回它;否则返回None。你可使用这个函数检查来投票的人是否投过票
def check_voter(name): if voted.get(name): print "kick them out!" else: voted[name] = True print "let them vote!"
五、散列表的应用其三
我们可以将散列表用作缓存。假设你访问网站csdn.net,需要经历以下的步骤:
①你向csdn的服务器发出请求。
②服务器做些处理,生成一个网页并将其发送给你。
③你获得一个网页。
但csdn的服务器必须为数以百万的用户提供服务,为服务好所有用户,服务器如果每次都重新读取数据而不进行缓存的话累加起来将耗费大量的计算成本。有没有办法让csdn的服务器少做些工作,从而提高网站的访问速度呢?这里可以使用缓存。
缓存的使用有以下的优点:
①用户能够更快地看到网页
②网站需要做的工作更少
cache = {} def get_page(url): if cache.get(url): return cache[url] #返回缓存的数据 else: data = get_data_from_server(url) cache[url] = data #先将数据保存到缓存中 return data
仅当URL不在缓存中时,你才让服务器做些处理,并将处理生成的数据存储到缓存中,再返 回它。这样,当下次有人请求该URL时,你就可以直接发送缓存中的数据,而不用再让服务器进行处理了。
未完待续......