from mergeSort_recursion import mergesort
import random
def partion(a, m, m_index):
#对a进行排序,使得比m小的元素放在m前面,比m大的元素放在m后面
#输入:m_index(m在a中的index)
#返回m前面, m后面各自元素的数目,以及m在新数组中的index
#将m与数组第一个元素交换位置,然后即可像快速排序一样将所有元素以m为中间元素
#分到左右两边
tmp = a[0]
a[0] = m
a[m_index] = tmp
i = 0
j = len(a)-1
control_m = a[0]
while i < j:
while i< j and a[j] >= control_m:
j -= 1
a[i] = a[j]
while i < j and a[i] <= control_m:
i += 1
a[j] = a[i]
#此时i = j, a[i]应该是最终的控制关键字所在位置
a[i] = control_m
print("m:{},after partion, a:{}".format(m, a))
return i, len(a)-i-1, i
def bfprt(a, k):
#得到a中第k大的元素
if len(a) < 5:
#元素数目不足5个时,排序后取index为k-1的数,即为第k大的元素
#由于只有在元素数目很小时才使用排序,因此时间复杂度很小,可以看作常数时间复杂度
mergesort(a,0, len(a)-1)
return a[k-1]
total_num = len(a)
splits = total_num//5 #一共分成这么多组
#获取每一个分组的中位数
split_medians = []
for i in range(splits):
cur = mergesort(a[i*5:(i+1)*5],0, 4)
mid = cur[2]
split_medians.append(mid)
#递归调用bfprt算法,求这些中位数的中第splits//2大的元素,也就是中位数的中位数
m = bfprt(split_medians, splits//2)
#求出m在a中的index
m_index = [i for i in range(total_num) if a[i] == m][0]
#根据m对a进行划分,线性复杂度
#num1, num2:小于m的元素的数目, 大于m的元素数目
num1, num2, m_index = partion(a, m, m_index)
if k == num1+1:
return m
elif k <= num1:
#说明在s1集合中
return bfprt(a[:m_index], k)
else:
return bfprt(a[m_index+1:], k-1-m_index)
a = [5, 3, 1, 8, 2,10, 15, 18, 11,13, 16, 17, 12, 19, 0, 6, 4, 7, 9, 14]
random.shuffle(a)
k = 3
print(bfprt(a,7))
BFPRT算法总体思想:
1.将所有n个数据分为n/5(的向下取整)组,每一组有5个元素, 用归并/快速排序求出每一组的中位数(时间复杂度为*5log5 * n/5, 即O(n)),并递归的调用BFPRT算法求出这n/5个中位数的中位数(T(n/5)), 也就是第n/10大的元素,记为m,找到m在原数组中的位置index(O(n))。
2.将原数组以m为分界点,将大于m的num2个元素放在m右边,小于等于m的num1个元素放在m左边,此时m的新位置为index_new
此处要注意,在找到m的合适位置之前,应将m与首元素位置互换。!!
3.把k与num1比较,如果k=num1+1,则说明分界点m就是第k大的元素,如果k<=num1,说明第k大的元素在m左边,接下来去m左边寻找;否则在m右边,应去m右边寻找。这样就将原问题转化为规模更小的问题了。