西工大算法复习资料总结最终修订版.docx
《西工大算法复习资料总结最终修订版.docx》由会员分享,可在线阅读,更多相关《西工大算法复习资料总结最终修订版.docx(35页珍藏版)》请在冰豆网上搜索。
![西工大算法复习资料总结最终修订版.docx](https://file1.bdocx.com/fileroot1/2022-11/27/bc7e403d-ba77-4033-8aaf-38bd4f64a732/bc7e403d-ba77-4033-8aaf-38bd4f64a7321.gif)
西工大算法复习资料总结最终修订版
笔试部分
1.简答题(40分)
1.算法的基本概念、性质及其与程序的联系与区别
算法:
是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令。
算法性质:
输入---有零个或者多个外部量作为算法的输入;
输出---算法产生至少一个量最为输出;
确定性:
组成算法的每条指令是清晰的、无歧义的;
有限性:
算法中指令的执行次数有限和执行的时间有限。
程序:
是算法用某种设计语言的具体实现,程序可以不满足算法的有限性。
2.大O表示法的含义和渐进时间复杂度(要会计算复杂度)
大O表示法:
称一个函数g(n)是O(f(n)),当且仅当存在常数c>0和n0>=1,对一切n>n0均有|g(n)|<=c|f(n)|成立,也称函数g(n)以f(n)为界或者称g(n)囿于f(n)。
记作g(n)=O(f(n))。
定义:
如果一个问题的规模是n,解这一问题的某一算法所需要的时间为T(n),它是n的某一函数。
T(n)称为这一算法的“时间复杂度”。
当输入量n逐渐加大时,时间复杂度的极限情形称为算法的“渐近时间复杂度”。
3.分治法的基本思想是什么
分治法的基本思想是:
将一个规模为n的问题分解为k个规模较小的子问题,这些子问题互相独立且与原问题相同。
递归地解这些子问题,然后将各子问题的解合并得到原问题的解。
4.回溯算法的基本思想及其一般模式(子集树+排列树)
基本思想:
在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。
当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。
若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束。
而若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束。
搜索子集树的一般模式:
voidsearch(intm)
{
if(m>n)//递归结束条件
output();//相应的处理(输出结果)
else
{
a[m]=0;//设置状态:
0表示不要该物品
search(m+1);//递归搜索:
继续确定下一个物品
a[m]=1;//设置状态:
1表示要该物品
search(m+1);//递归搜索:
继续确定下一个物品
}
}
搜索排列树的一般模式:
voidsearch(intm)
{
if(m>n)//递归结束条件
output();//相应的处理(输出结果)
else
for(i=m;i<=n;i++)
{
swap(m,i);//交换a[m]和a[i]
if()
if(canplace(m))//如果m处可放置
search(m+1);//搜索下一层
swap(m,i);//交换a[m]和a[i](换回来)
}
}
5.动态规划的基本思想、基本步骤、基本要素是什么
基本思想:
将待求解问题分解成若干子问题,先求解子问题,然后从这些子问题的解得到原问题的解。
基本步骤:
①找出最优解的性质,并刻画其结构特征;
②递归地定义最优值;
③以自底向上的方式计算出最优值;
④根据计算最优值时得到的信息,构造最优解。
基本要素:
最优子结构性质和子问题重叠问题
6.什么是最优子结构性质和子问题重叠性质
最优子结构性质:
如果问题的最优解所包含的子问题的解也是最优的,则称该问题具有最优子结构性质(即满足最优化原理)。
最优子结构性质为动态规划算法解决问题提供了重要线索。
子问题重叠性质:
指在用递归演算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。
动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只计算一次,然后将其计算结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只是在表格中简单地查看一下结果,从而获得较高的效率。
7.分治法与动态规划的联系与区别
联系:
基本思想相同,即将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。
区别:
适用于动态规划法求解的问题,经分解得到的子问题往往不是相互独立的。
分治法子问题被重复计算多次,而动态规划子问题可用一个表来记录已解决子问题的答案,避免了子问题重复计算的问题。
8.动态规划的变形(备忘录方法)与动态规划的联系与区别
联系:
都用表格保存已解决的子问题的答案
区别:
备忘录方法的递归方式是自顶向下的,而动态规划的递归方式是自底向上的。
9.分支限界法(广度优先搜索)的基本思想是什么
分支限界法常以广度优先或以最小耗费(最大效益)优先的方式搜索问题的解空间树。
在分支限界法中,每一个活结点只有一次机会成为扩展节点。
活结点一旦成为扩展节点,就一次性产生所有儿子节点。
在这些儿子节点中,导致不可行解或导致非最优解的儿子节点被舍弃,其余儿子节点被加入活结点表中。
此后,从活结点表中取下一节点成为当前扩展节点,并重复上述节点扩展过程,这个过程一直持续到找到所需的解或活结点表为空时为止。
10.分支限界法与回溯法的区别
1.搜索方式不同:
分支限界法使用广度优先或最小消耗优先搜索,而回溯法使用深度优先搜索。
2.主要区别:
在于它们对当前扩展节点所采用的扩展方式不同。
分支限界法:
每个节点只有一次成为活结点的机会
回溯法:
活结点的所有可行子节点被遍历后才被从栈中弹出
11.搜索算法的一般模式
搜索算法关键要解决好状态判重的问题:
voidsearch()
{
open表初始化为空;
起点加入到open表;
while(open表非空)
{
取open表中的一个结点u;
从open表中删除u;
for(对扩展结点u得到的每个新结点vi)
{
if(vi是目标结点)
输出结果并返回;
If(notused(vi))
vi进入open表;
}
}
}
12.贪心算法的基本思想、基本步骤及基本要素
基本思想:
贪心算法总是做出在当前看来是最好的选择,即贪心算法并不从整体最优上加以考虑,所做的选择只是在某种意义上的局部最优选择,即贪心选择。
基本步骤:
①从问题的某个初始解出发;
②采用循环语句,当可以向求解目标前进一步时,就根据局部最优策略,得到一个局部最优解缩小问题的规模;
③将所有局部最优解综合起来,得到原问题的解。
基本要素:
①贪心选择性质:
指所求问题的整体最优解可以通过一系列的局部最优的选择,即贪心选择来达到。
贪心选择策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。
②最优子结构性质:
一个问题的最优解包含其子问题的最优解。
13.贪心算法与动态规划算法联系与区别
联系:
都要求问题具有最优子结构性质,即一个问题的最优解包含其子问题的最优解。
区别:
①在动态规划算法中,每步所做的选择往往是依赖于相关子问题的解,因而只有在解出相关子问题后,才能做出选择。
而在贪心算法中,仅在当前状态下做出最好的选择,即局部最优选择,然后再去解做出这个选择后产生的相应的子问题。
②动态规划算法通常以自底向上方式解各子问题,而贪心算法则通常以自顶向下的方式进行,以迭代的方式做出相机的贪心选择以简化问题规模。
2.设计题(60分---写出核心代码或伪代码和相关的变量声明)
1.用二分查找算法查找某一个元素在线性表中的位置。
此问题的输入是待查元素x和线性表L,输出为x在L中的位置或者x不在L中的信息。
最直接的做法就是一个一个地扫描L的所有元素,直到找到x为止。
这种方法对于有n个元素的线性表在最坏的情况下需要n次比较。
一般来说,若没有其他的附加信息,在有n个元素的线性表中查找一个元素在最坏情况都需要n次比较。
考虑最简单的情况,该线性表为有序表,设其按照主键的递增顺序从小到大排列。
核心伪代码:
functionBinarySearch(L,a,b,x)
begin
ifa>bthenreturn-1
elsebegin
m=(a+b)div2;
ifx=L[m]thenreturn(m)
elseifx>L[m]
thenreturnBinarySearch(L,m+1,b,x);
else
returnBinarySearch(L,a,m-1,x);
end;
end;
2.请采用快速排序算法为下列数字排序,并写出最终结果(21254925*1608)
快速排序的基本思想:
选定一个基准值元素,对带排序的序列进行分割,分割之后的序列一部分小于基准值元素,另一部分大于基准值元素,再对这两个分割好的子序列进行上述的过程。
voidswap(inta,intb){intt;t=a;a=b;b=t;}
intPartition(int[]arr,intlow,inthigh)
{
intpivot=arr[low];//采用子序列的第一个元素作为基准元素
while(low{
//从后往前在后半部分中寻找第一个小于基准元素的元素
while(low=pivot)
{
--high;
}
//将这个比枢纽元素小的元素交换到前半部分
swap(arr[low],arr[high]);
//从前往后在前半部分中寻找第一个大于基准元素的元素
while(low{
++low;
}
//将这个基准元素大的元素交换到后半部分
swap(arr[low],arr[high]);
}
returnlow;//返回基准元素所在的位置
}
voidQuickSort(int[]a,intlow,inthigh)
{
if(low{
intn=Partition(a,low,high);
QuickSort(a,low,n);
QuickSort(a,n+1,high);
}
}
3.写出对下面的序列进行归并排序的过程(从小到大)(631499823481570)
核心代码:
voidMergeSort(intlow,inthigh)
{
if(low>=high)//每个子列表中剩下一个元素时停止
return;
//将列表划分成相等的两个子列表,若有奇数个元素,则在左边子列表大于右边子列表
else
intmid=(low+high)/2;
MergeSort(low,mid);//递归划分子列表
MergeSort(mid+1,high);//递归划分子列表
//新建一个数组b用于存放归并的元素
int[]b=newint[high-low+1];
//两个子列表进行排序归并,直到两个子列表中的一个结束
for(inti=low,j=mid+1,k=low;i<=mid&&j<=high;k++)
{
if(arr[i]<=arr[j])
{
b[k]=arr[i];
i++;
}
else
{
b[k]=arr[j];
j++;
}
}
for(;j<=high;j++,k++)
//如果第二个子列表中仍然有元素,则追加到新列表
b[k]=arr[j];
for(;i<=mid;i++,k++)
//如果第一个子列表中仍然有元素,则追加到新列表
b[k]=arr[i];
for(intz=0;z//将排序的数组b的所有元素复制到原始数组arr中
arr[z]=b[z];
}
4.装载问题
问题关键在于:
首先将第一艘船尽可能装满且c1<最大值max,然后将剩余的部分装上第二艘船c2,若:
总重量-c1关键代码:
intc1,c2,n,w[10];
intweight=0,max=0;
voidsearch(intm)
{
if(m==n)
{
if(weight<=c1)
if(weight>max)
max=weight;
}
else
{
if(weight+=w[m]<=c1)
{
weight+=w[m];
search(m+1);
weight-=w[m]
}
search(m+1);
}
}
5.01-背包问题(回溯法)
关键代码:
intn,c;
intw[1000],v[1000];
inta[1000],max=0;
voidsearch(intm)
{
if(m>=n)
checkmax();
else
{
a[m]=0;
search(m+1);
a[m]=1;
search(m+1)
}
voidcheckmax()
{
inti;
intweight=0,value=0;
for(i=0;i{
if(a[i]==1)
{
weight+=w[i];
value+=v[i];
}
}
if(weight<=c)
if(value>max)
max=value;
}
6.循环赛日程表(递归与分治)
设计思想:
按分治策略,可以将所有选手对分为两半,n个选手的比赛日程表就可以通过为n/2个选手设计的比赛日程表来决定。
递归地用这种一分为二的策略对选手进行分割,直至只剩下两个选手时,比赛日程表的指定就很简单了。
核心代码:
intN=1;
voidUpRightCopy(intn,intarray[])
{
for(inti=0;i{
for(intj=n;j<2*n;j++)
{
array[N*i+j]=2*n+1-array[N*i+2*n-1-j];
}
}
}
voidDownRightCopy(intn,intarray[])
{
for(inti=n;i<2*n;i++)
{
for(intj=n;j<2*n;j++)
{
array[N*i+j]=array[N*(i-n)+j-n];
}
}
}
voidLeftDownCopy(intn,intarray[])
{
for(inti=n;i<2*n;i++)
{
for(intj=0;j{
array[N*i+j]=array[N*(i-n)+j+n];
}
}
}
voidturn(intn,intarray[])
{
if(n==1)
array[0]=1;
else
{
turn(n/2,array);
DownRightCopy(n/2,array);
UpRightCopy(n/2,array);
LeftDownCopy(n/2,array);
}
}
7.最长公共子序列
核心代码:
chara[201],b[201];
intn1,n2;
voidsearch()
{
intList[201][201];
for(inti=0;i<=n1;i++)
List[i][0]=0;
for(intj=0;j<=n2;j++)
List[0][j]=0;
for(inti=1;i<=n1;i++)
{
for(intj=1;j<=n2;j++)
{
if(a[i-1]==b[j-1])
List[i][j]=List[i-1][j-1]+1;
else
{
if(List[i-1][j]>List[i][j-1])
List[i][j]=List[i-1][j];
else
List[i][j]=List[i][j-1];
}
}
}
8.矩阵连乘问题
核心代码:
intn;
intp[11];
voidsearch()
{
inta[11][11],temp;
for(inti=1;i<=n;i++)
a[i][i]=0;
for(intd=1;d<=n-1;d++)
{
for(inti;i<=n-d;i++)
{
intj=i+d;
a[i][j]=0+a[i+1][j]+p[i-1]*p[i]*p[j];
for(intk=i+1;k{
temp=a[i][k]+a[k+1][j]+p[i-1]*p[k]*p[j];
if(tempa[i][j]=temp;
}
}
}
}
9.用备忘录算法实现计算Fibonacci数列
核心代码:
intFib(intn)
{
intresult[n]={0,0,...,0};
intf1,f2;
if(n<2)
returnn;
if(result[n-1]==0)
f1=Fib(n-1);
else
f1=result[n-1];
if(result[n-2]==0)
f2=Fib(n-2);
else
f2=result[n-2];
result[n]=f1+f2;
return(f1+f2);
}
设计一个的高效算法实现Fibonacci数列
Version1(效率最差)
1.long Fibonacci(int n)
2.{
3. if (n == 0)
4. return 0;
5. else if (n == 1)
6. return 1;
7. else if (n > 1)
8. return Fibonacci (n - 1) + Fibonacci (n - 2);
9. else
10. return -1;
11.}
Version2(效率其次)
1.long Fibonacci2(int n)
2.{
3. if (n == 0)
4. return 0;
5. else if (n == 1)
6. return 1;
7. else if (n > 1)
8. {
9.
10. if(tempResult[n] !
= 0)
11. return tempResult[n];
12. else
13. {
14. tempResult[n] = Fibonacci2 (n - 1) + Fibonacci2 (n - 2);
15. return tempResult[n];
16. }
17. }
18.}
Version3(效率最高---放弃递归用循环实现)
1.long Fibonacci3(int n)
2.{
3. long * temp = new long[n + 1];
4.
5. temp[0] = 0;
6.
7. if (n > 0)
8. temp[1] = 1;
9.
10. for(int i = 2; i <= n; ++i)
11. {
12. temp[i] = temp[i - 1] + temp[i - 2];
13. }
14.
15. long result = temp[n];
16.
17. delete[] temp;
18.
19. return result;
20.}
10.活动安排问题
问题关键在于:
理解什么是相容性活动!
若区间[s1,f1)与区间[s2,f2)不相交,则称活动1与活动2是相容的,即s1>=f2或s2>=f1时,活动1与活动2相容。
(区间表示的是活动的起始时间s和结束时间f)
核心代码:
structaction
{
intbegin;
intend;
}a[1000];
intn;//n表示活动的总数
intsum=0;//sum表示能参加的活动的数量
voidsearch()
{
sum=1;
inttemp=a[0].end;//temp表示第一个活动结束的时间
for(inti=1;i{
if(a[i].begin>=temp)
{
sum++;
temp=a[i].end;
}
}
}
voidselection_sort()
{
for(inti=0;i{
intk=i;
for(intj=i+1;jif(a[j].endk=j;
structactiontemp=a[i];
a[i]=a[k];
a[k]=temp;
}
}
11.最优服务次序问题
思路是最短服务时间优先,先将服务时间排序,然后注意后面的等待服务时间既包括等待部分,也包括服务部分。
贪心策略:
对服务时间最短的顾客先服务的贪心选择策略。
首先对需要服务时间最短的顾客进行服务,即做完第一次选择后,原问题T变成了需对n-1个顾客服务的新问题T'。
新问题和原问题相同,只是问题规模由n减小为n-1。
基于此种选择策略,对新问题T',选择n-1顾客中选择服务时间最短的先进行服务,如此进行下去,直至所有服务都完成为止。
每个客户需要的等待时间为:
T
(1)=t
(1);
T
(2)=t
(1)+t
(2);
......
T(n)=t
(1)+t
(2)+...+t(n);
总等待时间为:
T=n*t
(1)+(n-1)*t
(2)+(n-2)*t(3)+...+(n+1-i)*t(i)+...+2*t(n-1)+1*t(n)
注:
st[]是服务数组,st[j]为第j个队列上的某一个顾客的等待时间;su[]是求和数组,su[j]的值为第j个队列上所有顾客的等待时间;
第一种代码:
1.double greedy(vector<