目录
一、排序算法
(一)快速排序
1、思想
- 步骤:先选pivot,再核心操作,再递归。
- 如果此时列表中只有一个元素,无需排序直接返回。
- 选择pivot,可以选择第一个元素,可以选择最后一个元素,还可以选择中间元素,也可以随机选择一个元素,还可以三数取中(选择第一个元素、中间元素和最后一个元素的中值作为pivot)。
- 循环当前列表直至处理成pivot左边都小于它,pivot右边都大于它的形式:①初始i=l-1,j=r+1,循环终止条件为i<j,此时列表处理完毕;②如果索引为i的列表元素小于pivot,则i指针不断右移(i++),直到当前指向的列表元素不小于pivot;如果索引为j的列表元素大于pivot,则j指针不断左移(j--),直到当前指向的列表元素不大于pivot。③判断i是否小于j,如果成立,交换i和j指向的列表元素。
- 递归处理左右两边:pivot最后所处的位置是j,因此以pivot为界,左右两边需要排序。
2、模板代码
def quicksort(q,l,r):
if l>=r:
return
mid=(l+r)//2;x=q[mid]
i=l-1;j=r+1
while i<j:
while True:
i+=1
if q[i]>=x:
break
while True:
j-=1
if q[j]<=x:
break
if i<j:
q[i],q[j]=q[j],q[i]
quicksort(q,l,j);quicksort(q,j+1,r)
def main():
n=int(input())
nums=list(map(int,input().split()))
quicksort(nums,0,n-1)
print(" ".join(map(str,nums)))
main()
3、应用:输出列表中第k个数
- 思想:利用快速排序,不断定位第k个数所在区间,直到区间长度缩小为1,返回第k个数的值。
- 题目和代码
def quicksort(q,l,r,k):
if l>=r:
return q[l]
pivot=q[l]
i=l-1;j=r+1
while i<j:
while True:
i+=1
if q[i]>=pivot:
break
while True:
j-=1
if q[j]<=pivot:
break
if i<j:
q[i],q[j]=q[j],q[i]
if j-l+1>=k:
return quicksort(q,l,j,k)
else:
return quicksort(q,j+1,r,k-(j-l+1))
def main():
n,k=map(int,input().split())
nums=list(map(int,input().split()))
res=quicksort(nums,0,n-1,k)
print(res)
main()
(二)归并排序
1、思想
- 步骤:先递归地将序列划分,再通过合并操作将划分后的子序列组合成有序序列。
- 递归:将序列划分成左右两个部分,递归处理两边,递归处理后得到的子序列都是有序的。
- 核心操作:创建空列表存储合并后的有序序列。循环比较子序列中的元素大小,将更小的纳入列表中,指针移动。最终将所有元素合并成一个有序序列。
2、模板代码
def mergesort(q,l,r):
if l>=r:
return
mid=(l+r)//2
mergesort(q,l,mid);mergesort(q,mid+1,r)
i=l;j=mid+1;ans=[]
while i<=mid and j<=r:
if q[i]<=q[j]:
ans.append(q[i])
i+=1
else:
ans.append(q[j])
j+=1
while i<=mid:
ans.append(q[i])
i+=1
while j<=r:
ans.append(q[j])
j+=1
for i in range(l,r+1):
q[i]=ans[i-l]
def main():
n=int(input())
nums=list(map(int,input().split()))
mergesort(nums,0,n-1)
print(" ".join(map(str,nums)))
main()
3、应用:找逆序对个数
(1)题目
(2)方法
①利用在归并排序的过程中,每个要处理的子序列都被划分成左右两个部分,通过指针的移动不断比较左右两个部分的元素大小。而逆序对恰好与左右位置和元素大小相关。
②在归并排序模板代码上的改动:用变量接收每个子序列归并排序过程中逆序对的数目;在左右两个部分比较大小的过程中,如果左边指针当前指向的元素大于右边的,那么满足逆序对条件。此时,可以确定的是至少有mid-i+1这么多的元素大于右边当前j指向的元素(因为左边部分是递增的,i~mid之间的元素都比第i个指向的元素大,那么肯定也是都大于右边当前j指向的元素的,个数可以由i-mid+1得到。)
③例子:左边序列是【1 3 5 7 8】右边是【2 9 10 11 12】;第一轮比较1和2后,1纳入空列表,左指针i右移一位,此时比较3和2,3大于2。而左边序列i后面的元素578都大于3,显然也是大于2的。因此当前比较可以得到的逆序对有【3 2】【5 2】【7 2】【8 2】,有mid-i+1=4-1+1=4个逆序对。
(3)代码:
def mergesort(nums,l,r):
if l>=r:
return 0
mid=(l+r)//2
count=mergesort(nums,l,mid)+mergesort(nums,mid+1,r)
ans=[];i=l;j=mid+1
while i<=mid and j<=r:
if nums[i]<=nums[j]:
ans.append(nums[i])
i+=1
else:
count+=mid-i+1
ans.append(nums[j])
j+=1
while i<=mid:
ans.append(nums[i])
i+=1
while j<=r:
ans.append(nums[j])
j+=1
for i in range(l,r+1):
nums[i]=ans[i-l]
return count
def main():
n=int(input())
nums=list(map(int,input().split()))
res=mergesort(nums,0,n-1)
print(res)
main()
二、二分
(一)整数二分
1、思想
(1)主要应用:用于在一个有序序列中查找指定元素是否存在,返回下标。
(2)方法:
①初始化区间边界:使用变量l和r分别表示区间的左右端点,初始时l为数组的起始位置,r
为数组的结束位置。
②循环跳出条件:循环的条件是 l<r,一旦l >= r,循环终止。
③区间调整:每次循环中计算区间的中间值mid,根据区间中间元素与指定元素的大小比较来调整区间的左右端点,直至跳出循环
④判断是否找到元素:根据下标为l的元素大小判断是否找到指定元素,q[l]=k的话就说明找到了。
2、两种类型:mid取值不同
(1)mid=(l+r)//2:这种取法的mid偏左/中间
l=0;r=n-1
while l<r:
mid=(l+r)//2
if nums[mid]>=k:
r=mid
else:
l=mid+1
(2)mid=(l+r+1)//2:这种取法的mid偏右
l=0;r=n-1
while l<r:
mid=(l+r+1)//2
if nums[mid]<=k:
l=mid
else:
r=mid-1
3、题目和代码
def main():
n,q=map(int,input().split())
nums=list(map(int,input().split()))
for _ in range(q):
k=int(input())
l=0;r=n-1
while l<r:
mid=(l+r)//2
if nums[mid]>=k:
r=mid
else:
l=mid+1
if nums[l]!=k:
print(-1,-1)
else:
print(l,end=" ")
l=0;r=n-1
while l<r:
mid=(l+r+1)//2
if nums[mid]<=k:
l=mid
else:
r=mid-1
print(l)
main()
(二)浮点数二分
1、与整数二分的区别
(1)区别1:循环跳出约束改变,while r-l<1e-7,当r与l之间的差距小于一个很小很小的数的时候,可以认为l和r足够接近,就可以跳出循环了。
(2)区别2:由于浮点数精度要求高,边界的调整方式需要特别注意。相较于整数二分法中的 l= mid+1和r=mid-1,在浮点数二分法中,通常使用 l=mid 或 r=mid 来缩小搜索区间。
(3)区别3:除法用浮点数除法。
2、题目和代码
def main():
n=float(input())
l=-10010;r=10010
while r-l>1e-8:
mid=(l+r)/2
if mid*mid*mid>=n:
r=mid
else:
l=mid
print(f'{l:.6f}')
main()