算法分析的实际目的是分析预测不同算法的性能,用于指导设计决策;
**首项:**最高指数的项;一般来说具有较小首项的算法对于规模比较大的问题是好的算法;
通常来讲,具有相同首项的算法被认为是相当的;
下面的算法的增量级从低到高:
对于对数来讲,对数的基数并不影响增量级,改变基数等价于乘以一个常数
虽然我们通常使用增量级来描述算法的性能,但是:
- python中大部分算数运算的开销是常数级的;乘法比加减法用更长的时间,除法则会更长;
- 索引操作——在序列中或者字典中读写元素的增量级是常数级的;
- 一层循环是线性的,嵌套的循环增量级要相乘;
- 大部分字符串和元组运算是线性的,除了索引和
len
;- 切片运算与输出的长度成正比,但是和输入的大小无关;
- 所有的字符串方法都是线性的,比如
join
;
大部分列表操作是线性的,例外:
- 列表尾部增加元素是常数级;
- 结尾删除元素是常数级
- 排序是O(nlogn);
大部分租店运算和方法是常数时间,例外:
update
的运算时间与作为形参被传递的字典大小成正比;
keys
,values
,items
是常数级的时间,因为返回迭代器;
搜索 (search)算法,接受一个集合以及一个目标项,并判断该目标项是否在集合中,通常返回目标的索引值;
in
操作符使用的是线性搜索,即顺序查找;find
和count
方法使用的也是线性搜索;如果是已经排序好的序列,可以使用二分查找,时间复杂度是O(logn);
另一个检索速度非常快的数据结构是哈希表(hashtable)
- 可以在常数的时间内检索结果,并不依赖于序列是不是已经排序;
- python中的字典是采用的哈希表技术实现的;因此大多数字典操作时间复杂度都是常数级的;
下面的过程用python说明哈希表的实现过程:
add(k, v)
增加新项;get(k)
查找并返回值;使用元组列表:
class LinearMap:
def __init__(self):
self.items = []
def add(self, k, v):
self.items.append((k, v))
def get(self, k):
for key, val in self.items:
if key == k:
return val
raise KeyError
上面的查找是顺序查找,另外的方案是将列表按照键值进行排序,使用二分查找,时间复杂度是对数级,但是添加一个元素是线性的;
class BetterMap:
def __init__(self, n=100):
self.maps = []
for i in range(n):
self.maps.append(LinearMap())
def find_map(self, k):
index = hash(k) % len(self.maps)
return self.maps[index]
def add(self, k, v):
m = self.find_map(k)
m.add(k, v)
def get(self, k):
m = self.find_map(k)
return m.get(k)
__init__
会生成一个由 n 个 LinearMap
组成的列表;find_map
使用内建函数hash
,这个内建函数接受几乎所有的python对象并返回一个整数【被认为相等的可哈希对象对应相同的哈希值】;
LiearMap
的最大长度是有上限的,则LinearMap.get
的时间复杂度的增长量级是常数的;这样跟踪每个LinearMap的项数,当超过某个阈值的时候,增加更多的LinearMap
来调整哈希表的大小:class HashMap:
def __init__(self):
self.maps = BetterMap(2)
self.num = 0 //用于跟踪项的数量
def get(self, k):
return self.maps.get(k)
def add(self, k, v):
if self.num == len(self.maps.maps):
self.resize()
self.maps.add(k, v)
self.num += 1
def resize(self):
new_maps = BetterMap(self.num * 2)
for m in self.maps.maps:
for k, v in m.items:
new_maps.add(k, v)
self.maps = new_maps
resize
生成一个新的BetterMap
,是之前的两倍大,之后从旧表重新"哈希"到新的表【重新哈希是必要的的,因为哈希表变大以后,其中每个键对应的哈希值会变化】
HashTable
大小的时候,是几何式增长