概述
分治算法(Divide and Conquer, D&C),是一种经典的递归式解决问题的方法,分而治之,是其主要思想。
适用场景:
1)找到终止条件(基线条件),停止递归,条件越简单越好;
2)能将问题分解,不断地缩小问题规模,直至满足终止条件。
快速排序是采用分治思想的经典排序算法。
Talk is cheap, show me code.
将通过几个简单的小例子帮助理解,均使用python实现。
例题1
求数组之和,例如x = [1, 3, 2, 6, 5,7, 7, 9, 0, 0, 8], sum(x)=48
首先,考虑能不能缩小问题规模,数组x
包含多个元素,求和操作是一个累加操作,始终是两个元素进行相加,那么整个数组可以分解成多次的两个元素相加,此时问题的最小规模即为两个元素相加;
然后,考虑终止条件,数组x
除了包含多个元素外,可能是空数组,也可能包含一个元素,如果是空数组,和为0,包含一个元素的数组,和为x[0]
。
def dc_sum(num_list):
if len(num_list) == 0: # 终止条件
return 0
elif len(num_list) == 1: # 终止条件
return num_list[0]
else:
return num_list[0] + dc_sum(num_list[1:]) # 缩小问题规模
例题2
求数组最大值,例如x = [1, 3, 2, 6, 5,7, 7, 9, 0, 0, 8], max(x)=9
和求和类似,求最大值可以分解成始终是两个元素之间的比较,终止条件一样。
def dc_max(num_list):
if len(num_list) == 0: # 终止条件
return 0
elif len(num_list) == 1: # 终止条件
return num_list[0]
else:
# 缩小问题规模
tmp1 = num_list[0]
tmp2 = dc_max(num_list[1:])
if tmp1 > tmp2
return tmp1
else:
return tmp2
例题3
有一块土地需要均匀的划分成方块,要求划分的方块要尽可能的大,求最大的方块边长,土地如下所示:
题目要求是方块,且尽可能的大,所以下图所示划分是不合理的:
设想一块土地是25x25的,那么符合条件的就是25x25的这样一个划分,如果是50x25呢,符合条件的就是划分出两个25x25的区域,也就是说,尽可能找到一个长宽能够整除的方块,这样才能保证划分的方块尽可能的最大。
但题目给的土地宽高并不能整除,怎么办呢?
可以先从土地中划分出两个640x640的区域,同时余下一块土地,对余下的这块土地采用相同的方式,划分出以最短边为方块的土地,然后再次余下一块土地,循环此操作,直到找到一个长宽能够整除的土地。
def dc_land_split(height, width):
max_len = max(height, width)
min_len = min(height, width)
if max_len%min_len == 0: # 终止条件
return min_len
else:
# 缩小问题规模
return dc_land_split(max_len%min_len, min_len)
快速排序
快速排序算法是经典的排序算法之一,其平均运行时间为
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn),最坏情况为
O
(
n
2
)
O(n^2)
O(n2)。
步骤:
1)选择一个基准值(pivot);
2) 将数组中其余的元素分成两个数组,小于pivot的放在一个数组,大于pivot的放在另一个数组,与pivot相等的放在其中任意一个数组,这样pivot就处于原始数组的中间位置。
3)重复2)操作,将两个数组分别进行排序。
上述步骤2)其实就是一个缩小规模的过程,逐渐的将原始数组进行不断分解成多个小数组,在分解的过程中实现了排序。
其终止条件即为数组中元素个数小于2个。
def quick_sort(num_list):
if len(num_list) < 2:
return num_list
# pivot = num_list[0]
pivot = num_list[len(num_list)//2]
num_list.pop(len(num_list)//2)
less = []
greater = []
for i in num_list:
if i < pivot:
less.append(i)
else:
greater.append(i)
less = quick_sort(less)
greater = quick_sort(greater)
return less + [pivot] + greater
在取pivot时,如果去首元素作为基准值,那么最糟的情况即为对已经排序后的数组进行快排操作,那么始终有一子数组为空数组,此时其调用栈为 O ( n ) O(n) O(n),运行时间为 O ( n 2 ) O(n^2) O(n2),若取数组中间元素,其调用栈为 O ( l o g n ) O(logn) O(logn),运行时间 O ( n l o g n ) O(nlogn) O(nlogn)。