牛客网_算法初级班_Lesson1_Part II_递归求数组最大值_归并排序_小和问题_逆序对问题_python语言描述

一、递归求数组最大值

递归行为就是说自己调用自己,但是它必须有一个离开条件,不然会陷入死循环。
递归的实质就是计算机自己进行压栈,执行到递归的地方则把父行为压进栈中,进而执行子行为;依次反复,直到执行到离开条件为止。

code:

def getMax(array, L, R):
    '''
    利用递归行为:自己调用自己;求出一个数组中的最大值:
        右边取得最大值,右边取得最大者,两个最大值再取最大值
        即为全局最大值
    
    递归函数就是系统在帮你压栈
    '''
    
    if L == R:
        return array[L]
    
    #mid = L + (R-L)//2  #相当于 (L + R)//2 ,一个技巧
    mid = L + ((R-L) >> 1)
    leftMax = getMax(array, L, mid)
    rightMax = getMax(array, mid+1, R)
    
    return max(leftMax, rightMax)

arr = [1,2,3,4]
print (getMax(arr, 0, len(arr) -1))

二、归并排序

归并排序就是分治的思想。分就是说利用二分法依次把原数组一分为二,然后再把小的一分为二,以此反复。直到分成独立个体。然后再“治”,也就是再合并(merge)过程。
注意: 每一个子过程也都会去合并(merge),例子可以参考逆序对问题去理解。就是说,分的过程利用递归分为了左右两部分;然后左右两部分都会去调用merge迭代过程。具体见:逆序对问题

code:

def MergeSort(arr):
    '''
    归并排序:
    利用递归过程。将原数组分为左右两个,
    左边右边分别有序后,再分别指针从左走,然后比较左右的数
    依次传进辅助数组中,直到有一侧赋值完以后,另一侧的剩下的直接拷贝
    当全部完成以后,将辅助数组中的数全部拷贝原数组即可
    '''
    return sortProcess(arr, 0, len(arr)-1)

def sortProcess(arr, L, R):
    '''
    归并排序中的排序功能函数
    
    递归的时候参数不能出现常数,因为参数就是不断地变化的,如果定死了常数就不变了
    '''
    if L == R:
        return
    
    mid = L + ((R-L) >> 1)   #相当于(L+R)//2 ;位运算:除以二也就右移一位
    sortProcess(arr, L, mid)
    sortProcess(arr, mid+1, R)  #这块就已经做到了左右两边分别有序
    return merge(arr, L, mid, R)

def merge(arr, L, mid, R):
    '''
    归并排序中的归并函数功能
    即将两个排序好的子序列合并到一起
    
    '''
    help_length = R - L + 1
    help_arr = []
    
    for i in range(0, help_length):
        help_arr.append(0)
        
    index = 0 #辅助数组的索引
    p1 = L   # L和R是随着变化的,不能一直赋值为0
    p2 = mid+1
    while p1 <= mid and p2 <= R : #保证不越界,也就是给一个终止条件
        if (arr[p1] < arr[p2]) :
            help_arr[index] = arr[p1]
            index += 1
            p1 += 1
        else :
            help_arr[index] = arr[p2]    
            index += 1
            p2 += 1
    
    while p1 <= mid:
        help_arr[index] = arr[p1]
        p1 += 1
        index += 1

        
    while p2 <= R:
        help_arr[index] = arr[p2]
        p2 += 1
        index += 1

    
    for i in range (len(help_arr)):
        arr[L+i] = help_arr[i]
    
    return arr
    
print (MergeSort([5,1,3,7]))

三、小和问题

题目描述:
在这里插入图片描述我们用归并排序做这道题。
思想:
把原始数组一分为二,依次拆开;左侧排好序(对子序列merge),右侧排好序(对子序列merge),然后再一起merge并返回。

code:

def smallSum(arr):
    '''
    小和问题:
    用归并排序(分治)实现,左右两边分别(分)排好序以后,合并(治)
    因为左右两边都排好了序,因此只要右边的第一个数大于左边的,那么右边的后面的所有的数都大于左边的该数,故不用比较直接加个数即可
    这就导致了时间复杂度的降低
    (左右两侧合并前,各自计算小和,也就是比较大小)
    '''
    return mergeSort(arr, 0, len(arr) - 1)

def mergeSort(arr, L, R):
    '''
    “分”的部分,让左右两部分有序
    递归过程(感觉递归最后的输出取决于跳出条件的return,这个return返回的是0;
    如果返回数组肯定就不能返回这个)
    '''
    if L == R:
        return 0
    
    mid = L + ((R-L) >> 1)
    left_smallSum = mergeSort(arr, L, mid)
    right_smallSum = mergeSort(arr, mid+1, R)
    return (left_smallSum + right_smallSum + merge(arr, L, mid, R))

def merge(arr, L, mid, R):
    '''
    迭代过程,“治”
    将左右两边排好序的序列合并到一起,找出两个指针,比较q1和q2的大小记录小和
    '''
    help_length = R - L + 1
    help_arr = []
    for i in range(0, help_length):
        help_arr.append(0)
        
    p1 = L
    p2 = mid+1
    index = 0
    while p1 <= mid and p2 <= R:
        if arr[p1] < arr[p2]:
            help_arr[index] = arr[p1]
            p1 += 1
            index += 1
        else:
            help_arr[index] = arr[p2]
            p2 += 1
            index += 1
        
    while p1 <= mid:
        help_arr[index] = arr[p1]
        p1 += 1
        index += 1
    
    while p2 <= R:
        help_arr[index] = arr[p2]
        p2 += 1
        index += 1
    
    for i in range(0, len(help_arr)):
        arr[i] = help_arr[i]

注意:

  • 变量 help_arr 是辅助数组,用来临时储存每一块(左侧、右侧、与整体),可以看出来它每次都会在merge函数调用时重新定义为空;这是因为每一次调用以后,都会把临时变量 help_arr 中的元素赋值给arr,因此要注意这里是arr[L + i]
  • 小和问题记录的是左侧小于右边的称之为小和,那么因此利用归并排序解题的时候,我们要找的是右边大于左边的,并且要注意如果右边的某一个数大于左边的数,那么右边的这个数的后面所有数都会大于左边的数(因此就不用重复比较,降低了时间复杂度;只需要个数(也就是索引做差) * 左边的数(表示有几个这样的值))

四、逆序对问题

题目描述:
在这里插入图片描述
这道题其实和小和问题同类型,我们可以利用归并排序做。唯一不同的点是:小和问题关心的是左边小于右边;而逆序对关心的是左边大于右边的;因此只需要更改merge()函数里的两行即可。

code:

def smallSum(arr):
    '''
    逆序对问题:
    用归并排序(分治)实现,左右两边分别(分)排好序以后,合并(治)
    因为左右两边都排好了序,因此只要左边的数a大于右边的数b,那么左边数a的后面的所有的数都大于右边的该数,
    故不用比较直接加 个数 即可。
    这就导致了时间复杂度的降低
    
    打印逆序对的时候,固定住p2,变化的是p1与p2的结合,依次打印即可
    '''
    return mergeSort(arr, 0, len(arr) - 1)

def mergeSort(arr, L, R):
    '''
    “分”的部分,让左右两部分有序
    递归过程(感觉递归最后的输出取决于跳出条件的return,这个return返回的是0;
    如果返回数组肯定就不能返回这个)
    '''
    if L == R:
        return 0
    
    mid = L + ((R-L) >> 1)

    return (mergeSort(arr, L, mid) + mergeSort(arr, mid+1, R) + merge(arr, L, mid, R))

def merge(arr, L, mid, R):
    '''
    迭代过程,“治”
    将左右两边排好序的序列合并到一起,找出两个指针,比较q1和q2的大小记录逆序对
    '''
    help_length = R - L + 1
    help_arr = []
    for i in range(0, help_length):
        help_arr.append(0)
        
    p1 = L
    p2 = mid+1
    index = 0
    
    smallMergeSum = 0

    while p1 <= mid and p2 <= R:
        if arr[p1] <= arr[p2]:
            help_arr[index] = arr[p1]            
            p1 += 1
            index += 1
        else:
            help_arr[index] = arr[p2]
            smallMergeSum += (mid - p1 + 1)
            for p in range (p1, mid+1):
                print (str(arr[p]) + ' ' + str(arr[p2]) + '|')
            p2 += 1
            index += 1
        
    while p1 <= mid:
        help_arr[index] = arr[p1]
        p1 += 1
        index += 1
    
    while p2 <= R:
        help_arr[index] = arr[p2]
        p2 += 1
        index += 1
        
    for i in range(0, len(help_arr)):
        arr[L + i] = help_arr[i]
    print ('arr', arr)
    
    return smallMergeSum

print (smallSum([1,3,4,2,5]))

注意:

  • merge函数中倒数第四行给arr数组赋值时,不可以用arr[i] = help_arr[i],因为每次调用merge函数都会重新定义help_arr列表,这样的话数组会变导致最后结果有偏差。如图(reverse_arr就是help_arr;只不过换了个名字):
    在这里插入图片描述
  • 计算逆序对个数:左边的第i个数大于右边的数a的话,那么左边第i个数后面的所有(mid-i+1)都大于a(因为子序列都有序,递增的顺序)
  • 打印所有逆序对的时候需要在merge过程中去打印,因为每一次调用merge的arr、L、R、mid都是在变的(因为递归过程);故打印时需要固定p2,使p1变化,并打印出满足条件的p1与p2作为逆序对输出(左边大于右边)
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值