目录
分而治之
分而治之(divide and conquer,D&C)——一种著名的递归式问题解决方法。
所谓“分而治之” 就是把一个复杂的算法问题按一定的“分解”方法分为等价的规模较小的若干部分,然后逐个解决,分别找出各部分的解,把各部分的解组成整个问题的解,这种朴素的思想来源于人们生活与工作的经验,也完全适合于技术领域。诸如软件的体系结构设计、模块化设计都是分而治之的具体表现。
使用D&C解决问题的过程包括两个步骤:
(1)找出基线条件(终止递归的条件),这种条件尽可能简单。
(2)不断将问题分解(或者说缩小规模),直到符合基线条件
(3)将拆分的子问题的解逐层返回合并成原问题的解
我们以一个例子来说明,我们给定一个数组[1,2,3,4,5,6],我们需要将这些数字相加,并返回他们的和。首先,这个问题可以使用一个for循环就能解决,但是如何用递归函数来完成这个任务呢?
第一步:找出基线条件,就是该何时停止递归了,我认为数组包含一个或0个元素就该终止了
第二步:缩小问题规模,就是每次递归我们都要离基线条件越来越近。
sum([1,2,3,4,5,6])=21似乎与1+sum([2,3,4,5,6])=1+20=21等效的,但是问题的规模,似乎缩小了欸。
第三步:返回每层子问题的解合并成原问题的解。
python代码实现
def sum(arr):
"""基线条件"""
if len(arr)<=1:
return arr[0]
'''递归条件'''
return arr.pop()+sum(arr)
if __name__ == '__main__':
arr=[1,2,3,4,5,6]
print(sum(arr))
这只是举个例子来说明D&C这种思想,但这个求和的算法效率其实并不比循环的效率高,甚至肯会更糟。
归并排序
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用D&C的一个非常典型的应用。
合并排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。
将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。归并排序也称合并排序。
关于归并排序的图解,大家可以去阅读这篇文章图解排序算法(四)之归并排序 - dreamcatcher-cx - 博客园
python代码实现
"""归并"""
def Merge_sort(arr):
if len(arr)<=1:
return arr
mid=len(arr)//2
"""递归划分成两个数组"""
left=Merge_sort(arr[:mid])
right=Merge_sort(arr[mid:])
return Sort(left,right)
"""排序"""
def Sort(a,b):
c=[]
while len(a)>0 and len(b)>0:
"""如果a的第一个元素比b的第一个元素大,就将b的第一个元素弹出并添加到数组c中,
反之就将a的第一个元素弹出并添加到c中,当a或者b中任意一个数组为空时,跳出循环"""
if a[0]>b[0]:
c.append(b.pop(0))
else:
c.append(a.pop(0))
"""如果a数组为空,就将b数组添加到c中,反之相同"""
if len(a)==0:
c.extend(b)
else:
c.extend(a)
return c
if __name__ == '__main__':
a=[2,6,5,20,1,4,22]
print(Merge_sort(a))
测试结果:
[1, 2, 4, 5, 6, 20, 22]
时间复杂度
归并排序的时间复杂度为O(n*logn),最平均情况和最糟情况均为O(n*logn),因为它的归并时间复杂度为O(logn),排序的时间复杂度为O(n),故总的时间复杂度为O(nlogn)。
快速排序
快速排序是一种常用的排序算法,比选择排序快许多,是冒泡排序算法的优化,也是D&C思想的体现。
快速排序算法通过多次比较和交换来实现排序,其排序步骤如下:
(1)选取基准值(pivot)
(2)根据基准值将数组分成两个子数组:小于基准值和大于基准值的数组
(3)对这个两个子数组重复上述操作,直至满足基准条件
我们以排序数组[2,0,5,3,1,6]为例,它的步骤如下所示
python代码实现
def Quick_sort(arr):
"""基线条件"""
if len(arr)<1:
return arr
pivot=arr[0]#选取基准值
left=Quick_sort([i for i in arr[1:] if i<pivot])#小于基准值
right=Quick_sort([i for i in arr[1:] if i>=pivot])#大于基准值
result=left+[pivot]+right
return result
if __name__ == '__main__':
a=[2,0,5,3,1,6]
print(Quick_sort(a))
测试结果:
[0, 1, 2, 3, 5, 6]
时间复杂度
快速排序的性能依赖于你选择的基准值,平均情况(最优情况)下的时间复杂度为O(nlogn),最糟情况下的时间复杂度为O()。假如每次我们选择数组第一个元素为基准值,且要排序的数组是有序的,那么其递归次数为n,即调用栈的高度为n,栈长为O(n),调用栈每层都涉及O(n)个元素,所以总的时间复杂度为为O();假如每次我们选择数组中间值为基准值,那么其递归次数为,栈长为O(),调用栈每层也涉及O(n)个元素,所以总的时间复杂度为O(nlogn)。