前言
详见第一章上前言
目录
时间复杂度和空间复杂度(铺垫)
简单来说,举个写作业的例子,时间复杂度就是总共作业布置了多少题,空间复杂度就是你每次同时能计算多少道题。
详见:http://t.csdnimg.cn/ckRKm或者http://t.csdnimg.cn/Wo0Y3
排序
冒泡排序
相信学过C语言的都知道,使用两层循环即可
就是每轮循环把一个最大或者最小值推到最后
长度为n,则要循环n-1轮
时间复杂度O(n^2),空间复杂度O(1),稳定
选择排序
同理,学过C的两种排序之二,又称“打擂台”,和冒泡一样也是使用两层循环即可
就是每轮把当轮最值挑出放在前面(从哪边开始哪边就是前边)
所以同理,时间复杂度O(n^2),空间复杂度O(1),稳定
相比冒泡笔者个人更喜欢选择
插入排序
和选择有点像,都是把一个跟多个比
区别是选择是把一个“擂主”跟多个没挑出来的比,插入是把一个“当前位置”跟多个已经挑出来的比
举个例子,相当于按身高排队,老师从第五个位置5号拿出一个,跟前面四个依次比,前面四个已经是从矮到高排好了的,此时5号需要从一个方向依次比(比如从矮的比),直到比到有比5号高的就插入在那个比5号高的同学的前面。678...以此类推(但是相当于5号后面的到已经排好的最后一个都要向后移动一位,事实上要一个个比,居然不能一次全部移动玩,笔者觉得不太好,可能笔者想的是另一种排序吧,不过要是笔者这样的话需要多一次循环,可能时间复杂度更大。。。)
和选择一样,时间复杂度O(n^2),空间复杂度O(1),但是不稳定
具体实现:
有[0,i-1]个从小往大排好的,第i个插入,从后往前比,j为n个之一,在操作i和j比大小时
若a[i]<a[j],则第j个要后移一位,给a[i]留位
若a[i]>=a[j],则就把a[i]放在a[j+1]留出的空位上
eg:
n = int(input()) # 读取整数个数
a = list(map(int, input().split())) # 读取整数列表
for i in range(1, n): # 从第二个元素开始遍历
value = a[i] # 当前要插入的元素
insert_idx = 0 # 初始化插入位置为0(实际上这个初始化是多余的,因为下面会找到正确的插入位置)
for j in range(i - 1, -1, -1): # 逆向遍历已排序的元素(这步是从倒数下标i-1+1=i个到倒数下标第-1+1=0个
if a[j] > value:
a[j + 1] = a[j] # 已排序元素大于当前元素时,已排序元素后移
else:
insert_idx = j + 1 # 找到插入位置
break # 退出内层循环
a[insert_idx] = value # 在正确的位置插入当前元素(注意这里的缩进)
print(' '.join(map(str, a))) # 打印排序后的列表
笔者说的方案:三次循环,更慢不好,只能说倒插倒着比确实有道理
n=int(input())
a = list(map(int,input().split()))
for i in range(1,n):
value=a[i]
insert_idx=0
for j in range(0,i):
if a[j]>value:
insert_idx=j
for x in range(j,i):
a[x+1]=a[x]
a[insert_idx] = value
print(' '.join(map(str,a)))
快速排序
时间复杂度:O(nlog(n)),空间复杂度:O(nlog(n)),不稳定
简单来说,不断的递归(子问题),把原序列变成三部分:小于x,x,大于x,直到最终序列长度为1
具体代码有很多种写法:比如用三个列表的
def quicksort(arr):
# 如果数组长度小于等于1,则直接返回该数组
if len(arr) <= 1:
return arr
else:
# 选择第一个元素作为基准
pivot = arr[0]
# 构造小于等于基准的元素构成的子数组 less 构造大于基准的元素构成的子数组 greater
less = [x for x in arr[1:] if x <= pivot] # 输出比第一个元素小的列表
greater = [x for x in arr[1:] if x > pivot] # 输出比第一个元素大的列表
print('less-----------', less)
print('greater-----------', greater)
# 递归地对这两个子数组进行快速排序,然后将它们合并起来,并加上基准元素
return quicksort(less) + [pivot] + quicksort(greater)
if __name__ == '__main__':
arr = [3, 5, 2, 8, 1, 4]
sorted_arr = quicksort(arr)
print(sorted_arr)
或者:只动一个
# coding=utf-8
def quick_sort(array, start, end):
if start >= end:
return
mid_data, left, right = array[start], start, end
while left < right:
while array[right] >= mid_data and left < right:
right -= 1
array[left] = array[right]
while array[left] < mid_data and left < right:
left += 1
array[right] = array[left]
array[left] = mid_data
quick_sort(array, start, left-1)
quick_sort(array, left+1, end)
if __name__ == '__main__':
array = [10, 17, 50, 7, 30, 24, 27, 45, 15, 5, 36, 21]
quick_sort(array, 0, len(array)-1)
print(array)
归并排序
时间复杂度:O(nlog(n)),空间复杂度:O(n),稳定
引入:
两个已经排好序的列表合并成一个排好序的列表(多个也可以)
简单来说:不断的将两列表的首元素比较,弹出极值,直到一边列表空了,另外一边的列表就直接移动到最后
备注:pop函数删除同时会返回删除值(前面说过)
def Merge(A,B):
result=[]
while len(A)!=0 and len(B)!=0:
if A[0]<=B[0]:
result.append(A.pop(0))
else:
result.append(B.pop(0))
result.extend(A)
result.extend(B)
return result
c=[1,2,4]
d=[3,5]
print(Merge(c,d))
所以归并排序就是把列表不断(递归处理!)拆成两半直到有序,然后再像上面那样合起来
a=list(map(int,input().split()))
def Mergesort(A):
if len(A)<2:
return A
mid=len(A)//2
left=Mergesort(A[mid:])
right=Mergesort(A[:mid])
return Merge(left,right)
def Merge(A,B):#用来合并两个列表的
result=[]
while len(A)!=0 and len(B)!=0:
if A[0]<=B[0]:
result.append(A.pop(0))
else:
result.append(B.pop(0))
result.extend(A)
result.extend(B)
return result
#print(Mergesort(a))
print(' '.join(map(str,Mergesort(a))))
笔者感觉这个好写一点
桶排序
更加彻底的归并排序,化整为零
比如按照十位上的数分类:则一个桶负责0-9,10-19...依次类推
from itertools import chain
def Bucket_sort(a,bucketcount):#bucketcount是有多少个数字要排序
minvalue,maxvalue=min(a),max(a)#测量排序的数字的范围
bucketsize=(maxvalue-minvalue+1)//bucketcount#每个桶的大小,注意要加一
res=[[]for i in range(bucketcount+1)]#创建bucketcount个空桶空二维列表,空列表res里面有bucketcount个二维列表
for x in a:
idx=(x-minvalue)//bucketsize#将排序的元素分桶
res[idx].append(x)
for res_x in res:#在每个桶里的res_x分别排序,可以采用其他排序算法
res_x.sort()
return list(chain(*res))#chain还没学到
n=int(input())
a=list(map(int,input().split()))
a=Bucket_sort(a,min(n,10000))#最多10000个桶
print(' '.join(map(str,a)))
总结:
笔者目前认为快速排序最难写
基础算法
时间复杂度
前面铺垫提到过
这里最后的是nlog2(n),但是带回没看明白,不是T(n)=4T(n/4)+2O(n/2)+O(n)吗
枚举
就是所有可能的都试一遍(一般配合循环)
步骤:
1.确定解空间(一维,二维...)
2.确定空间边界(每个变量的最小值,最大值,步长)
3.估算时间复杂度(由前文两次讲时间复杂度)
4.如果超时,则减少枚举空间,变换枚举顺序,重新从1开始
模拟
感觉一般之前做的简单题都是模拟
就是按照题目要求模拟,一般不涉及算法
注意:重复的可以写成函数,分块调试
递归
在C的汉诺塔问题见过
递归:通过自己调用自己把原问题不断变成规模较小的问题,最后层层返回
注意:递归的出口设计(在哪里停止递归哪里就是出口。返回时要能层层返回)和如何把当前问题变成子问题
eg:递归版阶乘函数
def jiecheng(x):
if x<=1:
return 1#递归出口(出口在前)
res=x*jiecheng(x-1)
return res
print(jiecheng(int(input())))
eg2:递归版pow(a,b)
def new_pow(a,b):
if b<=1:
return a
result=a*new_pow(a,b-1)
return result
a,b=map(int,input().split())
print(new_pow(a,b))
进制转换
在大学计算机这门课上学过,位权和基数
python也有自带的转换函数(见第一章)
基数:表示基本数字符号的个数
eg:16进制:0-9,A-F基数为16
位权(权):每位表示的数值
eg:123十进制1表示1个100,2表示2个10
位权=基数^从右往左的位数下标(从0开始,小数部分则从左往右)
任意进制转十进制
每个位的位权×基数之和
模拟代码:
int_to_char = '0123456789ABCDEF'
char_to_int = {}
for idx,char in enumerate(int_to_char):
char_to_int[char]=idx
print(char_to_int)#用于生成一个最高可用于十六进制的位权表
def K_to_10(k,x):
ans=0
for char in x:
ans = ans*k + char_to_int[char]
return ans
print(K_to_10(8,'11'))
十进制转任意进制
整数部分除K取余法,小数部分乘K取余法
模拟代码:
int_to_char = '0123456789ABCDEF'
def Ten_to_K(k,x):#整数部分才行
ans=''
while x != 0:
ans= ans + int_to_char[x % k]#相当于累加余数
x = x// k#整除k
return ans[::-1]
print(Ten_to_K(8,10))
前缀和
前缀和
感觉上就像求总和
前缀和:一般是可迭代的前缀和列表(这里用sum代表),对于一个长度为n的列表a
Sum[i]=a[1]+a[2]+...+a[i]
eg:a=[1,3,4,2,5],sum=[1,4,8,10,15]
前缀和性质:
sum[i]=sum[i-1]+a[i]用于循环得出前缀和
a[left]+...+a[right]=sum[right]-sum[left-1]可以在O(1)时间内求出区间和(前缀和的用途之一)
#求出a的前缀和
def get_presum(a):
n=len(a)
sum=[0]*n
sum[0]=a[0]
for i in range(1,n):
sum[i]=a[i]+sum[i-1]
return sum
#求区间a[l]...a[r]之和
def get_sum(sum,l,r):
if l==0:
return sum[r]
else:
return sum[r]-sum[l-1]
a=[1,2,3,4,5]
sum=get_presum(a)
print(a)
print(sum)
print(get_sum(sum,2,3))
或者将求出前缀和改为内置函数:accumulate是个迭代器?可以连续求
#求出a的前缀和
from itertools import accumulate
def get_presum(a):
sum=list(accumulate(a))
return sum
二维前缀和
这里比较抽象,故用图片加描述
两个求和符号放在一起的意思其实就跟两层for循环一样,就是遍历这一片区域(先求和x=1,y=1到j再到x=2....一直到底),这一片区域的求和。
eg:sum22=a11+a12+a21+a22
这是因为sumi-1j,sumij-1算了两遍ai-1j-1,所以要减去多出来的一遍,最后在加上右下角的aij就是sumij
同理:(也是减两条边,减多了再补)
具体代码: