文章目录
分而治之(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)。