时间复杂度
时间复杂度是一个算法流程中,最差情况下常数操作数量的指标。
例子:一个有序数组A,另一个无序数组B,请打印B中所有不在A中的数。A数组长度为N,B数组长度为M。
第一种方法:B中每个数在A中遍历一下,输出所有不在A中的数。
A = list(map(int,input().split()))
B = list(map(int,input().split()))
N = len(A)
M = len(B)
for i in range(M):
j = 0
while j<N and B[i]!=A[j]:
j += 1
if j==N:
print(B[i])
第一种方法包含两个循环,对B遍历一遍,B中每个数又要在A中遍历一遍,因此时间复杂度为 O ( M × N ) O(M\times N) O(M×N)。
第二种方法:B中每个数在A中二分找一下。
因A有序(假设升序),将A进行二分,分为left
和right
。中间位置的数为mid
。将B中每个数先与mid
相比较,如果B[i]
比mid
大,那么B[i]
只可能在A的right
,如果B[i]
比mid
小,那么B[i]
只可能在left
。每次砍一半的操作,时间复杂度为
O
(
log
2
N
)
O(\log_2N)
O(log2N),又对B中每个数遍历一次,故这个算法的时间复杂度为
M
×
O
(
log
2
N
)
M\times O(\log_2N)
M×O(log2N)。
二分法的停止条件是左边界超过右边界,
while L<=R
,也不用担心mid
在加加减减过程中越界。
A = list(map(int,input().split()))
B = list(map(int,input().split()))
N = len(A)
M = len(B)
def binSearch(A,b):
L = 0
R = len(A)-1
while L<=R:
mid = int((L+R)/2)
# 在A中找到b了,返回True
if b==A[mid]:
return True
# b比A[mid]小,说明b在左边
if b<A[mid]:
R = mid - 1
# b比A[mid]大,说明b在右边
if b>A[mid]:
L = mid + 1
# 没找到b,返回False
return False
for i in range(M):
flag = binSearch(A,B[i])
if not flag:
print(B[i])
第三种方法:先给B排序,然后用类似外排的方法打印不在A中的数。
关于排序算法之后单独介绍,对B排序后,在A,B均为有序数组。
a
指针指向1,b
指针指向3,当a<b
时,a
只能往后移动,当a=b
时,b
只能往后移动,当a>b
时,打印b
,同时b
往后移。
(1)排序代价
O
(
M
×
log
M
)
O(M\times\log M)
O(M×logM) (2)打印数过程中,a
最多指
N
N
N个数,b
最多指
M
M
M个数,所以时间复杂度
O
(
N
+
M
)
O(N+M)
O(N+M)
因此整个算法时间复杂度
O
(
M
×
log
M
)
+
O
(
N
+
M
)
O(M\times\log M)+O(N+M)
O(M×logM)+O(N+M) 。
停止条件是A或B数组已经到界了,如果A到界了,那么B[b,M-1]均需打印出来;如果B到界了,说明B数组里的数已经查找完了。
A = list(map(int,input().split()))
B = list(map(int,input().split()))
N = len(A)
M = len(B)
a = b = 0
while a<N and b<M:
if A[a]<B[b]:
a += 1
elif A[a]==B[b]:
b += 1
else:
print(B[b])
b += 1
while b<M:
print(B[b])
b += 1
排序算法
冒泡排序
算法思想:从小到大排。每次找到最大的数放在末位。
第一次看0,1位置比较大小,0位置比1位置大就交换。
第二次看1,2位置比较大小,1位置比2位置大就交换,依次进行下去,第一次遍历,最大的数会被排到最后。
第二次只需遍历0至end-1个数,因为最大的数已经到最后,然后重复第一步步骤,直到所有数被排好。
def BubbleSort(arr):
length = len(arr)
if not arr or length < 2:
return arr
for i in range(length,-1,-1):
for j in range(0,i-1):
if arr[j] > arr[j+1]:
tmp = arr[j]
arr[j] = arr[j+1]
arr[j+1] = tmp
return arr
常数操作次数为 N + ( N − 1 ) + ( N − 2 ) + ⋯ + 2 + 1 N+(N-1)+(N-2)+\cdots+2+1 N+(N−1)+(N−2)+⋯+2+1,所以算法时间复杂度为 O ( N 2 ) O(N^2) O(N2)。
选择排序
算法思想:从小到大排。
第一步,从0到N-1位置找到最小的数放在[0]位置上。
第二步,从1到N-1位置找到最小的数放在[1]位置上。
…
如何确定最小的数? 先找一个基准数,比基准数小的就互换,成为新的基准数。
def SelectionSort(arr):
length = len(arr)
if not arr or length < 2:
return arr
for i in range(length):
minNumber = arr[i]
for j in range(i+1,length):
if minNumber > arr[j]:
tmp = minNumber
minNumber = arr[j]
arr[j] = tmp
arr[i] = minNumber
return arr
算法时间复杂度为 O ( N 2 ) O(N^2) O(N2)。
插入排序
算法思想:从小到大排。
每进来一个数,与它前面一个数比较,比前面的数小就交换,到了前一个位置后,在与前前位置的数比较大小,小就交换,大就停止。再进来一个数,按照上面的方法与前面的数交换插入。
def InsertSort(arr):
length = len(arr)
if not arr or length < 2:
return arr
for i in range(1,length):
j = i - 1
while j>=0:
if arr[j] > arr[j+1]:
tmp = arr[j]
arr[j] = arr[j+1]
arr[j+1] = tmp
j -= 1
else:
j = -1
return arr
插入排序的时间复杂度与数据状况有关。例如数组[1,2,3,4,5]整体有序,那么在计算时代价为 O ( N ) O(N) O(N),但如果是[5,4,3,2,1]要升序排列,那么代价就是 O ( N 2 ) O(N^2) O(N2),按最差情况计算,所以插入排序算法时间复杂度为 O ( N 2 ) O(N^2) O(N2)。
归并排序
算法思想:将数组一分为二,分别把左侧和右侧部分排好序,然后再利用外排的方式将整体排好序。
左右排好序后,需要一个辅助数组,左右两边比较大小,将小的数依次填入辅助数组。额外空间复杂度为O(n)。
def MergeSort(arr):
length = len(arr)
if not arr or length < 2:
return arr
sortProcess(arr,0,length-1)
return arr
def sortProcess(arr,L,R):
if L==R:
return arr[L]
mid = (L+R)//2
sortProcess(arr,L,mid)
sortProcess(arr,mid+1,R)
merge(arr,L,mid,R)
def merge(arr,L,mid,R):
help = []
p1 = L
p2 = mid+1
while p1<=mid and p2<=R:
if arr[p1] < arr[p2]:
help.append(arr[p1])
p1 += 1
else:
help.append(arr[p2])
p2 += 1
while p1<=mid:
help.append(arr[p1])
p1 += 1
while p2<=R:
help.append(arr[p2])
p2 += 1
for i in range(len(help)):
arr[L+i] = help[i]
递过算法的时间复杂度计算有一个master公式:
T
(
N
)
=
a
×
T
(
N
b
)
+
O
(
N
d
)
T(N)=a\times T(\frac{N}{b})+O(N^d)
T(N)=a×T(bN)+O(Nd)
N
b
\frac{N}{b}
bN表示子过程的规模,
a
a
a表示子过程的个数,
O
(
N
d
)
O(N^d)
O(Nd)表示除去调用子过程之外,剩余操作的时间复杂度。
- 当 log b a > d \log_ba>d logba>d时,算法复杂度为 O ( N log b a ) O(N^{\log_ba}) O(Nlogba)
- 当 log b a = d \log_ba=d logba=d时,算法复杂度为 O ( N d × log N ) O(N^d\times\log N) O(Nd×logN)
- 当 log b a < d \log_ba<d logba<d时,算法复杂度为 O ( N d ) O(N^d) O(Nd)
归并排序将数据规模一分为二,分为左右两个子过程,即 a = 2 , b = 2 a=2,b=2 a=2,b=2,且外排时间复杂度为 O ( N ) O(N) O(N), d = 1 d=1 d=1,所以为第二种情况。归并算法的时间复杂度为 O ( N × log N ) O(N\times\log N) O(N×logN)。