elseleft=mid+1;
}
i=right;j=left;//或i=left-1;j=left
return0;
}
5.问题:
改写二分搜索算法,返回小于x的最大元素位置i和大于x的最小元素位置j。
当找到与x同的元素时,i和j相同,均为x在数组中的位置
第三章动态规划
3-5编辑距离问题
1.问题描述:
设A和B是两个字符串。
要用最少的字符操作将字符串A转换为字符串B。
这里所说的字符操作包括:
1)删除一个字符;
2)插入一个字符;
3)将一个字符改为另一个字符。
将字符串A变换为字符串B所用的最少字符操作数称谓字符串A到B的编辑距离,记为d(A,B)。
试设计一个有效算法,对任给的2个字符串A和B,计算出它们的编辑距离d(A,B)。
2.分析与证明:
a.开一个二维数组d[i][j]来记录a0-ai与b0-bj之间的编辑距离,要递推时,需要考虑对其中一个字符串的删除操作、插入操作和替换操作分别花费的开销,从中找出一个最小的开销即为所求
b.具体算法:
c.首先给定第一行和第一列,然后,每个值d[i,j]这样计算:
d[i][j]=min(d[i-1][j]+1,d[i][j-1]+1,d[i-1][j-1]+(s1[i]==s2[j]?
0:
1));
d.最后一行,最后一列的那个值就是最小编辑距离
3.代码实现:
#include
#include
#include
#include
inti,j;
char*s1=newchar[80];
char*s2=newchar[80];
intmd[81][81];
intmin(inta,intb,intc);
inteditDistance(intlen1,intlen2);//最短编辑距离
voidmain()
{
fstreaminDst;
inDst.open("E:
\\input.txt",ios:
:
in,0);
inDst>>s1;
inDst>>s2;
inDst.close();
fstreamoutDst;
outDst.open("E:
\\output.txt",ios:
:
app,0);
outDst<outDst.close();
}
intmin(inta,intb,intc)
{
inttemp=(a
a:
b;
return(temptemp:
c;
}
//最短编辑距离
inteditDistance(intlen1,intlen2)
{
//当某一个字符串为空时,最短编辑距离是另外一个字符串的长度。
for(i=0;i<=len1;i++)
{
md[i][0]=i;
}
for(j=0;j<=len2;j++)
{
md[0][j]=j;
}
for(i=1;i<=len1;i++)
{
for(j=1;j<=len2;j++)
{
intcost=(s1[i-1]==s2[j-1])?
0:
1;
intdelet=md[i-1][j]+1;
intinsert=md[i][j-1]+1;
intsubstit=md[i-1][j-1]+cost;
md[i][j]=min(delet,insert,substit);
}
}
returnmd[len1][len2];
}
4.测试数据:
Input.txt
fxpimu
xwrs
运行结果:
Output.txt
5
5.复杂度分析:
二重for循环复杂度为O(n^2)
第四章贪心算法
4-9汽车加油问题
1.问题描述:
一辆汽车加满油后可行驶nkm。
旅途中有若干加油站。
设计一个有效算法,指出应在哪些加油站停靠加油,使沿途加油次数最少。
2.证明与分析:
1 解题思路:
先判断是否有解,再对数组从1到k+1计算尽量往前走,直至不能走就+1,计算出最大
2 算法策略:
贪心算法
3 基本变量描述:
data[]和数组a[]都是用来保存k+1个距离值,sum和total均用来记录最少加油次数,total=-1表无解
3.代码实现:
#include
#include
#include
#include
intCarInGas(inta[],intn,intk);
intmain()
{
inti,k,n,data[100],total;
fstreamrData;
rData.open("E:
\\input.txt",ios:
:
in,0);
rData>>n>>k;
for(i=1;i<=k+1;i++)
{
rData>>data[i];
}
total=CarInGas(data,n,k);
rData.close();
fstreamwData;
wData.open("E:
\\output.txt",ios:
:
app,0);
if(!
wData)
{
cout<<"Falsetoopenfile."<}
if(total==-1)
{
cout<<"Nosolution!
"<wData<<"Nosolution!
"<}
else
{
cout<wData<}
wData.close();
system("pause");
return0;
}
intCarInGas(inta[100],intn,intk)
{
inti,sum=0,curDistan=0;
for(i=1;i<=k+1;i++)
{
if(a[i]>n)
return-1;
}
for(i=1;i<=k+1;i++)
{
curDistan=a[i]+curDistan;
if(curDistan>n)
{
sum++;
curDistan=a[i];
}
}
returnsum;
}
4.测试数据:
Input.txt
77
12345166
运行结果:
Output.txt
4
5.复杂度分析:
for(i=1;i<=k+1;i++)
rData>>data[i];
复杂度为:
O(n)
CarInGas(a,n,k)也为O(n)
两者互不嵌套,所以为O(n)
第五章回溯法
5-13工作分配问题
1.问题描述:
设有n件工作分配给n个人。
将工作i分配给第j个人所需的费用为Cij。
试设计一个算法。
为每一个人都分配1件不同的工作,并使总费用达到最小。
2.分析与证明:
由于每个人都必须分配到工作,在这里可以建一个二维数组allot[i][j],用以表示i号工人完成j号工作所需的费用。
给定一个循环,从第1个工人开始循环分配工作,直到所有工人都分配到。
为第i个工人分配工作时,再循环检查每个工作是否已被分配,没有则分配给i号工人,否则检查下一个工作。
可以用一个一维数组flag[j]来表示第j号工作是否被分配,未分配则flag[j]=0,否则flag[j]=1。
利用回溯思想,在工人循环结束后回到上一工人,取消此次分配的工作,而去分配下一工作直到可以分配为止。
这样,一直回溯到第1个工人后,就能得到所有的可行解。
在检查工作分配时,其实就是判断取得可行解时的二维数组的一下标都不相同,二下标同样不相同。
这样,得到了所有的可行解。
为了得到最少的费用,即可行解中和最小的一个,故需要再定义一个全局变量cost表示最终的总费用,初始cost为allot[i][i]之和,即对角线费用相加。
在所有工人分配完工作时,比较sumFare与cost的大小,如果sumFare小于cost,证明在回溯时找到了一个最优解,此时就把sumFare赋给cost。
考虑到算法的复杂度,可以设计一个剪枝函数。
就是在每次计算局部费用变量sumFare的值时,如果判断sumFare已经大于cost,就没必要再往下分配了,因为这时得到的解必然不是最优解。
3.代码实现:
#include
#include
//usingnamespacestd;
#include
intn;
intcost=0;//现行最小费用
intflag[100];//工作是否被分配,“1”代表已分配
intallot[100][100];//表示i号工人完成j号工作的费用
intWorkAllocation(inti,intsumFare);
intmain()
{
fstreamrData;
rData.open("E:
\\input.txt",ios:
:
in,0);
rData>>n;
for(inti=1;i<=n;i++)
{
for(intj=1;j<=n;j++)
{
rData>>allot[i][j];
flag[j]=0;
}
cost+=allot[i][i];//对角线费用相加,用于剪枝
}
rData.close();
fstreamwData;
wData.open("E:
\\output.txt",ios:
:
app,0);
if(!
wData)
{
cout<<"Falsetoopenfile."<}
wData<wData.close();
system("pause");
return0;
}
intWorkAllocation(inti,intsumFare)
{
if(i>n&&sumFare{
cost=sumFare;
return1;
}
if(sumFare{
for(intj=1;j<=n;j++)
{
if(flag[j]==0)
{
flag[j]=1;
WorkAllocation(i+1,sumFare+allot[i][j]);
flag[j]=0;
}
}
}
returncost;
}
4.测试数据:
Input.txt
3
1023
234
345
运行结果:
Output.txt
9
5.复杂度分析:
从main函数中的for循环中可以看出其算法复杂度为O(n^2)
第六章分支限界法
6-6n皇后问题
1.问题描述:
在nxn格的棋盘上放置彼此不受攻击的n个皇后。
按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。
n皇后问题等价于在nxn各的棋盘上放置n个皇后,任何两个皇后不放在同一行或者同一列或者同一斜线上。
设计一个解n皇后问题的队列式分支限界法,计算在nxn个方格上放置彼此不受攻击的n个皇后的一个放置方案。
2.分析与证明:
题目要求我们只需要输出摆放皇后的一中解,所以在分支限界法时,每一种皇后的摆放即为一条分支,而遍历解空间相当于广度优先遍历树,因此只要将n个皇后摆放完成,便可以输出解,并不存在最优解的情况。
3.代码实现:
#include
#include
#include
usingnamespacestd;
fstreamminput("D:
\\input.txt");
fstreammoutput("D:
\\output.txt");
classNode
{
public:
Node(intn)
{
t=0;
this->n=n;
loc=newint[n+1];
for(inti=0;i<=n;++i)
{
loc[i]=0;
}
}
Node(constNode&other)
{
this->t=other.t;
this->n=other.n;
this->loc=newint[n+1];
for(inti=0;i<=n;++i)
{
this->loc[i]=other.loc[i];
}
}
intt;
int*loc;
intn;
boolcheckNext(intnext);
voidprintQ();
};
boolNode:
:
checkNext(intnext)
{
inti;
for(i=1;i<=t;++i)
{
if(loc[i]==next)
{
returnfalse;
}
if(loc[i]-next==t+1-i)
{
returnfalse;
}
if(loc[i]-next==i-t-1)
{
returnfalse;
}
}
returntrue;
}
voidNode:
:
printQ()
{
for(inti=1;i<=n;++i)
{
moutput<}
moutput<}
classQueen
{
public:
intn;
intansNum;
Queen(intn){
this->n=n;
ansNum=0;
}
voidArrangQueen();
};
voidQueen:
:
ArrangQueen()
{
queueQ;
Nodeini(n);
Q.push(ini);
while(!
Q.empty())
{
Nodefather=Q.front();
Q.pop();
if(father.t==n)
{
father.printQ();
++ansNum;
break;
}
for(inti=1;i<=n;++i)
{
if(father.checkNext(i))
{
NodeChild(father);
++Child.t;
Child.loc[Child.t]=i;
Q.push(Child);
}
}
}
}
intmain(void)
{
intnum;
minput>>num;
QueenQ(num);
Q.ArrangQueen();
return0;
}
4.测试数据:
Input.txt
5
运行结果:
Output.txt
13524
5.复杂度分析:
递归调用for循环,算法复杂度为O(n!
)