一、递归求数组最大值
递归行为就是说自己调用自己,但是它必须有一个离开条件,不然会陷入死循环。
递归的实质就是计算机自己进行压栈,执行到递归的地方则把父行为压进栈中,进而执行子行为;依次反复,直到执行到离开条件为止。
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作为逆序对输出(左边大于右边)