问题引入
希望你可以在一个无序数组中找到第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一样的。
步骤
- 将所有的数据按照5个一组,不满5个的另作一组,进行分组。O(1)。
- 对每个小组内5个元素进行排序,每个小组排序的时间复杂度是O(1),有N/5个组,所以时间复杂度是O(N)的。
- 拿每个数组的中位数出来作为一个新数组new_arr。长度是N/5的。(O(N))
- 递归调用BFPRT算法,参数为new_arr(N/5),要查找的数为中位数,正好是数组一半的第k小的数。然后用返回值作为划分值。至少有3/10的数比他大,最多有7/10的数比你多。。同理,至少有3/10的数比他小。T(N/5)
- 用返回值进行partition。O(N)
- 如果命中直接返回,如果没有命中,走左边还是走右边。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)。
应用
- 在一个数组中找到最小的第k个数,我们可以使用堆,但是这样是O(NlogN)的。
- 最优解是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);
}