BFPRT算法

问题引入

希望你可以在一个无序数组中找到第k小的数,开始你的表演。
方法1:我们可以采取排序的方式,但是这样就会产生一个O(NlogN)的代价。
方法2:我们可以采取partation的过程,如果命中直接返回,否则看目标值在哪个区域,再次partition(快拍的改进)。虽然他不是BFPRT,但是已经足够优秀了。时间复杂度是基于概率的,长期的期望可以做到O(N),最好O(N),最差的时候为O(N^2)(每次值搞定一个数),就像是选择排序一样。最好情况下,即每次选择划分值都能落在最中间。那么时间复杂度的表达式T(N)=T(N/2)+O(N)。应用master公式我们可以得到其时间复杂度是O(N)的。
方法3:我们可以使用BFPTR算法,这样可以做到时间复杂度严格的O(N),与概率无关。

BFPRT算法

BFPRT算法与第二种算法的差别就在划分值的选择策略上。剩余的东西与方法2一样的。

步骤
  1. 将所有的数据按照5个一组,不满5个的另作一组,进行分组。O(1)。
  2. 对每个小组内5个元素进行排序,每个小组排序的时间复杂度是O(1),有N/5个组,所以时间复杂度是O(N)的。
  3. 拿每个数组的中位数出来作为一个新数组new_arr。长度是N/5的。(O(N))
  4. 递归调用BFPRT算法,参数为new_arr(N/5),要查找的数为中位数,正好是数组一半的第k小的数。然后用返回值作为划分值。至少有3/10的数比他大,最多有7/10的数比你多。。同理,至少有3/10的数比他小。T(N/5)
  5. 用返回值进行partition。O(N)
  6. 如果命中直接返回,如果没有命中,走左边还是走右边。T(7N/10)。
疑问
1.为什么在步骤1中选择5个数作为一组?

其实你可以选择 3个、7个或者9个,选择的数字的个数不同其实就是在搞复杂度的表达式,如果我们选择的数字个数使得复杂度不能收敛到O(N),那么就不能选择。外传:因为BFPRT算法的发明者有5个人并且因为5可以使得复杂度表达式收敛到O(N)上。

2.为什么你在步骤2中对于5个数进行排序的时候为什么可以说时间复杂度是O(1)的?

因为我们只有5个数啊。数据量很小,此时使用插入排序可以做到很快。

3.为什么在步骤4中,你说一定有至少有3/10的数比他大,最多有7/10的数比你多?

这里这个3/10是与我们选择几个数作为一组是有关的,如果选择5个数作为一组,那么我们会得到下面这样的一张图。

在这里插入图片描述
我们选择出每组中的中位数(5,7,6,5,3),然后排序得到其中位数(5),由于5是中位数,所以在这组选择出来的数据中,一定有1/2比该数大(总体数据的1/10),然后将这1/2放会到原来的数组中,那么又有两个数比选择出来的数大,这样就多出了2/10的数据,总体加起来就是3/10这个魔数了

4. 为什么步骤6中出现了一个7/10这个魔术?他有什么含义?

7/10这个魔数同样是因为我们选择了5个数作为一组,那么至少有3/10的数据比我们的划分值小,也就是说最多有7/10的数据比我们的划分值大,由于在复杂度的计算的过程中,我们计算的是最差时间复杂度,所以就有了7/10这个魔数。

时间复杂度的计算

最坏情况下:T(N) = T(N/5)+ T(7N/10)+O(N) = O(N)。所以BFPRT算法在最差的情况下也能O(N)。

应用
  1. 在一个数组中找到最小的第k个数,我们可以使用堆,但是这样是O(NlogN)的。
  2. 最优解是BFPRT,如果找出了第k小的数,那么前k个就好找了(要求无重复),通过遍历。
示例代码

方法1:

int findLastK0(const vector<int> &vec, int kTh)
{
	if (vec.size() == 0 || kTh > vec.size() || kTh < 1)
	{
		throw exception("I can't do it");
	}
	vector<int> myCopy(vec);
	sort(myCopy.begin(),myCopy.end());

	return myCopy[kTh-1];
}

方法2

vector<int> partition(vector<int> &vec,int left, int right)
{
	int size = vec.size();
	//swap(vec[rand() % size], vec[size - 1]);
	int more = right;
	int less = left-1;
	while (left < more)
	{
		if (vec[left] < vec[right])
		{
			swap(vec[left++], vec[++less]);
		}
		else if (vec[left] > vec[right])
		{
			swap(vec[left], vec[--more]);
		}
		else
		{
			++left;
		}
	}
	swap(vec[more], vec[right]);
	return vector<int>{less + 1, more};
}

int select(vector<int> &vec, int begin, int end, int i)
{
	if (begin == end&& begin != i)
	{
		cout << "return value is invalid" << endl;
		return vec[begin];
	}
	vector<int> equalVec = partition(vec, begin, end);
	if (i < equalVec[0])
	{
		return select(vec, begin, equalVec[0] - 1, i);
	}
	else if (i > equalVec[0])
	{
		return select(vec, equalVec[1] + 1, end, i);
	}
	else
	{
		return vec[i];
	}
}

int findLastK1(const vector<int> &vec, int kTh)
{
	if (vec.size() == 0 || kTh > vec.size() || kTh < 1)
	{
		throw exception("I can't do it");
	}
	vector<int> myCopy(vec);
	return select(myCopy, 0, myCopy.size() - 1, kTh - 1);
}

方法3

vector<int> partitionbybfptr(vector<int> &vec, int left, int right, int targetValue)
{
	int more = right+1;
	int less = left - 1;
	while (left < more)
	{
		if (vec[left] < targetValue)
		{
			swap(vec[left++], vec[++less]);
		}
		else if (vec[left] > targetValue)
		{
			swap(vec[left], vec[--more]);
		}
		else
		{
			++left;
		}
	}
	return vector<int>{less + 1, more-1};
}

void insertSort(vector<int> &vec, int beginIndex, int endIndex)
{
	if (endIndex-beginIndex < 1)
	{
		return;
	}
	for (int i = beginIndex+1; i < endIndex+1; ++i)
	{
		for (int j = i - 1; j >= beginIndex; --j)
		{
			if (vec[j] > vec[j + 1]) // 保证稳定性
			{
				swap(vec[j], vec[j + 1]);
			}
		}
	}
}

int getMid(vector<int> &vec, int beginIndex, int endIndex)
{
	insertSort(vec, beginIndex, endIndex);
	int sum = endIndex + beginIndex;
	int mid = (sum / 2) + (sum % 2);
	return vec[mid];
}

int selectbybfptr(vector<int> &vec, int begin, int end, int i);

int medianOfMedians(vector<int> &vec, int begin, int end)
{
	int num = end - begin + 1;
	int offset = num % 5 == 0 ? 0 : 1;
	vector<int> midArr;
	midArr.resize(num/5+offset);
	for (int i = 0; i < num / 5 + offset;i++)
	{
		int beginI = begin + i*5;
		int endI = beginI + 4;
		midArr[i] = getMid(vec, beginI, min(endI,end));
	}
	return selectbybfptr(midArr, 0, midArr.size() - 1, midArr.size() / 2);
}

int selectbybfptr(vector<int> &vec, int begin, int end, int i)
{
	if (begin == end)
	{
		return vec[begin];
	}
	
	int targetValue = medianOfMedians(vec,begin,end);
	vector<int> equalVec = partitionbybfptr(vec, begin, end, targetValue);
	if (i < equalVec[0])
	{
		return selectbybfptr(vec, begin, equalVec[0] - 1, i);
	}
	else if (i > equalVec[1])
	{
		return selectbybfptr(vec, equalVec[1] + 1, end, i);
	}
	else
	{
		return vec[i];
	}
}
// BFPTR
int findLastK2(const vector<int> &vec, int kTh)
{
	if (vec.size() == 0 || kTh > vec.size() || kTh < 1)
	{
		throw exception("I can't do it");
	}
	vector<int> myCopy(vec);
	return selectbybfptr(myCopy, 0, myCopy.size() - 1, kTh - 1);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值