Chapter02 算法分析
来科大讯飞实习一个星期了,之前面试喜马拉雅和百度因为数据结构和算法挂了,为了后面能找到工作,开始养写博客的习惯。大家一起学习吧!!!
何为算法分析
思考第一个问题:现在有两个程序实现了同一个问题,请问,这两个程序有没有好坏?
答案:显然有,那么评判的标准是什么?
举例说明:
现在实现计算一个前n项和的程序
代码1
from timeit import Timer
def my_sum(n):
my_sum = 0
n = 100
for i in range(1, n+1):
my_sum += i
return my_sum
time = Timer('my_sum(100)', 'from __main__ import my_sum')
print(time.timeit(1000000))
# 结果: 4.5124388
代码2
from timeit import Timer
def my_sum(n):
return n*(1+n)/2
time = Timer('my_sum(100)', 'from __main__ import my_sum')
print(time.timeit(1000000))
# 结果: 0.1856523
代码的解释说明:
代码1利用暴力求解计算了1000000次求前100项和的时间结果
代码2利用前n项和求解了1000000次求前100项和的时间结果
显然两个代码有好有坏,而且经过上面的结果显示,明显有一个很好的判断条件——就是时间。
但是如果大家拿去运行我上面的代码,就会出现一个问题,可能测试的时间跟我上面提供的结果不相同,显然第一个是因为计算机的性能原因,第二个是误差所致,此外,还有可能你的电脑可能运行其他的程序,导致占用了这里的速度,也会导致一个结果的不精确,因此,我们需要一个比较理性的分析结果——就是时间复杂度(看下文)。
大O表示法
我们要尝试拜托程序或计算机的影响,来描述算法的效率,也就是说我们要量化操作的步骤。把每一次的赋值操作看成一个计算单位,那么算法的执行时间也就被描述成所需的步骤数了。
拿上面的代码1举例,我们有两个赋值语句my_sum = 0
和n = 100
,此外还有一个循环语句里面的赋值语句 my_sum += i
,那么我们就需要运行(1+1+n)次赋值语句,我们用函数来描述它,定义成函数T,则
T
(
n
)
=
2
+
n
T(n)=2+n
T(n)=2+n,这为叫做问题规模。但是这个没有其实还不够精简,因为当n很大时,2显得很没用。因此我们直接使用数量级的比较,即大O表示法,可以用O(n)来表示,这个O来自order单词。大O表示法是说明问题规模中起决定性作用部分的表示。
举一个例子:
T ( n ) = 5 n 2 + 27 n + 1005 T(n)=5n^2+27n+1005 T(n)=5n2+27n+1005,当n很大时,27n+1005其实影响不了其问题规模,另外当n很大时系数5也不是那么重要了,因此会直接使用 O ( n 2 ) O(n^2) O(n2)。
举几个常见的大O表示法的函数
f(n) | 名称 |
---|---|
1 | 常数 |
log n \log n logn | 对数 |
n | 线性 |
n log n n \log n nlogn | 对数线性 |
n 2 n^2 n2 | 平方 |
n 3 n^3 n3 | 立方 |
2 n 2^n 2n | 指数 |
以上结果是复杂度递增的顺序。
案例:异序词检测
异序词:一个单词与另一个单词的使用的字母与数量完全相同,只是修改了顺序,那么这两个单词就被叫做异序词。例如heart与earth
实现异序词的方法很多
方案一:清点法
说白了就是循环一个单词,看看其字母在不在另一个里面,不在就不是,在的话就删除第二个单词中的这个字母一直进行
代码
def test(word1, word2):
word1 = list(word1)
word2 = list(word2)
for w in word1:
if w in word2:
# 如果在就删除这个字母
word2.remove(w)
else:
# 不在word2中
return False
# 都循环完了还要检测word2还有没有单词
if len(word2) == 0:
return True
else:
return False
if __name__ == '__main__':
word1 = 'earth'
word2 = 'heart'
print(test(word1, word2))
方案2:排序法
原理就是先对单词进行排序,排序完后一一比对
def test(word1, word2):
word1 = list(word1)
word2 = list(word2)
word1 = sorted(word1)
word2 = sorted(word2)
return word1 == word2
if __name__ == '__main__':
word1 = 'eet'
word2 = 'tte'
print(test(word1, word2))
方案三:计数法
这个方案的原理是异序词有相同的单词次数,所以我们只要比较每个字符出现的次数即可
代码
def test(word1, word2):
# 先给26个字母腾个位置
c1 = [0] * 26
c2 = [0] * 26
for i in range(len(word1)):
pos = ord(word1[i]) - ord('a')
c1[pos] += 1
for i in range(len(word2)):
pos = ord(word2[i]) - ord('a')
c2[pos] += 1
for i in range(26):
if c1[i] != c2[i]:
return False
return True
if __name__ == '__main__':
word1 = 'earth'
word2 = 'heart'
print(test(word1, word2))
大家思考一下上面三个方案,用大O表示法都应该是多少?
答案是 O ( n 2 ) O(n^2) O(n2), O ( n log n ) O(n\log n) O(nlogn), O ( n ) O(n) O(n),大家可能不理解,第一个不是只用了一个循环吗?,第二个都没用循环,第三个可能会写对,事实上,第一种方案使用了in,在list中使用in其实底层使用了for判断,第二个看似没有循环,但是排序没有代价吗?因为python的内部实现也必然有一部分并不是 O ( 1 ) O(1) O(1)的实现,具体内部实现的复杂度我下面贴一个链接,大家可以去看看。
python内部实现的时间复杂度页面:http://wiki.python.org/moin/TimeComplexity
内容来源于《python数据结构与算法》 第二版的第二章,删减了比较多,想看更详细的可以看原版,这本书的笔记我会更新完的。