算法设计技巧与分析(三):分而治之(Divide and Conquer)


分而治之(Divide and Conquer)

一、寻找最小最大元素(MINMAX)

比起直接寻找最小最大元素,分而治之法能更好的提升算法的性能。

算法思想:
将数组分为两部分,在每一半中寻找其最小最大元素,而后再对每一半中的最小最大元素进行比较,找到最终的最小最大元素。

算法伪代码如下:

Input:A[1...n]
Output:minimum x, maximum y
minmax(1,n)

def minmax(low, high):
	if low - high == 1:
		if low < high:
			return A[low], A[high]
		else:
			return A[high], A[low]
	else:
		x1, y1 = minmax(low, (low+high)/2)
		x2, y2 = minmax((low+high)/2, high)
		x = min(x1,x2)
		y = max(y1,y2)
		return x,y

令C(n)表示元素比较的次数,其中n为2的幂。当n=2时,C(n)=1;当n大于2时,C(n)=2*C(n/2)+2;故元素比较的次数为C(n)=(3n/2)-2

二、寻找中间或第k小的元素(Finding the Median and the kth Smallest Element)

算法思想:
(1)当规模小于某一阈值时,直接用排序算法返回结果。
(2)当n大于阈值44时,把n个元素划分为|n/5|,若n不是5的倍数,则排除剩余元素(不会有影响,这里只是为了求中项mm),分别排序,然后挑出每一组元素的中间值,再在所有的中间值中,递归调用本算法,挑出中间值mm,mm即为中项的中项。
(3)将所有元素划分为A1、A2、A3三组,分别包含小于、等于、大于mm的元素。
(4)分三种情况,求出第k小元素在这三个数组中的哪一个:
a.若A1的元素数量大于等于K,即第K个元素在第一组内:在A1中递归查找第k小元素。
b.若A1、A2元素个数之和大于等于K,即中项mm为第K个元素:返回mm。
c.否则,第K个元素在第三组:在A3中递归寻找第(k-A1、A2元素数量之和)小元素。

算法伪代码如下:

Input: 由n个元素组成的数组A[1…n]和整数k
Output: 第k小的元素
select(A,1,n,k)

def select(A,low,high,k):
	p = high - low + 1
	if p < 44:
		sort A 
		return A[k]
	q = p/5//将A分为q组,每组5个元素
	将每组q单独排序,找出中项,得到中项的集合M
	mm = select(M,1,q,q/2)//mm为中项的中项
	将A[low...high]分成三部分:
	A1 = {a|a<mm}
	A2 = {a|a=mm}
	A3 = {a|a>mm}
	if |A1| > k:
		select(A1, 1, |A1|, k)
	else if |A1| < k and |A1|+|A2| >= k:
		return mm
	else if |A1| < k and |A1|+|A2| < k: 
		select(A3, 1, |A3|, k-|A1|-|A2|)

其算法分析如下:
算法将其中的元素划分成具有5个元素的各组:
在这里插入图片描述
算法运行时间T(n)<=20cn,其中c是一个足够大的常数,故算法的时间复杂度为O(n)

三、快速排序(Quicksort)

在这里插入图片描述

首先先介绍SPILT算法,他可以将数组重新排列成前面的元素均小于中间的元素、后面的元素均大于中间的元素的形式,中间的元素即为未排序时数组的首元素。

算法伪代码如下:
版本一:(时间复杂度太高)

Input:A[low...high]
Output:重新排列的A、中间元素的位置w即未排序时数组的首元素的新位置
i = low + 1
while i <= high:
	if A[low] < A[i]:
		i++
	else:
		j = i
		//在后面找一个比A[low]小的进行交换
		while A[j] >= A[low] and j <= high:
			j++
		if j <= high:
			交换A[i]和A[j]
			i++
		else:
			break
将A[low]插入A[i+1]//不是交换
return A and i+1

版本二:(时间复杂度低)

Input:A[low...high]
Output:重新排列的A、中间元素的位置w即未排序时数组的首元素的新位置
i = low
for j = low+1 to high://遇见A[j]大于A[low],j继续自增
	if A[j] < A[low]://遇见A[j]小于A[low],交换A[i]和A[j]
		i = i + 1
		if i != j:
			交换A[i]和A[j]
交换A[low]和A[i]
w = i
return A and w

版本二伪代码示意图:
在这里插入图片描述
该算法的时间复杂度为O(N);空间复杂度为O(1)

而后介绍快速排序算法:
在这里插入图片描述
算法伪代码如下:

Input:A[1...n]
Output:升序A
quicksort(A[1...n])

def quicksort(A[low...high]):
	if low < high:
		w = split(A[low...high])//先SPLIT
		quicksort(A[low...w-1])
		quicksort(A[w+1...high])

时间复杂度(也是元素比较次数):

最好情况:快速排序最优的情况就是每一次取到的元素都刚好平分整个数组,此时的时间复杂度公式则为:T[n] = 2T[n/2] + f(n);T[n/2]为平分后的子数组的时间复杂度,f[n] 为平分这个数组时所花的时间;故有:T[n] = 2T[n/2]+n = 2^m T[1]+mn,其中m = logn。
T[n] = 2^(logn)T[1] + nlogn = nT[1] + nlogn = n + nlogn,又因为当n >= 2时:nlogn >= n,所以取后面的 nlogn;
综上所述,快速排序最优的情况下时间复杂度为:O( nlogn )

最坏情况:最差的情况就是每一次取到的元素就是数组中最小/最大的,这种情况其实就是冒泡排序了(每一次都排好一个元素的顺序),这种情况就是冒泡排序的时间复杂度:T[n] = n * (n-1) = n^2 + n;
综上所述:快速排序最差的情况下时间复杂度为:O(n^2)

平均情况:快速排序的平均时间复杂度也是:O(nlogn)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值