实验1
q1.请实现如下描述的快速选择算法:函数原型 void select(int a[],int beg,int end,int k)//在数组a的beg...end范围内选择最小的k个的元素并将其存于数组a的 beg....beg+k-1位置。
要求:
(1)采用分治算法:(递归程序)
区间beg...end内的选择k小问题定义为:
1.划分算法
2.根据划分位置p,决定三者之一(p-beg为左边元素个数):
a.如果p-beg=k或p-beg+1=k,什么也不做;
b.如果p-beg>k,求select(a,beg,p-1,k)
c.如果p-beg+1<k,求select(a,p+1,end,k-l-p+beg)
3.退回上级
(2)划分算法要求采用如下形式:int simplepart(int a[],int beg, int end)//返回划分后枢轴所在位置
1.i设为beg,j设为beg+1;
2.是否j<=end,如果不是,跳转到8
3.是否a[j]<a[beg],如果不是跳转到6
4.i自增
5.交换a[i]和a[j]
6.j++
7.jmp 2
8.交换a[i]和a[beg]
9.返回i,程序结束
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define ll long klong
#define mmset(a,b) memset(a,b,sizeof(a))
using namespace std;
int simplepart(int a[],int beg, int end)
{
int i = beg, j = beg + 1;
while(j <= end)
{
if(a[j] < a[beg])
{
i++;
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
j++;
}
int temp = a[j];
a[j] = a[beg];
a[beg] = temp;
return i;
}
void select (int a[], int beg, int end, int k)
{
int p = simplepart(a,beg,end);
if(p - beg > k )
{
select(a,beg,p-1,k);
}
else if(p - beg + 1 < k)
{
select(a,p+1,end,k-1-p+beg);
}
}
int main()
{
int a[] = {0,4,6,48,4,6,74,231,5,23,1,5,641,85,2,4,6,4,85,4,5,4,78,45,12};//25个数
select(a,0,24,10);
for(int i = 1; i < 25; i++)
{
printf("%d ",a[i]);
}
return 0;
}
(3)递归程序需要处理各种特殊情况,例如beg>end(元素个数为0),区间中元素个数不大于k,
(4)需要生成一个稍大的数组测试,并且在程序中增加一个统计元素比较次数的功能。
------------------------------------------------------------
q2.请编写算法求解如下问题:使用分治法在一个整型数组中去重。函数原型:int unique(int a[],int beg,end) 返回值为剩余元素个数
算法要求:
(1)采用分治法(递归程序);
区间beg...end内的去重(并排序)定义为
1.区间beg...mid的去重(并排序)问题
2.区间mid+1...end的去重(并排序)问题
3.有序表合并(可采用去一次重复版本:即在标准合并算法的基础上,遇到两个相同元素,只复制一个,但指针都后移),空间复杂度O(n),时间复杂度O(n)
(2)需要在主函数中生成一个规模和重复度均较大的数组测试。
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define ll long long
#define mmset(a,b) memset(a,b,sizeof(a))
using namespace std;
int unique(int a[],int beg,int end)
{
if(beg >= end)
{
return 1;
}
int mid = (beg + end) >> 1;
int len1 = unique(a,beg,mid);
int len2 = unique(a,mid + 1, end);
int len = len1 + len2;
int diff = end - beg + 1 - len;
int i = beg, j = mid + 1;
int nums = 0;
int data[1000];
for(int i = beg; i <= end; i++)
{
data[i] = a[i];
}
for(int index = beg - diff; index + nums <= end; index++)
{
if(i >= mid + 1)
{
a[index] = data[j];
j++;
}
else if(j > end)
{
a[index] = data[i];
i++;
}
else if(data[i] < data[j])
{
a[index] = data[i];
i++;
}
else if(data[i] > data[j])
{
a[index] = data[j];
j++;
}
else
{
a[index] = a[i];
i++;
j++;
nums++;
}
}
return end - beg + 1 - nums;
}
int main()
{
int a[] = {3, 1, 2, 2, 5, 4};
int len = unique(a,0,5);
for(int i = 0; i <= 5; i++)
{
printf("%d ",a[i]);
}
printf("\n");
return 0;
}
1.请参照附件“编程珠玑——最大子段和”中8.4介绍的算法,实现最大子段和问题(最短长度为0)的动态规划解法,要求:
(1)定义子问题:d(i)“截至于a[i]的最大子段和”
d(0)=max(0,a(0))
i>0时,d(i)=max(0,d(i-1)+a(i))
(2)只用一个单重循环完成以上算法(求最优值和最优解合到一个循环中),时间复杂度O(n),空间复杂度O(n)
(3)对某个实例运行算法验证正确性
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define ll long long
#define mmset(a,b) memset(a,b,sizeof(a))
using namespace std;
int main()
{
int data[] = {0,1,5,4,-4,8,4,90,4,-45,-2};
int dp[10];
int n = 10;
dp[1] = data[1] > 0 ? data[1] : 0;
for(int i = 2; i <= n; i++)
{
dp[i] = max(dp[i-1] + data[i],dp[i-1]);
}
printf("%d\n",dp[n]) ;
return 0;
}
2.请实现0-1背包问题:
有n个物品,存于数组t中,物品i(i=0...n-1)的重量为t[i].w,其价值为t[i].v。
现有一个容量为C的背包,请求出放入背包中的物品的组合,使其总价值最大。
要求:
(1)使用如下定义的动态规划解法:
定义子问题d(i,j)(i=0...n,j=0...C): 只有物品i,i+1...n-1,剩余背包容量为j的背包问题的最优值
d(n,j)=0(没有任何物品)
当i<n时:
如果t[i].w<j,d(i,j)=d(i+1,j);
否则d(i,j)=max(d(i+1,j),d(i+1,j-t[i].w)+t[i].v)
(2)将d(i,j)存于二维数组中,使用循环求解,可参见附件中的代码,但是该代码并不符合要求(1)
(3)写程序逆推求出最优解,
(4)对某个实例运行算法验证正确性
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int INF = 2005;
int d[INF][INF]; //定义状态d[i][j]为前i件物品放入容量为j的背包时的最大价值;
int W[INF],C[INF];
int main()
{
int n, v;
scanf("%d%d",&n,&v);
for(int i = 1; i <= n; i++)
{
scanf("%d",&W[i]);
}
for(int i = 1; i <= n; i++)
{
scanf("%d",&C[i]);
}
memset(d,0,sizeof(d));
for(int i = 1; i <= n; i++)
{
for(int j = 0; j <= v; j++)
{
if(j >= C[i])
{
d[i][j] = max(d[i-1][j],d[i-1][j - C[i]] + W[i]);
}
else
{
d[i][j] = d[i-1][j];
}
}
}
printf("%d\n",d[n][v]);
return 0;
}
请参照附件中“活动安排问题”的描述,编写算法实现。
有n个任务需要n个人完成,每人恰好完成一个任务,这些任务同时开始。
第i个人(i=0...n-1)完成第j个任务的时间是t(i,j),第i个人分配的任务记为s(i),则完成时间为t(i,s(i)),则全部任务结束时间为 et=max(t,s(i)),i=0...n-1。
选择合适的任务分配方案,使et在所有方案中最小。
要求:
1.随机生成t数组
2.使用回溯法求解这一问题,且把问题抽象为排列树上的搜索问题(按照i升序,依次试探s(i));
3.采用如下剪枝方案:当已经试探s(0)~s(i-1)时,判定该分支是否需要访问
已知 : curmax=max(t(j,s(j))) j=0~i-1,即 前面那些选择 的最大时间,一般通过递归参数传入
已知:对所有 k=0~n-1: mint(k)=t(k,j),j=0~n-1,即 mint是 一维数组,是t按行求最小值得出的。可预先计算得到。它的作用是给出后面某一步选择的时间的下限。
已知:对所有 k=0~n-1: maxmint(k)=max(mint(j)),j=k~n-1,即maxmint是mint从k到n-1部分的最大值。可预先计算得到。
已知:当前最优解的完成时间 curmin
剪枝条件:若 max(curmax,maxmin(i))>=curmin 则该分支不必访问。
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define ll long long
#define mmset(a,b) memset(a,b,sizeof(a))
using namespace std;
const int N = 105;
int s[N],mint[N],maxmint[N];
int t[N][N] = {
{},
{0,1,4,23,1,7},
{0,4,8,7,1,15},
{0,7,1,1,4,4},
{0,3,4,4,8,24},
{0,45,12,7,14,5}
};
int mark[N],n = 5;
int dfs(int i, int curmax)
{
if(i == n + 1)
{
return curmax;
}
else
{
int max1 = 0x3f3f3f3f;
for(int j = 1; j <= n; j++)if(mark[j] == 0)
{
if(max(curmax,maxmint[i]) >= t[i][j])
{
mark[j] = 1;
int temp = dfs(i + 1, max(curmax,t[i][j]));
max1 = min(max1, temp);
mark[j] = 0;
}
}
return max1;
}
}
int main()
{
for(int i = 1; i <= n; i++)
{
int temp = 0x3f3f3f3f;
for(int j = 1; j <= n; j++)
{
temp = min(t[i][j],temp);
}
mint[i] = temp;
maxmint[n] = mint[n];
for(int i = n - 1; i >= 1; i--)
{
maxmint[i] = max(mint[i],maxmint[i + 1]);
}
}
int res = dfs(1,0);
printf("%d\n",res);
return 0;
}
可自行搜索“最大值最小化问题”、“旅行商问题”、“任务分配问题”等,帮助理解其思路。