算法基础课第一章(上)快排,归并,二分

目录

一、排序算法

(一)快速排序

1、思想

2、模板代码

3、应用:输出列表中第k个数

(二)归并排序

1、思想

2、模板代码

3、应用:找逆序对个数

二、二分

(一)整数二分

(二)浮点数二分


一、排序算法

(一)快速排序

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()

 

  • 19
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值