介绍
算法:计算过程,解决问题的方法
程序=算法+数据结构(动态+静态)
时间复杂度
时间复杂度
原因:因为计算机的运行效率、事件处理的复杂度不同,不能精确的判断每个程序使用的时间,所以需要时间复杂度O()进行估算、比较快慢。
*比较量级(只要未达到质的变化)
O(logn):运算规模每次减半
常见的时间复杂度排序:
O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(n2logn)<O(n3)
快速判断时间复杂度
1、确定问题规模n
2、若有循环减半过程–>logn
3、k层关于n的循环–>nk
若情况复杂要根据代码的执行过程判断
递归与汉诺塔问题
递归特点
1、调用自身
2、结束条件
关注func3和func4:
若先调用递归再进行打印,则最开始传入的数据被压在最后打印
hanoi
hanoi函数编写的底层思路。函数是一种方法;递归则是多次调用方法。
`def hanoi(n,a,b,c):#将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)
hanoi(3,'A','B','C')
`
列表查找
内置列表查找函数:index()
str.index(substring, beg=0, end=len(string))
输入列表、带查找元素;输出元素下标(未找到时返回None或-1)
顺序查找(Linear Search)
# 线性搜索
def Linear_Search(li,val):#在列表li中查找元素val
for ind,v in enumerate(li):#enmerate同时获取元素和索引
if v == val:
return ind
else:
return None
# 测试
def main():
lst = ['a','b','c']
result = Linear_Search(lst,'c')
print(result)
main()
时间复杂度:O(n)
二分查找(Binary Search)
即折半查找;从有序列表的初始候选区li[0:n}开始,通过对待查找的值与中间值的比较。
内置列表排序函数:sort()
def Binary_Search(li,val):
left = 0
right = len(li) - 1
while left <= right:
mid = (left + right) // 2
if li[mid] == val:
return mid
elif li[mid] < val:
left = mid + 1
# continue
# 当pyhton没有执行到分支末尾时会自导执行下一次循环迭代
else:
right = mid - 1
else:# 没找到
return None
时间复杂度:O(logn)
排序
快速排序
1、确定分界点:最左、最右、中间、随机
2、分界点归位:左边的所有数都比分界点小,右边的数都比数大
3、递归处理左右两端
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)
def partition(li,left,right):
tmp = li[left] # 切片时位置不一定是列表最左边
while left < right: #因为当左右重合时就是归位点
while left<right and li[right] >= tmp:#在右边找比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
li = [3,4,6,7,8,2,1,0]
Quick_Sort(li,0,len(li)-1)
时间复杂度:O(nlogn)
问题:
1、最坏的情况:完全倒序,每次至少一个数 O()
2、递归
归并排序
sort()
def merge(li,low,mid,high):
i = low
j = mid + 1
litmp = []
while i <= mid and j <= high:#只要左右两边都有数
if li[i] < li[j]:
litmp.append(li[i])
i += 1
else:
litmp.append(li[j])
j += 1
#while执行完,肯定有一部分没数了
while i <= mid:
litmp.append(li[i])
i += 1
while j <= high:
litmp.append(li[j])
j += 1
li[low:high+1] = litmp
return li
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)
桶排序
def bucket_sort(li,maxnum):
bucket_count = 100 # 创建桶的数量
buckets = [[] for _ in range(bucket_count)] # 创建bucket_count个桶
#放数进桶
for i in li:
index = int((i/maxnum)*(bucket_count-1)) # 线性映射计算位置
buckets[index].append(i) # 在选定桶里放入元素
#桶内排序
for bucket in buckets:
bucket.sort()
#合并桶
sorted_arr = []
for bucket in buckets:
sorted_arr.extend(bucket)
return sorted_arr
import random
li = [random.randint(0,10000) for i in range(100000)]
print(li[:10])
sorted_li = bucket_sort(li,max(li))
print(sorted_li[:10])
高精度
关键:利用decimal库(结果保留多位小数)和Fraction库(分数计算)
Fraction:保留分数计算
可以同时提供分子(numerator)和分母(denominator)给构造函数用于实例化Fraction类,但两者必须同时是int类型或者numbers.Rational类型,否则会抛出类型错误。当分母为0,初始化的时候会导致抛出异常ZeroDivisionError。
decimal:保留高精度小数
import decimal
from fractions import Fraction
decimal.getcontext().prec = 200 # 设置decimal精度为200
n1 = Fraction(2,7)
n2 = Fraction(3,2)
#利用fraction函数避免在计算中的精度丢失
c = n1/n2
#fraction无法直接丢进dicimal计算,转化为除法
c2 = decimal.Decimal(c.numerator)/decimal.Decimal(c.denominator)
print("分数形式:",c)
print("精度200小数:",c2)
前缀和算法
关键:创建字典保存循环时看似无用的和。
思想:利用上述关键点和减法,省略了在暴力枚举中对中段数组数值的重复运算,使得算法更加简洁。
eg.求和为k的子数组:
def SubarraySum(nums,k):
cur_sum = 0
dict = {} # 用于存储在计算时出现的和的个数
dict[0] = 1 # 出现cur_sum - k = 0的情况
count = 0
for num in nums:# 遍历数组并加和
cur_sum += num
if (cur_sum - k) in dict: # 判断数组间是否有差为所求k
count += dict[cur_sum-k]
if cur_sum in dict:# 不管上个if有没有满足,数组加和都要进行存储
dict[cur_sum] += 1
else:
dict[cur_sum] = 1
return count
差分算法
双指针算法
关键:
- 初始值
- 遍历终止条件
- 左右指针的变化规则
1、二分查找
2、两数之和
3、不重复最长字串
# 求不重复最长字串长度
def max_long_str(s):
left = right = 0
s = list(s)
max_len = 0
while(right != len(s)):
right += 1
while(len(s[left:right+1]) > len(set(s[left:right+1]))):
left += 1
s.pop(0)
max_len = max(max_len,right-left+1)
return max_len