经典问题
1.寻找最大的子数组和
方法1:分治法。将n长数组中的最大数组和分为以下几种情况,在左子序列中,在右子序列中,横跨两个子序列中。
方法2:由[i,i+1,i+2,,,,j]到【i,i+1,i+2,,,,j+1】只有两种可能,第一种是最大数组仍是原始数组,第二种是加上j+1的新数组。
#方法1:分治法:
def find_max_across_subarray(A,low,mid,high):
left_max=0
max=-float('Inf')
left=mid+1
right=mid
for i in range(mid,low-1,-1):
left_max+=A[i]
if left_max>max:
max=left_max
left=i
right_max=max
for i in range(mid+1,high+1):
right_max+=A[i]
if right_max>max:
max=right_max
right=i
return left,right,max
def find_max_subarray(A,low,high):
if low==high: #基本函数
return low,high,A[low]
mid=(low+high)//2
left_i,left_j,left_max=find_max_subarray(A,low,mid) #分解
right_i,right_j,right_max=find_max_subarray(A,mid+1,high)
across_i,across_j,across_max=find_max_across_subarray(A,low,mid,high)
if left_max>right_max and left_max>across_max: #合并
return left_i,left_j,left_max
if right_max>left_max and right_max>across_max:
return right_i,right_j,right_max
return across_i,across_j,across_max
#方法2:设置一个边界最大值记录一直累加过来的最大值,设置一个当前最大值记录实际动态链接到J的当前最大值。
def find_max_subarray2(A):
n=len(A)
maxArray=0
boundaryA=0
for i in range(n):
boundaryA+=A[i]
if boundaryA<0:
boundaryA=0
if boundaryA>maxArray:
maxArray=boundaryA
return maxArray
排序问题
比较排序:各元素的次序依赖于它们之间的比较。
对n个输入的比较排序中,各算法的上届都要经过O(nlgn)次比较,因此堆排序和归并排序在算法上是渐进最优的,任何已知的比较排序最多就是在常数因子上优于它们。
1.冒泡排序
'''
冒泡排序:有两个版本,原始版本的最好和最坏时间复杂度为O(n^2),优化版本的最好时间复杂度为O(n),这样它的复杂度就与插入排序一样了。
方法:每次比较两个相邻的数,如果顺序不对就交换。
'''
def bubble_sort1(list):
n=len(list)
for i in range(n):
for j in range(n-1,0,-1): #注意这里是从最后到最前
if list[j-1]>list[j]:
list[j-1],list[j]=list[j],list[j-1]
return list
'''改进后的版本'''
def bubble_sort2(list):
n = len(list)
flag=0
for i in range(n):
for j in range(n - 1, 0, -1): # 注意这里是从最后到最前
if list[j - 1] > list[j]:
list[j - 1], list[j] = list[j], list[j - 1]
flag=1
if flag==0:
return list
return list
print(bubble_sort2([1,4,2,3]))
2.插入排序
'''
插入排序:稳定排序,最好情况的复杂度是O(N),最坏情况的复杂度是O(n^2),平均复杂度与最坏复杂度一样。一般来说,我们关心的都是算法的平均复杂度,即最坏情况下的复杂度。
具体方法:依次将一个数据插入到已经排好序的序列中。
本质:增量方法
'''
def insert_sort(list):
Len=len(list)
for i in range(1,Len):
key=list[i]
j=i-1
while j>=0 and list[j]>key:
list[j + 1]=list[j]
j-=1
list[j+1] = key
return list
print(insert_sort([1,3,5,6,2,4]))
3.归并排序
'''
归并排序:最坏的时间复杂度为O(nlgn) 主要时间花在“合并”步骤上。
具体方法:将每个长度为n的序列分解为2个长度为n/2的序列,然后对子序列进行排序,最后合并这两个子序列。
本质:分治法,三个步骤:分解,解决,合并。
程序注意两点:1.因为使用分治法最后需要合并,因此序列需要输入下标;2.需要从原序列copy出一个左子序列和右子序列,
为了简化代码,避免每一次都检测两个子序列是否为空,在两个子序列最后加上一个Inf值的标志,每次仍只需判断子序列的相应大小即可。
'''
def merge(A,s,m,e):
n1=m-s+1
n2=e-m
L=A[s:m+1].copy()
R=A[m+1:e+1].copy()
L.append(float('Inf'))
R.append(float('Inf'))
i=0
j=0
for k in range(n1+n2):
if L[i]<=R[j]:
A[s+k]=L[i]
i+=1
else:
A[s+k]=R[j]
j+=1
return
def merge_sort(A,s,e):
if s<e: #若分解到最小规模,则直接输出
m=(s+e)//2
merge_sort(A,s,m) #分解,因为使用递归,因此也在解决。
merge_sort(A,m+1,e)
merge(A,s,m,e) #合并
return A
print(merge_sort([1,3,5,7,2,10,6],0,6))
4.堆排序
'''
堆排序:集合了插入排序和归并排序的优点,时间复杂度为O(nlgn),且为原址排序。 所有优先队列的复杂度为O(lgn)
堆可以以一个完全二叉树和数组来表示,分为最大堆和最小堆,最大堆规定子节点值小于等于父节点值,最小堆相反。
'''
#维护一个最大堆,对破坏规则的结点进行数值交换,时间复杂度为O(lgn)
def max_heapify(A,i,heapsize):
#输入i是以0位开始的index
left=i*2+1 #写出左右结点
right=left+1
largest=i
if left<heapsize and A[left]>A[i]: #这里用heapsize约束的原因是:在排序时最后一个值与最大值交换后,最大值到数组的最后一位,此时它不应再参与规则。
largest=left
if right<heapsize and A[right]>A[largest]:
largest=right
if largest==i:
return
A[i],A[largest]=A[largest],A[i]
max_heapify(A,largest,heapsize)
#1:创建一个最大堆,时间复杂度为O(n)
def build_max_heap(A):
n=len(A)
for i in range(n//2-1,-1,-1):
max_heapify(A,i,n)
return A
#2:堆排序:时间复杂度为O(nlgn)
def heapsort(A):
build_max_heap(A)
for i in range(len(A)-1,0,-1):
A[0],A[i]=A[i],A[0]
max_heapify(A,0,i) #这里的i是heapsize,每次移走一个最大值到数组最后的位置后,堆的大小为heapsize-1。
return A
A=[5,13,2,25,7,17,20,8,4]
print(build_max_heap(A))
print(heapsort(A))
5.快速排序
'''
快速排序:最坏的时间复杂度为O(n^2),平均时间复杂度为O(nlgn),是最常用的一种排序算法,因为它的平均复杂度的隐含常数因子很小,且为原址排序。
这里的最坏情况是指利用主元素划分的数据及其不平衡,而只要数据以常数比例划分,就算是9:1划分,性能也会达到O(nlgn),
另外,为了使最坏情况尽量不发生,可以使用改进的随机化版本达到期望平均复杂度。
与归并排序类似,快速排序也使用了分治思想。
'''
def quicksort(list,start,end):
if start<end:
q=random_partition(list,start,end) #计算下标q,也就是划分的数值下标
quicksort(list,start,q-1) #分解并解决,不需要合并
quicksort(list,q+1,end)
return list
#写法1
def partition(list,start,end):
x=list[end] #选择一个主元素
left=start
right=end
for i in range(start,end):
if list[i]>x:
list[right]=list[i]
right-=1
else:
list[left]=list[i]
left+=1
list[left]=x
return left
#写法2:交换比主元素大的和比主元素小的数
def partition2(list,start,end):
x=list[end]
i=start-1 #记录大于x的位置
for j in range(start,end):
if list[j]<x:
i+=1
list[i],list[j]=list[j],list[i]
list[i+1],list[end]=list[end],list[i+1]
return i+1
#写法3:改进的随机版本,可以达到期望时间复杂度
import random
def random_partition(list,start,end):
rand=random.randint(start,end)
list[rand],list[end]=list[end],list[rand]
return partition(list,start,end)
A=[13,19,9,5,12,8,7,4,21,2,6,11]
n=len(A)
print(quicksort(A,0,n-1))
非比较排序
线性时间复杂度的排序算法:计数排序,基数排序和桶排序,是通过运算而不是比较来确定排序顺序的。
6.计数排序:输入数组为A,输出数组为B,临时存放数据的数组为C。
适用于输入数组中的n个数均小于k时,若k=O(n),则此时的算法复杂度为O(n).
def counting_sort(A,k):
'''
:param A: 输入序列;
:param k: 输入序列中数据的最大值;
:return:已经排好序的输出。
'''
n=len(A)
B=[0]*n;
C=[0]*(k+1);
for i in range(n):
C[A[i]]+=1 #统计每一位上有多少数
for i in range(1,k+1):
C[i]=C[i]+C[i-1] #累积小于i的有多少数
for i in range(n-1,-1,-1):
B[C[A[i]]-1]=A[i] #根据小于i的数量找到它在B中的位置。
C[A[i]]-=1
return B
A=[2,5,3,0,2,3,0,3]
print(counting_sort(A,5))
7.桶排序
桶排序的最坏时间复杂度为O(n^2),桶排序假设数据服从均匀分布,平均情况下它的时间复杂度为O(n).
桶排序对于相同对象特别多的列表速度特别快。但是遗憾的是需要排序的对象必须是已知的数值。
def bucket_sort(A):
n=max(A)-min(A)+1
B=[0]*n
for i in range(len(A)):
B[A[i]-min(A)]+=1
outlist=[]
for i in range(n):
if B[i]!=0:
outlist+=[i+min(A)]*B[i]
return outlist
A=[2,5,3,0,2,3,0,3]
print(bucket_sort(A))
搜索算法
搜索:1,在一个列表中找到第i小的元素(随机选择);2.在一个列表中找到元素a的下标(二分查找)
注意!!采用分治问题对数组做排序或者选择时,要输入列表的start和end索引,并且要记住用它们来切分数据!
1.随机选择:期望时间复杂度为线性
import random
def random_partition(A,s,e):
rand=random.randint(s,e)
x=A[rand]
A[rand],A[e]=A[e],A[rand]
j=s-1
for i in range(s,e):
if A[i]<x:
j+=1
A[j],A[i]=A[i],A[j]
A[j+1],A[e]=A[e],A[j+1]
return j+1
def random_select(A,s,e,i):
if s==e:
return A[s]
q=random_partition(A,s,e)
k=q-s+1
if i==k:
return A[q]
if i>k:
return random_select(A,q+1,e,i-k)
else:
return random_select(A,s,q-1,i)
A=[1,3,4,6,12,2,7,6]
print(random_select(A,0,7,3))
2.二分查找:二分查找的待查表是一个有序表。
def binary_search(A,value):
low=0
high=len(A)-1
while low<=high:
mid=(low+high)//2
if A[mid]==value:
return mid
if A[mid]>value:
high=mid-1
else:
low=mid+1
return None
A=[1,3,3,6,8]
print(binary_search(A,4))