一、实验目标
- 实现最坏情况下线性时间选取第k大元素的算法和Randomized_select,并比较效率
二、设计思路
最坏情况下线性时间选取第k大元素的算法
-
将n个元素分成5个一组,最后一组有n mod 5个元素
-
用插入排序对每一组排序,并取其中值
- 本实验使用了STL里自带的插入排序 __insertion_sort
- 没有使用新的数组存储n/5个组的中值,而是将它们swap到了数组的最前方,再进行递归调用,从而实现了原址排序
-
递归调用此算法,寻找n/5个中位数的中位数x
-
用x作为pivot对数组进行划分
-
判断是否找到目标值,如果没有则继续递归搜索
if i < k then return k;
else if i < k then 找左区间的第i个最小元
else 找右区间第i-k个最小元
Randomized_select
-
使用分治思想,对输入数组进行随机划分
-
使用Randomized_partition对数组进行划分
- Randomized_partition和partititon的主要区别在于Randomized_partition在算法开始时会将随机选取元素作为A[r]
-
判断是否找到目标值
if i < k then return k;
else if i < k then 找左区间的第i个最小元
else 找右区间第i-k个最小元
计时部分
-
利用里的函数
-
将两种算法分别按照answer.txt里的数据规模测试10遍,再进行统计和分析
-
由于Randomized_select速度较快,为了精确性计时使用ms(毫秒)作为单位;
而最坏情况为线性时间的算法采用m(秒)作为单位
-
源码
clock_t start,end; for(int j = 0;j<10;j++){ std::cout << "Randomized_Select begins:" << std::endl; for(int i = 10;i <= 100000;i*=10){ start = clock(); vector<int>temp = arr; int ans = Randomized_Select(temp,0,temp.size()-1,i); cout<<i<<" "<<ans<<endl; end = clock(); double time = (double)(end - start)/CLOCKS_PER_SEC; cout<<"Total time of Randomized_select :"<<time*1000<<" ms"<<endl; } } for(int j = 0;j < 10;j++){ std::cout << "K_select begins:" << std::endl; for(int i = 10;i <= 100000;i*=10){ start = clock(); vector<int>temp = arr; int ans = k_select(temp,0,temp.size()-1,i); cout<<i<<" "<<ans<<endl; end = clock(); double time1 = (double)(end - start)/CLOCKS_PER_SEC; cout<<"Total time of k_select :"<<time1<<" s"<<endl; } }
总结
-
显然,就期望时间而言,randomized_select算法比最坏情况是线性时间寻找第k大元素的算法的时间复杂度要好很多,它的常数项要小很多
对比发现,randomized_select比另一种算法少了对于每组元素用插入排序的过程,也少了递归寻找中位数的中位数的过程,因此其O(n)的系数要小很多
randomized select
#include<iostream>
#include<random>
#include<vector>
using namespace std;
int Random(int p,int r){
uniform_int_distribution<unsigned> u(p,r);
default_random_engine e;
return u(e);
}
int Randomized_Partition(vector<int>&arr,int p,int r){
swap(arr[Random(p,r)], arr[r]);
int x = arr[r];
int i = p - 1;
for (int j = p; j < r; ++j){
if (arr[j] < x){
i++;
swap(arr[i], arr[j]);
}
}
swap(arr[i + 1], arr[r]);
return i + 1;
}
int Randomized_Select(vector<int>arr, int p, int r, int i){
if (p == r)
return arr[p];
int q = Randomized_Partition(arr, p, r);
int k = q - p + 1;
if (k == i)
return arr[q];
else if (k > i)
return Randomized_Select(arr, p, q - 1, i);
else
return Randomized_Select(arr, q + 1, r, i-k);
}
int main(int argc, const char** argv) {
vector<int>a = {0,1,2,3,4,5,6,7,8,9,10};
int k = Randomized_Select(a,0,10,3);
cout<<k<<endl;
return 0;
}
K select (referring to <Introduction to Algorithms >)
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
bool cmp(int a,int b){
return a<b;
}
int partition_(vector<int>&arr,int l,int r,int x){
int i;
for(i = l;i < r;i++)
{
if(arr[i] == x)break;
}
swap(arr[i],arr[r]);
i = l;
for(int j = l;j <= r-1;j++){
if(arr[j]<=x){
swap(arr[i],arr[j]);
i++;
}
}
swap(arr[i],arr[r]);
return i;
}
int k_select(vector<int>&arr,int l,int r,int k){
//边界条件
if(r-l<=140)
{
__insertion_sort(arr.begin(),arr.end(),cmp);
return arr[l+k-1];
}
//将数组分成5个一小组再分别进行排序
int t = l - 1;//把找到的中位数放到前面来,方便选中位数的中位数
for(int sta = l,end = sta + 4;end <= r;sta+=5,end+=5)
{
vector<int>::iterator st = next(arr.begin(),sta),ed = next(arr.begin(),end+1);
__insertion_sort(st,ed,cmp);
t++;
swap(arr[t],arr[sta+2]);
}
int pivot_id = (t-l+2)/2;
int pivot = k_select(arr,l,t,pivot_id);
//判断结果 递归调用
int m = partition_(arr,l,r,pivot);
int cur = m - l + 1;
if(k == cur)return arr[m];
if(k < cur)return k_select(arr,l,m-1,k);
if(k > cur)return k_select(arr,m+1,r,k-cur);
return 0;//为了不让编译器warning QwQ
}
int main(int argc, const char** argv) {
vector<int>a;
for(int i = 0;i < 10000;i++)a.push_back(i);
int k = k_select(a,0,a.size()-1,100);
cout<<k<<endl;
return 0;
}