一、实验名称
随机算法实验—舍伍德法求解线性时间元素选择问题:
问题描述:
元素选择问题的一般提法是:给定线性序集中n个元素和一个整数k (1≤k≤n), 要求找出这n个元素中第k小的元素,即如果将这n个元素依其线性序排列时,排在第k个位置的元素即为要找的元素。当k=1时,就是要找的最小元素:当k=n时, 就是要找最大元素;当k=(n+1)/2 时,称为找中位数。
二、实验目的
通过上机实验,要求掌握Sherwood 型线性时间选择算法的问题描述、算法设计思想、程序设计。
三、实验原理
利用 Sherwood 型随机化思路求解线性时间选择算法,并计算出程序运行所需要的时间。
四、实验步骤
问题分析
对于选择问题而言,用拟中位数作为划分基准可以保证在最坏的情况下用线性时间完成选择。如果只简单地用待划分数组的第一个元素作为划分基准,则算法的平均性能较好,而在最坏的情况下需要O(n^2)计算时间。
舍伍德选择算法则随机地选择一个数组元素作为划分基准,这样既保证算法的线性时间平均性能,又避免了计算拟中位数的麻烦。
算法思想
类似于快排的一种思想。
- 随机选择一个基准值pivot,将基准值与范围左侧元素交换位置。
- 以基准值为轴进行划分,先从右往左找到一个位置j的元素小于pivot,然后从左往右找到一个位置i的元素大于pivot,交换两个元素的位置。
- 当i==j时停止交换,这时j的左侧元素小于等于pivot,右侧元素大于等于pivot。
- 因为j处的元素肯定小于等于pivot,将L与j处元素交换位置,j位置的元素就是这个序列里面第 j - L+1小的元素。
- 如果j - L +1==k,则问题解决
- 否则判断k与j - L +1的相对大小,当j - L +1< k时继续对右侧的序列重复以上过程,否则对左侧序列重复以上过程,直到找到满足条件的j - L +1
五、关键代码
1.舍伍德选择算法
//内联交换函数
inline void Swap(int& a, int& b){
int temp = a;
a = b;
b = temp;
}
int select(vector<int> a, int l, int r, int k){
while (true){
if (l >= r){
return a[l];
}
//随机选择划分基准
int i = l, j = l + rand() % (r - l + 1);
Swap(a[i], a[j]);
j = r + 1;
int pivot = a[l];
//以划分基准为轴做元素交换
while (true){
while (i<r&&a[++i] < pivot);
while (j>l&&a[--j] > pivot);
if (i >= j){
break;
}
Swap(a[i], a[j]);
}
//如果最后基准元素在第k个元素的位置,则找到了第k小的元素
if (j - l + 1 == k){
return pivot;
}
//a[j]必然小于pivot,做最后一次交换,满足左侧比pivot小,右侧比pivot大
a[l] = a[j];
a[j] = pivot;
//对子数组重复划分过程
if (j - l + 1 < k){
k = k - (j - l + 1 );//基准元素右侧,求出相对位置
l = j + 1;
}else{//基准元素左侧
r = j - 1;
}
}
}
int Select(vector<int> a,int n,int k){
if(k<1||k>n){
printf("Index out of bounds\n");
exit(0);
}
return select(a,0,n-1,k);
}
2.自己创建测试数据
//生成规模为n的随机数
cout<<"请输入数据规模n:"<<endl;
int n;
cin>>n;
ofstream out("input1.txt");
out<<n<<'\n';
srand((unsigned)time(NULL));
int a,b;
a=1,b=n;
//k
out<<(rand() % (b-a+1))+ a<<endl;
//随机生成数组元素
a=0,b=100000;
for(int i=0;i<n;i++){
out<<(rand() % (b-a+1))+ a<<' ';
}
out.close();
六、测试结果
时间复杂度:O(n)
划分基准是随机的。可以证明,当用算法Select对含有n个元素的数组进行划分时,划分出的低区子数组中含有一个元素的概率为2/n,含有i个元素的概率为1/n (i=2, 3,.,. n-1)。
设T(n)是算法Select作用于一个含有n个元素的输入数组上所需的期望时间的上界,且T(n)是单调递增的。在最坏情况下,第k小元素总是被划分在较大的子数组中。得到关于T(n)的递归式:
n从100-20000,每次递增100的测试结果:
自定义输入n的数值的测试结果:
七、实验心得
通过这次实验,我了解熟悉了利用 Sherwood 型随机化思路求解线性时间选择算法,。对于自己实现的案例,感觉存在误差,画出来的图不符合直觉,也可能是求解运行时间的程序有问题。
八、完整代码
#include <iostream>
#include <time.h>
#include <stdlib.h>
#include <fstream>
#include <windows.h>
#include <vector>
#include <algorithm>
using namespace std;
ifstream in("input.txt");
ofstream out("output.txt");
//内联交换函数
inline void Swap(int& a, int& b){
int temp = a;
a = b;
b = temp;
}
int select(vector<int> a, int l, int r, int k){
while (true){
if (l >= r){
return a[l];
}
//随机选择划分基准
int i = l, j = l + rand() % (r - l + 1);
Swap(a[i], a[j]);
j = r + 1;
int pivot = a[l];
//以划分基准为轴做元素交换
while (true){
while (i<r&&a[++i] < pivot);
while (j>l&&a[--j] > pivot);
if (i >= j){
break;
}
Swap(a[i], a[j]);
}
//如果最后基准元素在第k个元素的位置,则找到了第k小的元素
if (j - l + 1 == k){
return pivot;
}
//a[j]必然小于pivot,做最后一次交换,满足左侧比pivot小,右侧比pivot大
a[l] = a[j];
a[j] = pivot;
//对子数组重复划分过程
if (j - l + 1 < k){
k = k - (j - l + 1 );//基准元素右侧,求出相对位置
l = j + 1;
}else{//基准元素左侧
r = j - 1;
}
}
}
int Select(vector<int> a,int n,int k){
if(k<1||k>n){
printf("Index out of bounds\n");
exit(0);
}
return select(a,0,n-1,k);
}
int main(){
ofstream out1("output1.txt");
ofstream out2("output2.txt");
// int n=50000000;
while(1){
//生成规模为n的随机数
cout<<"请输入数据规模n:"<<endl;
int n;
cin>>n;
ofstream out("input1.txt");
out<<n<<'\n';
srand((unsigned)time(NULL));
int a,b;
a=1,b=n;
//k
out<<(rand() % (b-a+1))+ a<<endl;
//随机生成数组元素
a=0,b=100000;
for(int i=0;i<n;i++){
out<<(rand() % (b-a+1))+ a<<' ';
}
out.close();
int i,maxi,mini;
LARGE_INTEGER nFreq,nBegin,nEnd;
double time=0;
ifstream in("input1.txt");
int k;
in>>n>>k;
cout<<"n= "<<n<<" k= "<<k<<endl;
vector<int> arr(n);
for(int i=0;i<n;i++){
in>>arr[i];
}
int cishu=1;
int copy_cishu=cishu;
long long result;
while(cishu--){
QueryPerformanceFrequency(&nFreq);
QueryPerformanceCounter(&nBegin);
result=Select(arr,n,k);;
QueryPerformanceCounter(&nEnd);
time+=(double)(nEnd.QuadPart-nBegin.QuadPart)/(double)nFreq.QuadPart;
}
time=time/copy_cishu;
cout<<"算法-结果:"<<result<<"\t 查询时间:"<<time<<endl;
out1<<n<<' '<<time<<endl;
QueryPerformanceFrequency(&nFreq);
QueryPerformanceCounter(&nBegin);
sort(arr.begin(),arr.end());
result=arr[k-1];
QueryPerformanceCounter(&nEnd);
time=(double)(nEnd.QuadPart-nBegin.QuadPart)/(double)nFreq.QuadPart;
cout<<"排序-结果:"<<result<<"\t 查询时间:"<<time<<endl<<endl;
out2<<n<<' '<<time<<endl;
}
out1.close();
out2.close();
}
九、绘图代码
import matplotlib.pyplot as plt
def read_file(filename):
data = []
with open(filename, 'r') as file:
for line in file:
x, y = map(float, line.split())
data.append((x, y))
return data
# 读取文件的数据
# file1_data = read_file('F:\\3-CourseMaterials\\3-1\\3-算法设计与分析\实验\lab4\\1-code\\1-优先队列式分支限界法求解0-1背包问题\\output1.txt')
# file2_data = read_file('F:\\3-CourseMaterials\\3-1\\3-算法设计与分析\实验\lab4\\1-code\\1-优先队列式分支限界法求解0-1背包问题\\output2.txt')
file3_data = read_file('F:\\3-CourseMaterials\\3-1\\3-算法设计与分析\实验\lab4\\1-code\\4-舍伍德法求解线性时间元素选择问题\\output1.txt')
file4_data = read_file('F:\\3-CourseMaterials\\3-1\\3-算法设计与分析\实验\lab4\\1-code\\4-舍伍德法求解线性时间元素选择问题\\output2.txt')
# 分别提取 x 和 y 的值
# file1_x, file1_y = zip(*file1_data)
# file2_x, file2_y = zip(*file2_data)
file3_x, file3_y = zip(*file3_data)
file4_x, file4_y = zip(*file4_data)
# print(file1_x)
# print(file1_y)
# print(file2_x)
# print(file2_y)
print(file3_x)
print(file3_y)
print(file4_y)
# 绘制图形,指定不同颜色
# plt.plot(file1_x, file1_y, label='Solution 1', color='red')
# plt.plot(file2_x, file2_y, label='Solution 2', color='green')
plt.plot(file3_x, file3_y, label='Solution 1', color='red')
plt.plot(file4_x, file4_y, label='Solution 2', color='green')
# 添加图例、标签等
plt.legend()
plt.title('Data from Three Files')
plt.xlabel('X-axis')
plt.ylabel('Y-axis')
# 显示图形
plt.show()