算法分析与设计实验
1 带权中位数
1.问题描述
设有n个互不相同的元素x1,x2,x3,…,xn,每个元素xi带有一个权值wi,且i=1.若元素xk满足i<=1/2且i<=1/2,则称元素xk,为x1,x2,x3,…,xn的带权中位数。编写一个算法,最坏情况下用O(n)时间找出n个元素的带权中位数。
2.问题分析
该问题能够分为规模较小的子问题,并且子问题之间相互独立,可以采用分治与递归的思想求解。
3.算法设计
使用快速排序的划分方法,以权值为标准确定枢纽值,一次划分使枢纽值在中央元素,左边元素权值都比枢纽值小,右边都大。如果枢纽值左边权值之和大于0.5,则递归的在枢纽值左边的元素中寻找带权中位数,否则在枢纽值右边寻找;如果两边权值都小于0.5,则枢纽值就是所求带权中位数。
4.算法实现
#include <iostream>
#include <string>
#include <cmath>
#include <vector>
#include <algorithm>
using namespace std;
void WeightMedian(int length,vector<int>num, vector<double>weight,int index)
{
int numpivot=num[index];
double weightpivot=weight[index];
int left=index,right=length-1;
if(left==right)
{
printf("%d",num[left]);
return;
}
while(left<right)
{
while(left<right&&num[right]>numpivot)
{
right--;
}
num[left]=num[right];
weight[left]=weight[right];
while(left<right&&num[left]<=numpivot)
{
left++;
}
num[right]=num[left];
weight[right]=weight[left];
}
num[right]=numpivot;
weight[right]=weightpivot;
double weightsum1=0,weightsum2=0;
for(int i=0;i<left;i++) weightsum1+=weight[i];
for(int i=right+1;i<length;i++) weightsum2+=weight[i];
if(weightsum1<0.5&&weightsum2<0.5)
{
printf("%d", num[left]);
return;
}
else if(weightsum1<0.5&&weightsum2>0.5) WeightMedian(length,num,weight,left+1);
else WeightMedian(length,num,weight,0);
}
int main() {
vector<int> num;
vector<double> weight;
int i,input,length;
double input2;
cin>>length;
for(i=0;i<length;i++)
{
cin>>input;
num.push_back(input);
}
for(i=0;i<length;i++)
{
cin>>input2;
weight.push_back(input2);
}
WeightMedian(length,num,weight,0);
return 0;
}
5 运行结果
2 钢条切割
1.问题描述
设有一个长度为L的钢条,在钢条上标有n个位置点(p1,p2,…pn).现在需要按钢条上标注的位置将钢条切割成n+1段,假定每次切割所需要的代价与所切割的钢条长度成正比。编写一个算法,使切割的总代价最小。
2.问题分析
原钢条切割问题可以分为更小规模的钢条切割问题,各问题之间相互不独立。要使得原钢条切割代价最小,那么所分得的小规模的钢条切割代价也应为最小。可以使用动态规划的思想,使用备忘录储存每次计算小规模问题得到的解,自底向上解决原问题。
3.算法设计
利用二维数组mincost[][]储存任意两个切割点之间的最小切割代价。利用公式
递归地求出最终所求最小切割代价mincost[0][n+1]。
4.算法实现
#include <iostream>
#include <string>
#include <cmath>
#include <vector>
#include<algorithm>
using namespace std;
void MinCost(int L,int n,int *p)
{
int **mincost=new int*[n+2];
int i,j,k,step;
int costtemp;
for(i=0;i<=n+1;i++)
{
mincost[i]=new int[n+2]{};
}
sort(p,p+n+1);
for(i=0;i<=n;i++)
{
mincost[i][i+1]=0;
if(i<=n-1)
{
mincost[i][i+2]=p[i+2]-p[i];
}
}
for(step=3;step<=p[n+1];step++)
{
for(k=0;k+step<=n+1;k++)
{
mincost[k][k+step]=mincost[k][k+1]+mincost[k+1][k+step]+p[k+step]-p[k];
for(j=2;j<step;j++)
{
costtemp=mincost[k][k+j]+mincost[k+j][k+step]+p[k+step]-p[k];
if(costtemp<mincost[k][k+step])
{
mincost[k][k+step]=costtemp;
}
}
}
}
printf("%d",mincost[0][n+1]);
for(i=0;i<=n;i++)
{
delete[] mincost[i];
}
delete[] mincost;
}
int main() {
int L, n;
cin>>L>>n;
int *p;
p = new int[n+2];
p[0] = 0;
p[n+1] = L;
for(int i=1;i<n+1;i++){
cin>>p[i];
}
MinCost(L,n,p);
return 0;
}
5.运行结果
3 d森林带权树最小顶点集合
1.问题描述
设T为一带权树,树中的每个边的权都为整数。又设S为T的一个顶点的子集,从T中删除S中的所有结点,则得到一个森林,记为T/S。如果T/S 中所有树从根到叶子节点的路径长度都不超过d,则称T/S是一个d森林。设计一个算法求T的最小顶点集合S,使T/S为一个d森林。
2.问题分析
要求所有树从根到叶子节点的路径长度都不超过d,那么可以考虑从叶子节点向上回溯,当回溯到某一结点到叶子结点的距离大于d时删除这个结点来得到d森林。如果要使得删除的顶点数量最少,那么每个子森林中删除的顶点都应该最少。所以,d森林问题满足最优解子结构性质。
可以证明该问题具有贪心选择性质:从叶结点回溯,当某一结点到叶子结点的路径长度大于d时,如果不删除此节点,而(只得)删除该节点的子节点,假设从另外一个结点沿另一条回溯路径回溯到该节点且路径长度大于d时还需要再删除一个子结点,即可能删除2个及以上结点,而如果直接删除这个结点,则只需要删除一个结点。
所以可以使用贪心算法的思想,每一次删除的结点都是从叶子结点回溯至路径长度大于d时的结点,来求得一个最优解。
3.算法设计
总体使用dTree类来实现算法。使用顺序存储法来存储初始的树。输入树的结点个数以及从下标0开始每个结点的出度以及子节点。从最后一个结点(必然是叶子结点)出发至最后一个个非根结点,向上回溯至遇到路径长度大于d或者已经被删除的结点,如果遇到使得到叶子结点路径长度大于d的结点则删除之,并进行下一个(倒数第二个)结点的回溯,删除结点或回溯结点后,更新与之相关结点的出度,查看是否有新结点产生;如果回溯起点的结点已经被删除则略过。
但整体从最后一个叶子节点向根结点回溯时,可以保证当前情况下回溯起点的结点必是叶子结点,所以只需要写for循环即可,代码中无需更新、查看有无新结点产生。
使用快速排序的划分方法,以权值为标准确定枢纽值,一次划分使枢纽值在中央元素,左边元素权值都比枢纽值小,右边都大。如果枢纽值左边权值之和大于0.5,则递归的在枢纽值左边的元素中寻找带权中位数,否则在枢纽值右边寻找;如果两边权值都小于0.5,则枢纽值就是所求带权中位数。
4.算法实现
#include <iostream>
#include <fstream>
#include <string>
#include <cmath>
#include <vector>
#include <algorithm>
using namespace std;
class dTree{
public:
dTree(int n, int d)
{
nodeNum=n;
maxDist=d;
parent=new int[n]{-1,};
outDegree=new int[n]();
leaf=new int[n]();
inDist=new int[n]();
disToLeaf=new int[n]();
isDelete=new int[n]();
leafCount=0;
deleteCount=0;
int i=0;
int j,k,count=0;
while(i<nodeNum)
{
cin>>outDegree[i];
if(outDegree[i]==0)
{
leaf[count++]=i;
leafCount++;
}
else
{
for (j = 1; j <= outDegree[i]; j++)
{
cin >> k;
parent[k] = i;
cin>>inDist[k];
}
}
i++;
}
//此处为构造函数,初始化构建树。
}
void solution()
{
int i,j;
for(i=nodeNum-1;i>=1;i--)
{
j=parent[i];
if(isDelete[i]||isDelete[j]) continue;
if(disToLeaf[j]<disToLeaf[i]+inDist[i]) disToLeaf[j]=disToLeaf[i]+inDist[i];
if(disToLeaf[j]>maxDist)
{
isDelete[j]=1;
deleteCount++;
}
}
cout<<deleteCount;
}
~dTree()
{
delete[] parent;
delete[] outDegree;
delete[] inDist;
delete[] disToLeaf;
delete[] isDelete;
delete[] leaf;
}
private:
int nodeNum;
int maxDist;
int *parent;
int *outDegree;
int *inDist;
int *disToLeaf;
int *isDelete;
int deleteCount;
int *leaf;
int leafCount;
};
int main() {
int n, d; //n为顶点个数,d为路径长度
cin >> n >> d;
dTree dt(n, d); //构建与初始化树
dt.solution(); //通过solution函数输出结果
return 0;
}
5.运行结果
4 寻找互斥集合
1.问题描述
给定 1 个 1000 行×20 列的 01 矩阵,对于该矩阵的任意1列,其中值为1的元素的数量不超过%10.设有两个非空集合A和B,每个集合由矩阵的若干列组成.集合 A和B 互斥是指对于矩阵的任意一行,同时满足下列2个条件:
(1)若A中有一个或多个元素在这一行上的值是1,则B中的元素在这一行全部是0;
(2)若B中有一个或多个元素在这一行上的值是1,则A中的元素在这一行全部是0.
请设计一个算法,找出一对互斥集合A和B,使得A和B包含的列的总数最大.
2.问题分析
可以发现,对于任意一个情况下的A,如果对应存在互斥的B,那么包含列数最多的B是唯一的。而A的状态对应20列的矩阵可以用解空间全部列出,只需找出对应解空间中最优的解即可。所以考虑使用回溯法解决此问题。
3.算法设计
使用二位数组存储01矩阵,使用队列来存储当前A/B中的列。对A的状态进行回溯时,使用函数JudgeRest()作剪枝函数:计算当前A的状态下对应B中列数最大值,加上现在A中列数和A接下来可能增加的最大列数,如果和小于当前最优值BestNum,则剪枝,每回溯到一个叶子结点,找到对应情况下最大的B(通过Ajudge算法,对不属于A中的所有列,如果它与A中所有列互斥则为true,加入B中),并判断是否为最优解。如果遇到BestNum相同,为保证解的唯一性,每次保证:1.AB列数差绝对值最小;2.A的列数大于B的列数;3.使A B的首元素在列数和相同的情况下最小
使用output数组记录最佳的解(大小为20,对应列编号,值为1则在A中,值为2则在B中,为0则不在A、B中),最后通过output数组输出最佳的解。
4.算法实现
#include <iostream>
#include <cmath>
using namespace std;
class Matrix
{
private:
int Matrix[1000][20]={};
int *q=new int[22]();
int *rear=q;
int *front=q;
int NumofAc=0;
int NumofBc=0;
int Row=1000;
int Colomn=20;
int BestAbs=999;
int BestNum=0;
int output[20]={};
int AbsTemp=0;
int NumTemp=0;
int *temp;
int Isfind=0;
int ABesthead=0;
int BBesthead=0;
bool MutualEx[20][20]={}; //存储第i列与第j列是否互斥
public:
void Init()
{
for(int i = 0;i<Row;i++)
{
for(int j =0;j<Colomn;j++)
{
cin >> Matrix[i][j];
}
}
for(int i=0;i<Colomn;i++)
{
for(int j=i+1;j<Colomn;j++)
{
if(judge(i,j)) MutualEx[i][j]=MutualEx[j][i]=true;
else MutualEx[i][j]=MutualEx[j][i]=false;
}
}
}
void Output()
{
if(Isfind==0)
{
cout<<"\n"<<endl;
}
else
{
for(int i=0;i<Colomn;i++)
{
if(output[i]==1)
{
cout << i<<" ";
}
}
cout << "\n";
for(int i=0;i<Colomn;i++)
{
if(output[i]==2)
{
cout <<i << " ";
}
}
}
}
void Dequeue()
{
*(rear-1)=0;
rear--;
}
void Inqueue(int i)
{
*rear=i;
rear++;
}
void reset() //重置列数和等元素
{
NumofBc=0;
Dequeue(); //队列尾标志-1出队
}
bool judge(int c1,int c2) //判断两列是否互斥,true为互斥
{
for(int i=0;i<Row;i++)
{
if(Matrix[i][c1]+Matrix[i][c2]==2) return false;
}
return true;
}
bool Ajudge(int c) //判断第C列是否与当前A互斥
{
for(int i=0;i<Colomn;i++)
{
if(Isinqueue(i))
{
if(!MutualEx[i][c]) return false;
}
}
return true;
}
bool judgeRest(int i) //判断当前情况下,最好情况时总和是否可以超过Bestnum,以剪枝用
{
int Bcount=0;
for(int k=0;k<Colomn;k++)
{
if(!Isinqueue(k)&&Ajudge(k)) Bcount++;
}
if(Bcount+NumofAc+Colomn-i<BestNum) return false;
return true;
}
bool Isinqueue(int i) //判断第i列是否在队列中
{
int *p=front;
while(p!=rear)
{
if(*p==i) return true;
p++;
}
return false;
}
void backtrack(int i)
{
if(i==Colomn)
{
Inqueue(-1);
for(int k= 0;k< 20;k++)
{
if(!Isinqueue(k)) //当前列没在A中并且与A互斥,则加入B中
{
if(Ajudge(k))
{
Inqueue(k);
NumofBc++;
}
}
}
if(NumofBc!=0) //B中至少有一列,则找到一组解
{
Inqueue(-1);
AbsTemp = abs(NumofAc - NumofBc);
NumTemp = NumofAc + NumofBc;
if (NumTemp > BestNum)
{
if (NumofAc > NumofBc)
{
Isfind=1;
BestAbs = AbsTemp;
BestNum = NumTemp;
ABesthead=*front;
int *p2=front;
while(*p2!=-1) p2++;
p2++;
BBesthead=*p2;
for(int n=0;n<Colomn;n++) output[n]=0;
temp = front;
while (*temp!=-1)
{
output[*temp++]=1;
}
temp++;
while (*temp!=-1)
{
output[*temp++]=2;
}
}
}
else if(NumTemp==BestNum&&AbsTemp<=BestAbs)
{
int AHeadtemp=*front;
int Bheadtemp;
int *p2=front;
while(*p2!=-1) p2++;
p2++;
Bheadtemp=*p2;
if(AHeadtemp<=ABesthead&&Bheadtemp<=BBesthead)
{
BestAbs = AbsTemp;
BestNum = NumTemp;
for (int n = 0; n < Colomn; n++) output[n] = 0;
temp = front;
while (*temp != -1)
{
output[*temp++] = 1;
}
temp++;
while (*temp != -1)
{
output[*temp++] = 2;
}
}
}
for(int k=1;k<=NumofBc+1;k++) Dequeue(); //当前B中列全部出队列,进行下一次寻找
}
reset();
}
else
{
if(judgeRest(i))
{
NumofAc++;
Inqueue(i);
backtrack(i + 1);
NumofAc--;
Dequeue();
backtrack(i + 1);
}
}
}
};
int main()
{
class Matrix matrix;
matrix.Init();
matrix.backtrack(0);
matrix.Output();
return 0;
}