本节:
- 理解时间复杂度,递归的master公式
- 用python实现冒泡排序,选择排序,插入排序,对数器,归并排序
- 练习:小和问题,逆序对问题
认识时间复杂度
- 常数时间的操作:跟数据量没有关系,每次时间都是固定的
- 在常数操作数量的表达式中,只要高阶项,不要低阶项,也不要高阶项的系数,剩下的部分如果记为f(N),那么时间复杂度为0(f(N))
- 评价算法好坏,先看时间复杂度的指标,然后分析样本实际运行时间(即常数项)
实例:
一个有序数组A,另外一个无序数组B,请打印B中所有不在A中的数,A数组长度为N,B数组长度为M
算法1:对于数组B中的每个数,都在A中通过遍历方式找。时间复杂度: (M*N)
算法2:对于数组B中每个数,都在A中通过二分的方式找。时间复杂度: (M*logN)
算法3:先把数组B排序,然后用类似外排的方式打印所有在A中出现的数。时间复杂度: o(M*logM) + o(N+M)
这里根据M的数量选择2(量少)或3(量大),通过时间复杂度去判断一个算法的好坏。
# 算法2
de
# 算法3
冒泡排序
- 比较相邻的元素,如果第一个比第二个大(升序),交换他们
- 从第一对到最后一对执行1操作,这步完成后,最后的元素为最大的数
- 对所有元素重复12,已经排序的数不用交换(保证稳定性)。
# 时间复杂为o(n^2)
def bubble_sort(alist):
# 边界问题,-1和不取零,是由于单个数不用比较
for j in range(len(alist)-1, 0, -1): # 这里不需要取0
for i in range(j): # 这里不需要取到-1
if alist[i] > alist[i+1]:
alist[i], alist[i+1] = alist[i+1], alist[i]
li = [54,26,93,17,77,31,44,55,20]
bubble_sort(li)
print(li)
选择排序
遍历数组,取出一个数,与后续所有元素比较,如果后续元素较小,互换位置
# 时间复杂为o(n^2)
def selection_sort(alist): #升序
for i in range(len(alist)-1): # 最后一个数不需要比较
for j in range(i+1, len(alist)): # 不需要跟自己比较,最后一个数需要比较
if alist[i] > alist[j]:
alist[i], alist[j] = alist[j], alist[i]
li = [54,26,93,17,77,31,44,55,20]
selection_sort(li)
print(li)
插入排序(业务还有使用)
原理是构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入
# 数据状况最好时间和最坏时间,一律取最坏状况时间
# 时间复杂度Sn=a1*n + (n*(n-1)d)/2 = n^2
def insert_sort(alist):
for i in range(1, len(alist)):
for j in range(i, 0, -1):
if alist[j] < alist[j-1]:
alist[j], alist[j-1] = alist[j-1], alist[j]
alist = [54,26,93,17,77,31,44,55,20]
insert_sort(alist)
print(alist)
对数器
没有OJ(Online Judge)系统测试自己代码时,就可以用对数器测试。
- 有一个你要测的方法a
- 实现一个绝对正常但复杂度不好的方法b
- 实现一个随机样本产成器
- 实现比对的方法
- 把方法a和方法b对比很多次来验证a是否正确
- 如何有一个样本出错,打印样本并分析
- 当样本数量很多时测试依然正确,可以确定方法a正确
# 实现对数器
def generateRandomArray(size, value):
"""random -> double[0,1), 也确保size和value随机"""
arr = []
arr_len = int((size+1)*random.random()) #range不取最大那个数,则size+1
for i in range(arr_len):
arr.append(int((value+1)*random.random()) - int(value*random.random()))
return arr
"""
Author: lllong33
data: 2019/5/26/0026
"""
import random
import copy
import time
def generate_random_array(max_size, value):
"""
random -> double[0,1),还需要保证 size 和 value 随机
size 范围 [0, size]
value 保证取值范围为[-value+1, value]
int()会取整,保证 arr_len 取值范围为 [0, size]
int(value * random.random) --> [0, value-1]
验证:max([int((value+1) * random.random()) for _ in range(10000)])
"""
arr = []
arr_len = int((max_size+1) * random.random()) # 生成[0, size]
for i in range(arr_len):
random_value = int((value+1) * random.random()) - int(value*random.random())
arr.append(random_value)
return arr
def bubble_sort(alist):
for j in range(len(alist)-1, 0, -1):
for i in range(j):
if alist[i] > alist[i+1]:
alist[i], alist[i+1] = alist[i+1], alist[i]
return alist
def right_mathod(arr):
arr.sort()
return arr
def main():
s_time = time.time()
test_num = 50000
max_size = 10
max_value = 1000
success = True
for i in range(test_num):
arr1 = generate_random_array(max_size, max_value)
arr2 = copy.copy(arr1)
a = bubble_sort(arr1)
b = bubble_sort(arr2)
if a != b:
success = False
print(f'spend time: {time.time() - s_time}', 'Good' if success else f'Fuck fucked! my:{a}, right:{b}')
print(f'my:{a}, right:{b}')
if __name__ == '__main__':
main()
# bubble_sort(generate_random_array(100, 10000))
递归行为和递归时间复杂度
master公式:
T(N) = a*T(N/b) + O(N^d)
log(b,a) > d —> 复杂度为O(N^log(b,a))
log(b,a) = d —> 复杂度为O(N^d * logN)
log(b,a) < d —> 复杂度为O(N^d)
补充阅读:http://www.gocalf.com/blog/algorithm-complexity-and-master-theorem.html
# T(N) = 2T(N/2) + O(1) 即log(2,2)>0 --> O(N^log(2,2))
def getMax(arr, left, right):
if left == right:
return arr[left]
mid = left+ (right - left)//2
left = getMax(arr, left, mid)
right = getMax(arr, mid+1, right)
return max(left, right)
arr = [4,3,2,1]
print(getMax(arr, 0, len(arr)-1))
归并排序(分治)
先递归分解数组,再合并数组
# 时间复杂度O(nlogn),空间复杂度为O(N)
def merge_sort(arr):
if len(arr) <= 1:
return arr
mid = len(arr)//2 #分解
left = merge_sort(arr[:mid])
right = merge_sort(arr[mid:])
return merge(left, right) #合并
def merge(left, right):
"""将两个有序数组left[]和right[]合并为一个大的有序数组"""
l,r = 0,0 #left与right的下标指针
result = []
while l<len(left) and r<len(right):
if left[l] < right[r]:
result.append(left[l])
l += 1
else:
result.append(right[r])
r += 1
result += left[l:]
result += right[r:]
return result
arr = [54,26,93,17,77,31,44,55,20]
sorted_alist = merge_sort(arr)
print(sorted_alist)
小和、逆序对问题
"""在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和,求某个数组小和 eg:[1,3,4,2,5]"""
def merge_sort(arr):
if len(arr) <= 1:
return arr
mid = len(arr)//2 #分解
left = merge_sort(arr[:mid])
right = merge_sort(arr[mid:])
return merge(left, right) #合并
def merge(left, right):
"""将两个有序数组left[]和right[]合并为一个大的有序数组"""
global result_sum
l,r = 0,0 #left与right的下标指针
result = []
while l < len(left) and r<len(right):
if left[l] < right[r]:
result_sum += left[l] * (len(right) - r)
result.append(left[l])
l += 1
else:
result.append(right[r])
r += 1
result += left[l:]
result += right[r:]
return result
result_sum = 0
arr = [1,3,4,2,5]
sorted_alist = merge_sort(arr)
print(result_sum)
"""在一个数组中,左边数如果比右边的数大,则两个数构成一个逆序对,请打印所有逆序对"""
def merge_sort(arr):
if len(arr) <= 1:
return arr
mid = len(arr)//2 #分解
left = merge_sort(arr[:mid])
right = merge_sort(arr[mid:])
return merge(left, right) #合并
def merge(left, right):
"""将两个有序数组left[]和right[]合并为一个大的有序数组"""
global result_sum
l,r = 0,0 #left与right的下标指针
result = []
while l < len(left) and r<len(right):
if left[l] > right[r]:
for i in left[l:]:
print('逆序对:', i, right[r])
result.append(right[r])
r += 1
else:
result.append(left[l])
l += 1
result += left[l:]
result += right[r:]
return result
arr = [1,3,4,2,5]
merge_sort(arr)