一. 散列表
- ,散列函数是“将输入映射到数字”。(类似数组索引)。你可能根本不需要自己去实现散列表,任一优秀的语言都提供了散列表实现。Python提供的散列表实现为字典,你可使用函数dict来创建散列表——键映射到值。生活中的实例就比如DNS解析,将IP与网址域名映射。
- 将散列表用作缓存 。缓存是一种常用的加速方式,所有大型网站都使用缓存,而缓存的数据则存储在散列表中!比如Facebook不仅缓存主页,还缓存About页面、Contact页面、Terms and Conditions页面等众多 其他的页面。因此,它需要将页面URL映射到页面数据。当你访问Facebook的页面时,它首先检查散列表中是否存储了该页面。
# 代码实现:
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时,你就可以直接发送缓存中的数据,而不用再让服务器进 行处理了
- 这里总结一下,散列表适合用于:
- 模拟映射关系;
- 防止重复;
- 缓存/记住数据,以免服务器再通过处理来生成它们。
-
冲突(collision):实际上,散列函数并非总是将不同的键映射到数组的不同位置。处理冲突的方式 很多,最简单的办法如下:如果两个键映射到了同一个位置,就在这个位置存储一个链表。 但是,需要注意的是:假设你工作的杂货店只销售名称以字母A打头的商品,那么除第一个位置外,整个散列表都是空的,而第一个位置包含一个很长的列表!换言之, 这个散列表中的所有元素都在这个链表中,这与一开始就将所有元素存储到一个链表中一样糟 糕:散列表的速度会很慢。
-
性能:在平均情况下,散列表执行各种操作的时间都为O(1)。O(1)被称 为常量时间。这意味着无论散列表包含一个元素还是10亿个元素,从其中获取数 据所需的时间都相同。实际上,你以前见过常量时间——从数组中获取一个元素所需的时间就是 固定的:不管数组多大,从中获取一个元素所需的时间都是相同的。在平均情况下,散列表的速度确实很快。不过,在最糟情况下,散列表所有操作的运行时间都为O(n)——线性时间。
因此,在使用散列表时,避开最糟情况至关重要。为此,需要避免冲突。而要避免冲突,需要有:较低的填装因子;良好的散列函数
散列小结:
- 你可以结合散列函数和数组来创建散列表。
- 冲突很糟糕,你应使用可以最大限度减少冲突的散列函数。
- 散列表的查找、插入和删除速度都非常快。
- 散列表适合用于模拟映射关系。
- 一旦填装因子超过0.7,就该调整散列表的长度。 (填装因子:散列表包含的元素数/位置总数 调整长度通常开销很大,因此不应该频繁这样做。此外,调整长度通常是长度扩大一倍)
- 散列表可用于缓存数据(例如,在Web服务器上)。
- 散列表非常适合用于防止重复。
二. 广度优先搜索(breadth-first search,BFS):
- 广度优先搜索让你能够找出两样东西之间的最短距离。它可以:
- 图+队列+BFS实现举例:假设你经营着一个芒果农场,需要寻找芒果销售商,以便将芒果卖给他。在Facebook,你与芒果销售商有联系吗?为此,你可在朋友中查找。 这种查找很简单。首先,创建一个朋友名单。然后,依次检查名单中的每个人,看看他是否是芒果销售商。 假设你没有朋友是芒果销售商,那么你就必须在朋友的朋友中查找,检查名单中的每个人时,你都将其朋友加入名单
(队列类似于栈,你不能随机地访问队列中的元素。队列只支 持两种操作:入队和出队。如果你将两个元素加入队列,先加入的元素将在后加入的元素之前出队。因此,你可使用队 列来表示查找名单!这样,先加入的人将先出队并先被检查)
算法实现:
图示:
BUT:我们需要想到这样一种情况:Peggy既是Alice的朋友又是Bob的朋友,因此她将被加入队列两次:一次是在添加Alice的朋 友时,另一次是在添加Bob的朋友时。因此,搜索队列将包含两个Peggy。但你只需检查Peggy一次,看她是不是芒果销售商。如果你检查两次,就做了无用功。因此,检查完一个人后,应将其标记为已检查,且不再检查他。为此,你可使用一个 列表来记录检查过的人。
def search(name):
search_queue = deque()
search_queue += graph[name]
searched = [] # 这个数组用于记录检查过的人
while search_queue:
person = search_queue.popleft()
if not person in searched: # 仅当这个人没检查过时才检查
if person_is_seller(person):
print person + " is a mango seller!"
return True
else:
search_queue += graph[person]
searched.append(person) # 将其标记为检查过
return False
search("you")
小结:
- 广度优先搜索指出是否有从A到B的路径。
- 如果有,广度优先搜索将找出最短路径。
- 面临类似于寻找最短路径的问题时,可尝试使用图来建立模型,再使用广度优先搜索来 解决问题。
- 有向图中的边为箭头,箭头的方向指定了关系的方向,例如,rama→adit表示rama欠adit钱。
- 无向图中的边不带箭头,其中的关系是双向的,例如,ross - rachel表示“ross与rachel约 会,而rachel也与ross约会”。
- 队列是先进先出(FIFO)的。
- 栈是后进先出(LIFO)的。
- 你需要按加入顺序检查搜索列表中的人,否则找到的就不是最短路径,因此搜索列表必 须是队列。
- 对于检查过的人,务必不要再去检查,否则可能导致无限循环