算法是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作
2.1两种算法的比较
求:一个1+2+3+…+100
# O(n)
n = 1
sum = 0
while n <= 100:
sum = sum + n
n += 1
print(sum)
5050
# 高斯的方法O(1)
n = 100
sum = 0
sum = (1 + n) * n / 2
print(sum)
5050.0
再来感受一下算法的力量
求:所有在1000内满足 a^2 + b^2 = c^2的所有组合?
import time
def main_1():
"""
# 枚举法
三重循环:T = 1000 * 1000 * 1000 * 2
T(N) = O(N^3)l
:return:
"""
start_time = time.time()
for a in range(0, 1001):
for b in range(0, 1001):
for c in range(0, 1001):
if a + b + c == 1000 and a ** 2 + b ** 2 == c ** 2:
print("a, b, c:%d, %d, %d" % (a, b, c))
end_time = time.time()
print("time:%d" % (end_time - start_time))
print("finished")
return None
main_1()
a, b, c:0, 500, 500
a, b, c:200, 375, 425
a, b, c:375, 200, 425
a, b, c:500, 0, 500
time:80
finished
def main_2():
"""
#两重循环
T = 1000 * 1000 * 3
T(N) = O(N^2)
"""
start_time = time.time()
for a in range(0, 1001):
for b in range(0, 1001):
c = 1000 - a - b
if a ** 2 + b ** 2 == c ** 2:
print("a, b, c:%d, %d, %d" % (a, b, c))
end_time = time.time()
print("time:%d" % (end_time - start_time))
print("finished")
main_2()
a, b, c:0, 500, 500
a, b, c:200, 375, 425
a, b, c:375, 200, 425
a, b, c:500, 0, 500
time:0
finished
2.2算法的特性
- 输入(大于等于0个)、输出(至少有一个,算法至少有一个或多个输出)
- 有穷性(不会无限循环,且每一个步骤都在可接受时间内完成)
- 确定性(每一步都有确定含义,无歧义)
- 可行性(每一步都在执行有限次数完成)
2.3算法的设计要求
- 正确性
- 无语法错误
- 对于合法输入有正确的输出
- 对于不合法的输入有满足规格说明的输出
- 可读性
- 健壮性(输入数据不合法时,也能做出相关处理,而不是产生异常)
- 时间效率高和存储量低
2.4算法效率的度量
一个用高级语言编写的程序在计算机上运行时所消耗的时间取决于下列因素:
- 算法采用的策略、方法
- 编译产生的代码质量(软件支持的优劣)
- 问题的输入规模
- 机器执行指令的速度(硬件性能)
抛开计算软硬件的影响,一个程序的运行时间,依赖于算法的好坏和问题的输入规模(输入量的多少)。
分析一个算法的运行时间时,将基本操作的数量表示为输入规模的函数
2.5算法的时间复杂度(最坏时间复杂度)
O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(n3)<O(2n)<O(n!)<O(nn)
2.5.1常数阶O(1)
# 执行1次
sum = (1 + n) * n / 2
# 执行12次
sum = (1 + n) * n / 2
sum = (1 + n) * n / 2
sum = (1 + n) * n / 2
sum = (1 + n) * n / 2
sum = (1 + n) * n / 2
sum = (1 + n) * n / 2
sum = (1 + n) * n / 2
sum = (1 + n) * n / 2
sum = (1 + n) * n / 2
sum = (1 + n) * n / 2
sum = (1 + n) * n / 2
sum = (1 + n) * n / 2
- 无论n为多少,上面的两段代码就是1次和12次执行的差异,这种与问题大小无关(n的多少),执行时间恒定的算法,称为O(1)的时间复杂度,即常数阶
- 对于分支结构,无论是真是假,执行的次数都是恒定的,不会随着n的变大而变化,所以单纯的分支结构(不包括在循环结构中),其时间复杂度也是O(1)
2.5.2线性阶O(n)
分析算法的复杂度,关键就是分析循环结构的运行情况
# 循环体中的代码必须执行n次
i = 0
while i < n:
i += 1
# 时间复杂度为O(1)的程序步骤序列
2.5.3对数阶O(logn)
count = 1
while count < n:
count = count * 2
# 时间复杂度为O(1)的程序步骤序列
由于每次count乘以2后,就距离n更近了一分。也就是说有多少个2相乘后大于n,则会退出循环。由2^x = n,得x=logn,所以这个循环的时间复杂度为O(logn)
5.5.4平方阶O(n^2)
循环的时间复杂度 = 循环体的复杂度 * 该循环体运行的次数
# 时间复杂度为O(n*m)
i = 0
while i < n:
i += 1
j = 0
while j < m:
j += 1
# 时间复杂度为O(1)的程序步骤序列
# 时间复杂度为O(n^2)
i = 0
while i < n:
j = i
i += 1
while j < n:
j += 1
# 时间复杂度为O(1)的程序步骤序列
当i=1时,内循环执行了n次
当i=2时,内循环执行了n-1次
…
当i=n-1时,内循环执行了1次
内循环总的执行次数:n+(n-1)+(n-2)+…+1=O(n^2)