时间复杂度
时间复杂度是用来估计算法运行时间的一个单位。时间复杂度越高,算法越慢
常见的时间复杂度
O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(n2logn)<O(n3)
快速判断算法复杂度:
- 确定问题规模n
- 是否有循环减半过程 – logn
- 是否有k层关于n的循环 – n的k次方
空间复杂度
空间复杂度是用来评估算法内存占用大小的式子
快速判断算法复杂度:
- 算法使用了几个变量:O(1)
- 算法使用了长度为n的一维列表:O(n)
- 算法使用了m行n列的二维列表:O(mn)
递归
递归的两个特点:
调用自身,结束条件
汉诺塔
def hanoi(n, a, b, c):
if n > 0:
hanoi(n-1, a, c, b)
print('moving from %s to %s' % (a, c))
hanoi(n-1, b, a, c)
汉诺塔移动次数的递推式:
h(x)=2h(x)+1
h大概是2的64次方
斐波那契数列
def fib_list(num):
""" 求出长度为num的fib """
fibList = []
for i in range(num):
if len(fibList) < 2:
fibList.append(1)
continue
fibList.append(fibList[-1] + fibList[-2])
return fibList
1.原始递归
def fib_recur(n):
# 递归,时间复杂度为O(2^n)
if n <= 1:
return n
else:
return fib_recur(n - 1) + fib_recur(n - 2)
2.把之前算过的斐波那契数存在字典中,这样的话递归要用的话就直接存取,而不是去重新计算。
def fib_recur_dict(n, rabbits=None):
if rabbits is None:
rabbits = {1: 1, 2: 1}
if n in rabbits:
return rabbits[n]
res = fib_recur_dict(n - 1) + fib_recur_dict(n - 2)
rabbits[n] = res
return res
3.递推
def fib_loop(n):
# 递推,时间复杂度为O(n)
a, b = 0, 1
for i in range(n):
a, b = b, a + b
return a
查找
输入:列表,待查找元素
输出:元素下标(未找到元素时返回None或-1)
线性查找/顺序查找
Linear Search
从列表第一个元素开始,顺序进行搜索,直到找到元素或搜索到列表最后一个元素为止。
def linear_search(ls, target):
""" 线性查找,时间复杂度为O(n) """
for index, value in enumerate(ls):
if value == target:
return index
return None
二分查找/折半查找
Binary Search
从有序列表的初识候选区li[:n]开始,通过对待查找的值与候选区的中间值的比较,可以使候选区减少一半。
def binary_search(ls, target):
""" 二分查找,时间复杂度为O(logn),要求列表是有序的 """
left = 0
right = len(ls) - 1
while right >= left: # 确保候选区有值
mid = (right + left) // 2
if ls[mid] == target:
return mid
elif ls[mid] > target:
right = mid - 1
else:
# ls[mid] < target
left = mid + 1
return None
排序
冒泡排序
Bubble Sort
原地排序
判断列表每两个相邻的数,如果前面比后面大,则交换这两个数(需要升序排列)。
一趟排序完成后,则无序区减少一个数,有序区增加一个数。
def bubble_sort(ls):
""" 冒泡,时间复杂度为O(n^2) """
for i in range(len(ls) - 1): # 第i趟,从0开始,只剩一个数时不需要最后一趟
exchange = False
for j in range(len(ls) - i - 1): # j:无序区的索引指针,指针顾头不顾尾所以-1
if ls[j] > ls[j + 1]:
ls[j], ls[j + 1] = ls[j + 1], ls[j]
exchange = True
if not exchange:
return
优化如果冒泡排序中的一趟排序没有发生交换,则说明列表已经有序,可以直接结束算法。
选择排序
Select Sort
原地排序
一趟排序记录无序区最小的数的位置,放到无序区的第一个位置…
def select_sort(ls):
""" 选择排序,时间复杂度O(n^2) """
for i in range(len(ls) - 1): # 第i趟
# 无序区最小值的索引位置,默认i
min_loc = i
# 遍历无序区
for j in range(i, len(ls)):
if ls[j] < ls[min_loc]:
min_loc = j
# 找到无序区最小数,与无序区第一个数交换
ls[i], ls[min_loc] = ls[min_loc], ls[i]
插入排序
Insert Sort
原地排序
初始时手里(有序区)只有一张牌,每次(从无序区)摸一张牌,插入到手里的正确位置。
无序区有n-1张牌
@execute_time
def insert_sort(ls):
""" 插入排序,时间复杂度O(n^2) """
for i in range(1, len(ls)): # i表示摸到的牌(无序区)的下标
j = i - 1 # j表示手里的牌(有序区)的下标
# 1.j>i,将j右挪,将i插到j前面 || 2.j<i,直接插到j后面
tmp = ls[i]
while ls[j] > tmp and j >= 0:
# 右挪
ls[j + 1] = ls[j]
j -= 1
ls[j + 1] = tmp
快速排序
Quick Sort
思路:取第一个元素p,使元素p归位,列表被p分成两部分
左边都比p小,右边都比p大,递归完成排序(不断递归,当左右的元素数为0或1时完成递归)
框架:
def quick_sort(data,left,right):
if left < right:
mid = partition(data,left,right)
quick_sort(data,left,mid-1)
quick_sort(data,mid+1,right)
重点实现partition函数:
def partition(ls, left, right):
""" 快速排序,时间复杂度O(nlogn) """
tmp = ls[left]
while left < right:
while left < right and ls[right] >= tmp:
right -= 1
ls[left] = ls[right]
while left < right and ls[left] <= tmp:
left += 1
ls[right] = ls[left]
ls[left] = tmp
return left
缺点:
- python递归最大深度问题(999),可以修改
- 最坏情况,列表是有序的时候…
堆排序
树
树是一种数据结构,树是一种可以递归定义的数据结构。
树是由n个节点组成的集合:
- 如果n=0,那这是一颗空树
- 如果n>0,那存在一个节点作为树的根节点,其他节点可分为m个集合,每个集合本身又是一棵树。
- 根节点:A
- 叶子节点:B,C,H,I,P,Q,K,L,M,N(下面没有子节点的节点)
- 树的深度(高度):4层
- 节点的度:F节点的度是3(分了3个叉)
- 树的度:整个树里最大节点的度-> 6
- 孩子节点/父节点:E是I的父节点,I是E的孩子节点
- 子树
二叉树
二叉树:度不超过2的树,每个节点最多有两个孩子节点。
两个孩子节点被区分为左孩子节点和右孩子节点。
二叉树的存储方式:
- 链式存储方式
- 顺序存储方式
满二叉树
一个二叉树,如果每一层的节点都达到最大值,则为满二叉树。
完全二叉树
叶节点只能出现在最下层和次下层,并且最下面一层的节点都集中在该层最左边的若干位置的二叉树。