算法
什么是算法
算法 (Algorithm): 一个计算过程, 解决问题的方法
Niklaus Wirth: "程序=数据结构+算法"
时间复杂度
⽤用来评估算法运⾏时间或者运行效率的一个式⼦
一般来说, 时间复杂度高的算法比时间复杂度低的算法慢
常见的时间复杂度排序(按效率排序)
O(1)<O(logn)<O(n)<O(nlogn)<o(n2)<O(n2logn)<O(n^3)
第一类
- 对于一个循环,假设循环体的时间复杂度为 O(n),循环次数为 m,则这个
循环的时间复杂度为 O(n×m)
- O(1)
print('Hello World')
print('Hello World')
print('Hello lxx')
print('Hello lyy')
- O(n)
for i in range(n):
print('Hello World')
第二类
- 对于多个循环,假设循环体的时间复杂度为 O(n),各个循环的循环次数分别是a, b, c...,则这个循环的时间复杂度为 O(n×a×b×c...)。分析的时候应该由里向外分析这些循环。
- O(n**2)
for i in range(n):
for j in range(n):
print('Hello World')
for i in range(n):
print('Hello World')
for j in range(n):
print('Hello World')
- O(n**3)
for i in range(n):
for j in range(n):
for k in range(n):
print('Hello World')
第三类
- 对于顺序执行的语句或者算法,总的时间复杂度等于其中最大的时间复杂度。
void aFunc(int n) {
// 第一部分时间复杂度为 O(n^2)
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
printf("Hello, World!\n");
}
}
// 第二部分时间复杂度为 O(n)
for(int j = 0; j < n; j++) {
printf("Hello, World!\n");
}
}
此时时间复杂度为 max(O(n^2), O(n)),即 O(n^2)
第四类
- 对于条件判断语句,总的时间复杂度等于其中 时间复杂度最大的路径 的时间复杂度。
void aFunc(int n) {
if (n >= 0) {
// 第一条路径时间复杂度为 O(n^2)
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
printf("输入数据大于等于零\n");
}
}
} else {
// 第二条路径时间复杂度为 O(n)
for(int j = 0; j < n; j++) {
printf("输入数据小于零\n");
}
}
}
此时时间复杂度为 max(O(n^2), O(n)),即 O(n^2)。
第五类
- log2n / logn
while n > 1:
print(n)
n = n // 2
n = 64输出:
64
32
16
8
4
2
说明: 当n = 64 的时候, 程序执行了6次; 然而 2**6 = 64 ;
2**x = n ===> x=log2n
void aFunc(int n) {
for (int i = 2; i < n; i++) {
i *= 2;
printf("%i\n", i);
}
}
假设循环次数为 t:
循环第一次, i 约等于 2*2
循环第二次, i 约等于 2*2*2
循环第三次, i 约等于 2*2*2*2
循环第四次, i 约等于 2*2*2*2*2
所以 循环条件满足 2^t < n。
可以得出,执行次数t = log(2)(n),即 T(n) = log(2)(n),可见时间复杂度为 O(log(2)(n)),即 O(log n)。
简单判断时间复杂度
- 确定问题规模n
- 循环减半过程 ==> logn
- K层关于n的循环 ==> n^k
复杂情况:根据算法执行过程判断
空间复杂度
-
用来评估算法内存占用大小的式子
-
空间复杂度的表示方式与时间复杂度完全一样
- 算法使用了几个变量: O(1)
- 算法使用了长度为n的一维列表: O(n)
- 算法使用了m行n列的二维列表: O(mn)
-
空间换时间
宁可占用更多的内存, 也要缩短程序运行的时间
比如说分布式系统, 将本来一个机器上运算的资源分散到多个机器上, 相当于占用了多个机器的内存, 导致的结果就是程序的运行时间缩短了
递归
递归的特点
- 调用自身
- 结束条件
- 先递归, 再打印
def func(x)
if x>0
func(x-1)
print(x)
- 先打印, 再递归
def func(x)
if x>0
print(x)
func(x-1)
实例 1 汉诺塔
n个圆盘从一根柱子上移动到另一根, 圆盘从上到下是从小往大排列的
- 把n-1 个圆盘当成一个整体, 从柱子A经过柱子C移动到柱子B
- 把第n个圆盘从柱子A移动到C
- 把n-1 个圆盘从B经过A移动到C
def hanoi(n, a, b, c):
if n > 0:
hanoi(n - 1, a, c, b)
print("movie from %s to %s" % (a, c))
hanoi(n - 1, b, a, c)
hanoi(3, 'A', 'B', 'C')
'''
movie from A to C
movie from A to B
movie from C to B
movie from A to C
movie from B to A
movie from B to C
movie from A to C
'''
实例2 递归思路整理
long aFunc(int n) {
if (n <= 1) {
return 1;
} else {
return aFunc(n - 1) + aFunc(n - 2);
}
}
分析:
当 调用 aFunc(3)的时候, 返回 aFunc(2) + aFunc(1)
- 调用 aFunc(2),继续递归调用, 调用aFunc(1) 返回1
- aFunc(2) 递归调用后, 返回 aFunc(1) + aFunc(0), 调用 aFunc(1) 返回1, 调用aFunc(0)也是返回1
- 输出的顺序就是 1 1 2 3
查找
- 什么是查找
在一些数据元素中, 通过一定的方法找出与给定关键字相同数据元素的过程
-
列表查找
从列表中查找指定元素
输入: 列表, 待查找元素
输出: 元素下标(未找到元素时一般返回None或-1)
-
内置列表查找函数
index()
顺序查找
也叫线性查找, 从泪飙第一个元素开始, 顺序进行搜索, 知道找到元素或搜索到列表的最后一个元素
def linear_search(li, val):
for ind, v in enumerate(li):
if v == val:
return ind
else:
return '-1'
res = linear_search([1, 2, 3],2)
print('index:', res)
时间复杂度: O(n)
二分查找
前提是列表必须是有序列表
原理
代码实现
def binary_search(li, val):
left = 0
right = len(li)-1
while left <= right: # 候选区有值
mid = (left+right) // 2
if li[mid] == val:
return 'index',mid
elif li[mid] > val: # 查找的值在mid左侧
right = mid - 1
elif li[mid] < val: # 查找的值在mid右侧
left = mid + 1
else:
return '-1'
li = [1, 2, 3, 4, 5, 6, 7, 8]
print(binary_search(li, 3))
时间复杂度: O(logn)
区别
index()
查找用的是线性列表
二分查找更快
- 运行时间装饰器
import time
def cal_time(func):
def wrapper(*args, **kwargs):
t1 = time.time()
result = func(*args, **kwargs)
t2 = time.time()
print("%s running time: %s"%(func.__name__, t2 - t1))
return result
return wrapper
- 二分查找
from time_cal import cal_time
import random
@cal_time
def binary_search(li, val):
left = 0
right = len(li)-1
while left <= right: # 候选区有值
mid = (left+right) // 2
if li[mid] == val:
return 'index',mid
elif li[mid] > val: # 查找的值在mid左侧
right = mid - 1
elif li[mid] < val: # 查找的值在mid右侧
left = mid + 1
else:
return '-1'
li = list(range(100000000))
binary_search(li, random.randint(0, 10000000))
'''
binary_search running time: 0.0009920597076416016
'''
- 线性查找
from time_cal import cal_time
import random
@cal_time
def linear_search(li, val):
for ind, v in enumerate(li):
if v == val:
return ind
else:
return '-1'
li = list(range(100000000))
linear_search(li, random.randint(0, 100000000))
'''
linear_search running time: 6.8932390213012695
'''
排序
-
什么是排序
将一组无序的记录序列(列表)变成有序的记录序列
列表排序
输入: 列表
输出:有序列表
排序方式: 升序和降序
常用排序算法
排序NB三人组
- 冒泡排序
- 选择排序
- 插入排序
排序NB三人组
- 快速排序
- 堆排序
- 归并排序
其他排序
- 希尔排序
- 计数排序
- 基数排序
冒泡排序
列表每2个相邻的数, 如果前面的比后面的大, 则交换这2个数
一趟排序完成后, 则无序区减少一个数, 一开始有序区的数为0, 有序区增加一个数
整个排序算法排了(n-1)趟