Summary--算法图解(二)BFS

一. 散列表

  1. ,散列函数是“将输入映射到数字”。(类似数组索引)。你可能根本不需要自己去实现散列表,任一优秀的语言都提供了散列表实现。Python提供的散列表实现为字典,你可使用函数dict来创建散列表——键映射到值。生活中的实例就比如DNS解析,将IP与网址域名映射。
  2. 将散列表用作缓存 。缓存是一种常用的加速方式,所有大型网站都使用缓存,而缓存的数据则存储在散列表中!比如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时,你就可以直接发送缓存中的数据,而不用再让服务器进 行处理了
  • 这里总结一下,散列表适合用于:
  • 模拟映射关系;
  • 防止重复;
  • 缓存/记住数据,以免服务器再通过处理来生成它们。
  1. 冲突(collision):实际上,散列函数并非总是将不同的键映射到数组的不同位置。处理冲突的方式 很多,最简单的办法如下:如果两个键映射到了同一个位置,就在这个位置存储一个链表。 但是,需要注意的是:假设你工作的杂货店只销售名称以字母A打头的商品,那么除第一个位置外,整个散列表都是空的,而第一个位置包含一个很长的列表!换言之, 这个散列表中的所有元素都在这个链表中,这与一开始就将所有元素存储到一个链表中一样糟 糕:散列表的速度会很慢。
    在这里插入图片描述

  2. 性能:在平均情况下,散列表执行各种操作的时间都为O(1)。O(1)被称 为常量时间。这意味着无论散列表包含一个元素还是10亿个元素,从其中获取数 据所需的时间都相同。实际上,你以前见过常量时间——从数组中获取一个元素所需的时间就是 固定的:不管数组多大,从中获取一个元素所需的时间都是相同的。在平均情况下,散列表的速度确实很快。不过,在最糟情况下,散列表所有操作的运行时间都为O(n)——线性时间。
    在这里插入图片描述
    因此,在使用散列表时,避开最糟情况至关重要。为此,需要避免冲突。而要避免冲突,需要有:较低的填装因子;良好的散列函数

散列小结:

  •  你可以结合散列函数和数组来创建散列表。
  •  冲突很糟糕,你应使用可以最大限度减少冲突的散列函数。
  •  散列表的查找、插入和删除速度都非常快。
  •  散列表适合用于模拟映射关系
  • 一旦填装因子超过0.7,就该调整散列表的长度。 (填装因子:散列表包含的元素数/位置总数 调整长度通常开销很大,因此不应该频繁这样做。此外,调整长度通常是长度扩大一倍)
  •  散列表可用于缓存数据(例如,在Web服务器上)。
  •  散列表非常适合用于防止重复。

二. 广度优先搜索(breadth-first search,BFS):

  1. 广度优先搜索让你能够找出两样东西之间的最短距离。它可以:
    在这里插入图片描述
  2. 图+队列+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)的。
  • 你需要按加入顺序检查搜索列表中的人,否则找到的就不是最短路径,因此搜索列表必 须是队列。
  • 对于检查过的人,务必不要再去检查,否则可能导致无限循环
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值