《算法图解》笔记

《算法图解》笔记

C1 算法简介

1. 二分查找

如果数据的组织是有序的,那么从中间开始进行查找,每次判断可以排除一半的数据,可以极大的节省资源。对于包含n个元素的列表,用二分查找最多需要log2n步,而简单查找最多需要n步。

def binary_search(list, item): 
	low = 0 
	high = len(list)1 
	while low <= high:
	mid = (low + high)
	guess = list[mid] 
	if guess == item:
		return mid 
	if guess > item:
		high = mid - 1 
	else:
		low = mid + 1 
	return None

2. 大 O 表示法

大O表示法是一种特殊的表示法,指出了算法的速度有多快(大O表示法表示的是最慢的情形)。

  • 假设列表包含n个元素。简单查找需要检查每个元素,因此需要执行n次操作,这个运行时间为O(n)。
  • 二分查找需要执行log n次操作,使用大O表示法,这个运行时间为O(log n)。

算法的速度指的并非时间,而是操作数的增速,随着输入的增加,其运行时间将以什么样的速度增加。

C2 选择排序

数组和链表

  1. 数组:占用连续地址的内存空间,如果内存不够大,是无法创建数组的。此外,数组也无法增加或减少数量。
  2. 链表:可以存储在内存的任意空间,链表的每个元素都存储了下一个元素的地址,从而使一系列随机的内存地址串在一起。这样的存在一个问题,当我们要查找第n个元素的内容时,必须从第一个元素开始逐个读取,效率很低。

效率

数组链表
读取O(1)O(n)
插入O(n)O(1)
删除O(n)O(1)
  • 数组读取时只需要读出对于地址的元素,但插入和删除时必须将元素后移。
  • 链表读取时需逐个读取,插入和删除时只需要修改对应元素指向的地址即可。

选择排序

遍历所有数据,找到最大或最小的那个,存入新列表的第一个位置,再找出第二打或小的放入新列表,依此类推。

C3 递归

递归是指:在函数的定义中使用函数自身的方法。

每个递归函数都有两部分:

  • 基线条件(base case)

  • 递归条件(recursive case):定义如何停止,避免死循环。

栈是只能在一端进行插入和删除操作的特殊线性表。它按照先进后出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据。

使用栈虽然很方便,但是也要付出代价:存储详尽的信息可能占用大量的内存。每个函数调用都要占用一定的内存,如果栈很高,就意味着计算机存储了大量函数调用的信息。

C4 快速排序

分而治之

工作原理:

  1. 找出基线条件,这种条件必须尽可能简单。
  2. 不断将问题分解(缩小规模),直到符合基线条件。

快速排序

快速排序使用了分而治之的思想。

  • 基准条件:只包含一个元素的序列;
  • 问题分解:找到一个基准值,将大于它的放一边,小于的放另一边;
def quicksort(array): 
	if len(array) < 2: 
		return array 
	else: 
		pivot = array[0] 
		less = [i for i in array[1:] if i <= pivot] 
		greater = [i for i in array[1:] if i > pivot] 
		return quicksort(less) + [pivot] + quicksort(greater) 
	print quicksort([10, 5, 2, 3])

算法效率

在平均情况下,快速排序的运行时间为O(n log n),快速排序的性能高度依赖于基准值的选择。

最糟情况

基准值总是最边缘的元素,其中一个子数组始终为空,这导致调用栈非常长,最终效率为O(nlogn)

最佳情况

基准值总是中间的元素,因为你每次都将数组分成两半,所以不需要那么多递归调用,很快就到达了基线条件,因此调用栈短得多,最终效率为O(logn)。

(在调用栈的每层都涉及O(n)个元素)

值得注意的是,最佳情况也是平均情况。只要你每次都随机地选择一个数组元素作为基准值,快速排序的平均运行时间就将为O(nlog n)。

C5 散列表(哈希表)

散列函数

散列函数是这样的函数,即无论你给它什么数据,它都还你一个数字。

  • 它必须是一致的。例如,假设你输入apple时得到的是4,那么每次输入apple时,得到的都必须为4。如果不是这样,散列表将毫无用处。
  • 它应将不同的输入映射到不同的数字。例如,如果一个散列函数不管输入是什么都返回1,它就不是好的散列函数。最理想的情况是,将不同的输入映射到不同的数字。

散列表(哈希表)

散列表由键和值组成,是能够通过给定的键(关键字)的值直接访问到具体对应的值的一个数据结构。

冲突

给两个键分配的位置相同就会产生冲突。

处理冲突的方式很多,最简单的办法如下:如果两个键映射到了同一个位置,就在这个位置存储一个链表,但这样做会降低性能。如果链条很长,性能就会急速下降。

最理想的情况是,散列函数将键均匀地映射到散列表的不同位置。

性能

在平均情况下,散列表执行各种操作的时间都为O(1),查找速度与数组一样快,而插入和删除速度与链表一样快。

O(1)被称为常量时间。你以前没有见过常量时间,它并不意味着马上,而是说不管散列表多大,所需的时间都相同。)

但在最糟情况下,散列表的各种操作的速度都很慢。

散列表平均情况散列表最糟情况数组链表
读取O(1)O(n)O(1)O(n)
插入O(1)O(n)O(n)O(1)
删除O(1)O(n)O(n)O(1)

因此,在使用散列表时,避开最糟情况至关重要。为此,需要避免冲突。而要避免冲突,需要有:

  • 较低的填装因子

  • 良好的散列函数

填装因子

填装因子 = 散列表存储的元素数 / 位置数

填装因子越低,发生冲突的可能性越小,散列表的性能越高。所以一旦填装因子开始增大,你就需要在散列表中添加位置,这被称为调整长度。一个不错的经验规则是:一旦填装因子大于0.7,就调整散列表的长度。

广度优先搜索

解决最短路径问题的算法被称为广度优先搜索。

图简介

图由节点和边组成,模拟一组连接,一个节点可能与众多节点直接相连,这些节点被称为邻居。

广度优先搜索

广度优先搜索是一种用于图的查找算法,可帮助回答两类问题。

  • 从节点A出发,有前往节点B的路径吗?

  • 从节点A出发,前往节点B的哪条路径最短?

查找最短路径

在广度优先搜索的执行过程中,搜索范围从起点开始逐渐向外延伸,即先检查一度关系,再检查二度关系。

注意,只有按添加顺序查找时,才能实现这样的目的。有一个可实现这种目的的数据结构,那就是队列(queue)。

队列

队列的工作原理与现实生活中的队列完全相同。

队列只支持两种操作:入队和出队。

队列是一种先进先出(First In First Out,FIFO)的数据结构,而栈是一种后进先出(Last In First Out,LIFO)的数据结构。

实现图

图由多个节点组成,每个节点都与邻近节点相连。

而使用散列表可以实现图结构。

实现算法

需求:从你的关系网中寻找芒果商人

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

对于检查过的人,务必不要再去检查,否则可能导致无限循环。

运行时间
  1. 你将沿着每一条边前行,所以有O(边数)
  2. 你还使用了一个队列,其中包含要检查的每个人。将一个人添加到队列需要的时间是固定的,即为O(1),因此对每个人都这样做需要的总时间为O(人数)。

广度优先搜索的运行时间为O(人数 + 边数),这通常写作O(V + E),其中V为顶点(vertice)数,E为边数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值