什么是算法
算法 (Algorithm): 一个计算过程, 解决问题的方法
Niklaus Wirth: "程序=数据结构+算法"
时间复杂度
⽤用来评估算法运⾏时间或者运行效率的一个式⼦
一般来说, 时间复杂度高的算法比时间复杂度低的算法慢
常见的时间复杂度排序(按效率排序)
O(1)<O(logn)<O(n)<O(nlogn)<o(n^2)<O(n^2logn)<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 Cmovie from A to Bmovie from C to Bmovie from A to Cmovie from B to Amovie from B to Cmovie 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 timedef 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_timeimport random@cal_timedef 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_timeimport random@cal_timedef 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)趟