基础
时间复杂度
一种表示算法快慢的方式
是用来估计算法运行时间的一个式子(单位)
一般来说, 时间复杂度高的算法比时间复杂度低的算法慢
- 一行代码的时间复杂度1 :
- 一个 for 循环时间复杂度为:
- 两层循环时间复杂度:
例:
时间复杂度为 的代码
1 2 3
| for i in range(n): for j in range(n): print("Hello~")
|
时间复杂度为 的代码
1 2 3
| while n > 1: print(n) n = n // 2
|
简单来说, 当算法过程出现循环折半的时候
时间复杂度就会出现
简单判断时间复杂度
绝大多数简单情况:
- 确定问题规模
n
- 循环过程减半
- k 层关于 n 的循环
空间复杂度
用于评估算法内存占用的式子
空间复杂度的表示方式与时间复杂度完成一样
- 算法使用了几个变量:
- 算法使用了长度为 n 的一维列表:
- 算法使用了 m 行 n 列的二维列表:
在算法中, 时间远比空间重要
因此, 大多算法常采用 “时间换空间” 的做法
递归
递归的特点:
例:
1 2 3 4 5 6 7 8 9 10
| def go(x: int): if x > 0: print(x) go(x - 1)
go(3) >>>3 >>>2 >>>1
|
1 2 3 4 5 6 7 8 9 10
| def go(x: int): if x > 0: go(x - 1) print(x)
go(3) >>>1 >>>2 >>>3
|
汉诺塔
著名的递归问题, 具体内容自己网上查
当有 n 个盘子时:
- 把 n-1 个盘子从 A 经过 C 移动到 B
- 把 n 个盘子从 A 移动到 C
- 把 n-1 个小圆盘经过 A 移动到 C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| def hanoi(n, a, b, c): ''' 参数a, b, c 分别对应a柱, b柱, c柱 ''' if n > 0: hanoi(n-1, a, c, b) print(f"把{a}移动到{c}上")
hanoi(n-1, b, a, c)
hanoi(3, "A", "B", "C")
|
查找
查找: 在一些数据元素中
通过一定的方法找出与给定关键字相同的数据元素的过程
列表查找(线性表查找): 从列表中查找指定元素
顺序查找
也称线性查找, 从列表的第一个元素开始
顺序进行搜索, 直到找到元素或搜索到列表最后一个元素为止
例:
1 2 3 4 5 6 7
| def linear_search(): for i, e in enumerate(li): if e == value: return e else: return -1
|
二分查找
二分查找仅适用于有序列表
又称折半查找, 从有序列表的初始候选区 li[0:n]
开始
通过对待查找的值与候选区中间值的比较, 可以使候选区减少一半
例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| def binary_search(value, li: list) -> int: left = 0 right = len(li) - 1
while left < right: mid = (left + right) // 2
if li[mid] == value: return mid elif li[mid] > value: right = mid - 1 else: left = mid + 1 else: return -1
|
排序
将一组无序的记录序列调整为有序的记录序列
简单的排序:
复杂的排序:
冒泡排序
过于简单, 不予介绍
例:
1 2 3 4 5
| def bubble_sort(li: list): for i in range(len(li) - 1): for j in range(len(li)-i-1): if li[j] > li[j+1]: li[j], li[j+1] = li[j+1], li[j]
|
时间复杂度:
改进代码:
如果在冒泡排序过程中
二层循环没有发生冒泡, 就说明列表已经排序完成
可以直接终止循环
1 2 3 4 5 6 7 8 9
| def bubble_sort(li: list): for i in range(len(li) - 1): exchange = False for j in range(len(li)-i-1): if li[j] > li[j+1]: li[j], li[j+1] = li[j+1], li[j] exchange = True if not exchange: return
|
选择排序
简单的选择排序
遍历一遍列表, 将最小的数添加进新列表
然后删除旧列表中的最小数
1 2 3 4 5 6 7
| def select_sort_simple(): new_li = [] for i in range(len(li)): min_val = min(li) new_li.append(min_val) li.remove(min_val) return new_li
|
min(li)
和 li.remove(min_val)
的时间复杂度为
该代码的时间复杂度为:
该算法空间复杂度较大, 不推荐使用
改进的选择排序
相较于冒泡排序, 这一算法的交换次数会较少一点
原理:
第一层循环为有序区的起始位置
第二层循环找到最小的数的位置, 并将最小数移动到有序区
1 2 3 4 5 6 7 8
| def select_sort(li):
for i in range(len(li)-1): min_loc = i for j in range(i+1, len(li)): if li[j] < li[min_loc]: min_loc = j li[i], li[min_loc] = li[min_loc], li[i]
|
插入排序
原理:
第一层循环为无序区
第二层循环, 判断插入元素大小
按要求移动有序区元素, 然后将元素有序的插入有序区
tmp
为插入的元素
i
为无序区的起始位置
j
为有序区的结束位置
1 2 3 4 5 6 7 8
| def insert_sort(li): for i in range(1, len(li)): tmp = li[i] j = i - 1 while tmp < li[j] and j>= 0: li[j+1] = li[j] j -= 1 li[j+1] = tmp
|
时间复杂度:
快速排序
原理:
- 取一个元素 p(第一个元素), 使元素 p 归位
- 归位: 列表 p 分为两部分, 左边比 p 小, 右边比 p 大
- 递归完成排序
1 2 3 4 5 6 7 8
| [5, 7, 4, 6, 3, 1, 2, 9, 8]
[2, 1, 4, 3, 5, 6, 7, 9, 8]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
|
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| def partition(li, left, right): tmp = li[left] while left < right: while left < right and li[right] >= tmp: right -= 1
li[left] = li[right]
while left < right and li[left] <= tmp: left += 1 li [right] = li[left]
li[left] = tmp return left
|
1 2 3 4 5
| def quick_sort(li, left, right): if left < right: mid = partition(li, left, right) quick_sort(li, left, mid-1) quick_sort(li, mid+1, right)
|
由于 python 有着递归深度限制, 默认为 1000
所以上述代码在排序较大的列表时, 会引发 maximum recursion depth exceeded in comparison
报错
不建议使用太深的递归算法
在最坏的情况下(即倒序的列表), 时间复杂的为
堆排序