1、算法概念
算法:一个计算过程,解决问题的方法
程序 = 数据结构 + 算法
2、时间复杂度
时间复杂度是用来评估算法运行时间大小的一个式子
用时间复杂度来体现算法运行的快慢
O就是大概的意思,时间复杂度强调的更多的是一个大概的时间
print('hello world')
O(1) :O是数学里上界的意思,可以见到理解为大约,括号里1就是单位,O(1)就是执行了一次,我们把一些基本的操作,比如加减乘除打印等,就叫做时间消耗是1。
for i in range(n):
print('hello world')
O(n):这个代码是一个循环,大概会运行n次,所以表示成大约O,单位n,n就是一个运行单位
for i in range(n):
for i in range(n):
print('hello world')
O(n2):两层循环,定义为n的平方
O(n3):三层循环,定义为n的三次方
四层循环,就是n的四次方
如果打印3次,时间复杂度还是O(1)
两次循环里各带着两个打印,时间复杂度还是O(n2),只留大单位
while n > 1:
print(n)
n = n // 2
n = 64输出:
64
32
16
8
4
2
n = 64,代码运行了6次
2^6 = 64
所以时间复杂度记为O(log n)或者O(以2为底n的对数)
当算法出现循环减半的过程,一定会出现log n
2.1 时间复杂度小结:
- 时间复杂度是用来估计算法运行时间的一个式子(单位)。
- 一般来说,时间复杂度高的算法比复杂度低的算法慢
- 常见的时间复杂度(按效率排序):
O(1) < O(log n) < O(n) < O(nlogn) < O(n^2) < O(n^2logn) < O(n^3) - 复杂问题的时间复杂度
O(!) O(2^n) O(n^n)
2.2 如何简单快速的判断算法复杂度
适用于绝大多数简单的过程
- 先确定问题规模n,比如循环次数,列表长度
- 看有没有循环减半过程,若有,就有logn
- 循环的话,有几层循环就是n的几次方
复杂情况:根据算法执行过程判断
3、空间复杂度
空间复杂度就是用来评估算法内存占用大小的式子
空间复杂度的表示方式与时间复杂度完全一样
- 若算法只使用了几个变量:O(1)
- 算法使用了长度为n的一维列表:O(n)
- 算法使用了m行n列的二维列表:O(mn)
若是O(n^2),则表示n行n列的二维列表 - 空间换时间:意思就是算法宁可占用更多内存,也要尽量让时间更快
现在空间复杂度没有时间复杂度讨论的那么多了
4、递归
递归的2个特点:(合法递归)
- 调用自身
- 结束条件
def f1(x):
if x > 0:
print(x)
f1(x - 1)
f1(3)
一层一层,先打印,后递归,函数执行过程是从上往下
(大框代表递归,小框代表打印)
打印结果:3,2,1
def f2(x):
if x > 0:
f2(x - 1)
print(x)
f2(3)
一层一层,先打印,后递归,函数执行过程是从上往下,所以是先打印1
(大框代表递归,小框代表打印)
运行结果 1,2,3
5、汉诺塔问题
n个盘子时,把上面n-1个盘子看成一个整体
- 把n-1个圆盘从A经过C移动到B
- 把第n个圆盘从A移动到C
- 把n-1个圆盘从B经过A移动到C
此递归终止条件是n=0
def hanoi(n, a, b, c):
if n > 0:
hanoi(n-1, a, c, b) #调用自身,把n-1个盘子从a经过c移动到b
print(f'{a}-->{c}') #将第n个从a移动到c,打印出来
hanoi(n-1, b, a, c) #把n-1个圆盘从B经过A移动到C
hanoi(3, 'A', 'B', 'C') #abc三个盘子名字分别传字符串ABC
汉诺塔移动次数的递推式:
h(x) = 2h(x - 1) + 1
(两次调用自身,一次打印)
6、查找
查找:在一些数据元素中,通过一定的方法找出与给定关键字相同的数据元素的过程。
列表查找(线性表查找):从列表中查找指定元素
- 输入:列表、待查找元素
- 输出:元素下标(未找到元素时一般返回None或-1,因为下标都是从0开始的,所以返回-1表示没有查到)
python内置列表查找函数:index()
index()是实现线性查找方式的
二分查找列表必须是有序列表
6.1 顺序查找
顺序查找:也叫线性查找,从列表第一个元素开始,顺序进行搜索,直到找到元素或搜索到列表最后一个元素为止。(从头走到尾,如果中间找到就停掉,返回下标)
def linear_search(li, val): #输入有一个列表,还有待查找元素
for ind, v in enumerate(li): #值和下标都需要
if v == val:
return ind #返回下标
else: #如果循环执行完毕还没有找到
return None
时间复杂度:O(n)
6.2 二分查找
二分查找:又叫折半查找,从有序列表的初始候选区li[0:n]开始,通过对待查找的值与候选区中间值的比较,可以使候选区减少一半
输入:列表、待查找元素
维护候选区:用变量left和right ,left指向第一个元素,right指向最后一个元素,初始left=0,right=n-1,n为列表长度
中间元素指向:mid=(left + right)/ 2
若候选区在mid左边,就让right = mid - 1
接下来,若候选区在mid右边,就让left = mid + 1
(left > right时,候选区没有值就结束算法,说明找不到)
def binary_search(li, val):
left = 0
right = len(li) - 1 #长度减1
while left <= right: #候选区有值
mid = (left + right) // 2 #整除,因为是下标
if li[mid] == val:
return mid #返回下标
elif li[mid] > val: #待查找的值在mid左侧
right = mid - 1
else: # li[mid] < val 待查找的值在mid右侧
left = mid + 1
else: #while条件不满足,没找到
return None
时间复杂度:O(log n) 减半
二分查找比线性查找效率高