算法优劣评判标准
时间复杂度:
定义:用来评估算法运行效率的一个式子
print('Hello World') O(1)
for i in range(n):
print('Hello World') O(n)
for i in range(n):
for j in range(n):
print('Hello World') O(n^2)
for i in range(n):
for j in range(n):
for k in range(n):
print('Hello World') O(n^3)
注:O(1)、O(n)、O(n2)..是一个单位,且当n足够大时,n2的值可以忽略n的大小,所以计算时间复杂度时不会出现O(3)、O(n^2+n)的情况。
while n>1:
print(n)
n = n//2
输入:64
输出: 64 32 16 8 4 2
计算:2^6=64,执行次数6=log2 64
时间复杂度:O(logn)
小节:
时间复杂度是用来估计计算法运行时间的一个式子。
一般来说,时间复杂度高的算法比复杂度低的算法慢。
常见的时间复杂度(按效率排序)
O(1)<O(logn)<O(n)<O(nlogn)<O(n^2)<O(n^2logn)<O(n^3)
复杂问题的时间复杂度
O(n!) O(2^n) O(n^n)
如何简单快速地判断算法复杂度
- 确定问题规模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 [%s] from %s to %s" %(n,a,c))
hanoi(n-1,b,a,c)
hanoi(3,"a","b","c")
查找问题
查找:在一些数据元素中,通过一定的方法找出与给定关键字相同的数据元素的过程
列表查找(线性表查找):从列表中产兆指定元素
输入:列表、待查找元素
输出:元素下表(未找到元素之一般返回None或-1)
内置列表查找函数:index()
方法一:顺序查找
顺序查找:也叫线性查找,从列表第一个元素开始,顺序进行搜索,直到找到元素或搜索到列表最后一个元素为止。
# 顺序查找
def linear_search(li,val):
for i,v in enumerate(li):
if v == val:
return i
else:
return None
#i = linear_search([1,2,3,6,7],6)
#print(i)
复杂度:O(n)
方法二:二分查找
二分查找:又叫折半查找,从有序列表的初始候选区li[0:n]开始,通过对待查找的值与候选区中间值的比较,可以使候选区减少一半。
# 二分法查找
def binary_search(li,val):
left,right = 0,len(li)-1
while left<=right: # 候选区有值
mid = (left+right)//2
if li[mid]==val:
return mid
elif li[mid]<val:
left = mid+1
else:
right = mid=1
else:
return None
i = binary_search([1,2,3,6,7],10)
print(i)
复杂度:O(logn)
对比:使用二分查找的速度远远大于顺序查找,但是二分查找只能应用于线性排序的列表。
列表排序
排序:将一组“无序”的记录序列调整为“有序的记录序列”。
列表排序:将无序列表变为有序列表
输入:列表
输出:有序列表
升序和降序
内置排序函数:sort()
冒泡排序
列表每两个相邻的数,如果前面比后面大,则交换这两个数。
一趟排序完成后,则无序区减少一个数,有序区增加一个数。
代码关键:趟,无序区范围。
#冒泡排序
def bubble_sort(li):
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]
import random
li = [random.randint(1,1000) for _ in range(30)]
print(li)
bubble_sort(li)
print(li)
时间复杂度:O(n^2)
优化冒泡排序
#冒泡排序,当列表不再改变时 无需再循环
def bubble_sort(li):
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
print(li)
if not exchange:
return
import random
li = [1,2,3,7,6,8,9]
print(li)
bubble_sort(li)
选择排序
每次选出列表中最大值或者最小值,放在列表的首位或者末尾。
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[min_loc],li[i] = li[i],li[min_loc]
li = [1,2,3,7,6,8,9]
print(li)
select_sort(li)
print(li)
插入排序
拿到手里的牌和之前的所有牌比较,如果比之前的牌小,则记录该位置,最后记录的值就是手里的牌正确排序后的位置。
def insert_sort(li):
for i in range(len(li)):
tmp = li[i]
j = i - 1 # 候选区开头索引-1 就是有序区的结尾
while j >= 0 and tmp < li[j]:
li[j + 1] = li[j]
j -= 1
li[j + 1] = tmp
li = [9, 5, 6, 7, 3]
insert_sort(li)
快速排序
快速排序:快
快速排序思路:
取一个元素p(第一个元素),使元素p归位;
列表别p分成两部分,左边都比p小,右边都比p大;
递归完成排序。
def partition(li,left,right):
temp = li[left]
while left < right:
while left < right and li[right] >= temp:
right -= 1
li[left] = li[right]
while left < right and li[left] <= temp:
left += 1
li[right] = li[left]
li[left] = temp
return left
def quick_sort(li,left,right):
if left<right:
mid = partition(li,left,right)
quick_sort(li,left,mid-1)
quick_sort(li,left+1,right)
li = [9,4,3,6,8,15,3,31]
quick_sort(li,0,len(li)-1)
print(li)
快速排序的效率:
快速排序的时间复杂度 O(mlogn)
快速排序的问题:
最坏情况
递归
堆排序
树与二叉树
堆是一种特殊的完全二叉树
大根堆:一棵完全二叉树,满足任一节点都比其孩子节点大
小根堆:一棵完全二叉树,满足任一节点都比其孩子节点小
堆的向下调整:
假设:节点的左右子树都是堆,但是自身不是堆。
堆排序过程
堆排序过程:
- 建立堆。
- 得到堆顶元素,为最大元素。
- 去掉堆顶,将堆最后一个元素放到堆顶,此时可通过一次调整重新使堆有序。
- 堆顶元素为第二大元素。
- 重复步骤3,知道堆变空。
堆的向下调整
向下调整函数
def sift(li,low,high):
i = low
tmp = li[i]
j = i*2+1
while j<=high:
if j<high and li[j+1]>li[j]:
j = j+1
if tmp<=li[j]:
li[i]=li[j]
i = j
j = i*2+1
else:
break
li[i] = tmp
li = [1,9,8,7,6,5,0,2]
sift(li,0,len(li)-1)
print(li)
堆排序函数
def heap_sort(li):
n = len(li)
# 建堆
for i in range(n//2-1,-1,-1):
sift(li,i,n-1)
# 建堆完成
for i in range(n-1,-1,-1):
li[i],li[0]=li[0],li[i]
sift(li,0,i-1)
li = [2,6,4,6,8,5,3,10]
heap_sort(li)
print(li)
堆排序的内置模块heapq
import heapq
import random
li = list(range(100))
random.shuffle(li)
heapq.heapify(li)
print(li)
for i in range(len(li)-1):
print(heapq.heappop(li),end=' ')
归并排序
假设li是以mid为分界线的左右两边都是正序列表的列表
从头比较两边分列表的值,较小的取出放在新列表里,再比较该分列表下一个值和另一列表的当前值大小
def merge(li, low, mid, high):
i = low
j = mid + 1
ltmp = []
while i <= mid and j <= high:
if li[i] < li[j]:
ltmp.append(li[i])
i += 1
else:
ltmp.append(li[j])
j += 1
li[low:high + 1] = ltmp + li[i:mid + 1] + li[j:high + 1]
def merge_sort(li, low, high):
if low < high:
mid = (low + high) // 2
merge_sort(li, low, mid)
merge_sort(li, mid + 1, high)
merge(li, low, mid, high)
li = [3, 6, 4, 5, 8, 1, 2, 9, 0, 7]
merge_sort(li, 0, len(li) - 1)