本届题型:
一、单选题(25=10分)
二、多选题(45=20分)
三、填空题(25=10分)
四、算法补充题(25=10分)
五、算法理解题(3题,28分)
六、算法设计与分析题(2题,22分)
仅供个人复习用,如有疑问请私信我
须知
此文章为笔者为考试复习准备,参考意义不大。
此文章参考了很多大佬的文章,和自己本人的一些理解。
因链接众多,未能及时一一附上。参考了有51篇文章,及算法设计与分析这本书,再有就是青岛大学《算法设计与分析》课程视频。
第一章
1.算法的定义
算法是指解决问题的一种方法 或一个过程。更严格地讲,算法是由若干条指令组成的有序序列。
2.算法时间复杂度的分类,计算及规则
略
3.算法的性质
- 输入
- 输出
- 有限性
- 确定性
- (可行性)
4.时间复杂度和空间复杂度的计算
略
第二章
1.分治的基本思想
分治算法的主要思想是将原问题递归地分成若干个子问题,直到子问题满足边界条件,停止递归。将子问题逐个击破(一般是同种方法),将已经解决的子问题合并,最后,算法会层层合并得到原问题的答案。
2.分治的基本步骤、特点
基本步骤
step1 分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;
step2 解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题
step3 合并:将各个子问题的解合并为原问题的解。
特点
分治法所能解决的问题一般具有以下几个特征:
- 该问题的规模缩小到一定的程度就可以容易地解决
- 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质。
- 利用该问题分解出的子问题的解可以合并为该问题的解;
- 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题。
第一条特征是绝大多数问题都可以满足的,因为问题的计算复杂性一般是随着问题规模的增加而增加;
第二条特征是应用分治法的前提它也是大多数问题可以满足的,此特征反映了递归思想的应用;
第三条特征是关键,能否利用分治法完全取决于问题是否具有第三条特征,如果具备了第一条和第二条特征,而不具备第三条特征,则可以考虑用贪心法或动态规划法。
第四条特征涉及到分治法的效率,如果各子问题是不独立的则分治法要做许多不必要的工作,重复地解公共的子问题,此时虽然可用分治法,但一般用动态规划法较好。
3.递归公式的推导(过程、结论)
图(a)表示T(n)。
图(b)表示对T(n)进行扩展,形成与递归方程等价的一棵树。cn2表示树的根,即递归顶层的开销。根的三棵子树为小一级的递归方程T(n/4)。
图( c )表示对T(n/4)的进一步展开。根据递归方程,继续展开树中的每个结点,直到问题规模变成1,每个开销为T(1)。
图(d)表示最终结果树。树的高度是log4n,深度为log4n+1。
详情参考原文链接:算法设计与分析期末不挂科
4.应用:二分搜索(算法、时间复杂度、递归关系)、快速排序(方法、时间复杂度)、大整数乘法、合并排序(方法法、时间复杂度)、棋盘覆盖问题、循环赛日程表
二分算法
> #include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
//时间复杂度:O(logn)
const int N=1000;
int yz[N],n;
int x;
>int BSearch(int q[],int x,int n)
{
int l=0,r=n-1;
while(l<=r)
{
int mid=(l+r)/2;
if(x==q[mid])
return mid+1;
if(x>q[mid])
l=mid+1;
else
r=mid-1;
}
return -1;
}
int main()
{
cin>>n;
for(int i=0;i<n;i++)
cin>>yz[i];
cin>>x;
cout<<BSearch(yz,x,n);
return 0;
}
时间复杂度:O(logn)
递归关系:
快速排序
>#include<iostream>
using namespace std;
//时间复杂度:O(nlogn),若分得不对称则最坏情况是O(n^2)
const int N=10000000;
int yz[N], n;
void quick_sort(int q[],int l,int r)
{
if(l>=r)
return ;
int i=l-1,j=r+1,x=q[l+r>>1];
while(i<j)
{
do i++; while(q[i]<x);
do j--; while(q[j]>x);
if(i<j)
{
swap(q[i],q[j]);
}
}
quick_sort(q,l,j);
quick_sort(q,j+1,r);
}
int main()
{
cin>>n;
for(int i=0;i<n;i++)
cin>>yz[i];
quick_sort(yz,0,n-1);
for(int i=0;i<n;i++)
cout<<yz[i];
return 0;
}
合并排序
>#include<iostream>
using namespace std;
//时间复杂度O(nlogn)
const int N=1000000;
int yz[N],n;
int temp[N];
void merge_sort(int q[],int l,int r)
{
if(l>=r)
return;//只有一个数或者没有数的话,就不用排序了
int mid=(l+r)/2 ;//取中点
//先分左边和右边
merge_sort(q,l,mid);
merge_sort(q,mid+1,r);
int k=0,i=l,j=mid+1;//k为temp中数据的个数
//i为左半边有序数列的起点,j为右半边有序数列的起点
//取左半边和右半边中更小的那个值存入temp
while(i<=mid&&j<=r)
if(q[i]<q[j]) temp[k++]=q[i++];
else temp[k++]=q[j++];
//如果左半边或右半边有没排序完的,直接放入temp中
while(i<=mid) temp[k++]=q[i++];
while(j<=r) temp[k++]=q[j++];
//将temp中数据粘贴到原函数中
for(i=l,j=0;i<=r;i++,j++)
q[i]=temp[j];
}
int main()
{
cin>>n;
for(int i=0;i<n;i++)
cin>>yz[i];
merge_sort(yz,0,n-1);
for(int i=0;i<n;i++)
cout<<yz[i];
return 0;
}
其他算法需掌握算法思想,根据书上的代码进行复习
第三章
1.贪心法的基本思想
2.贪心法的特点、基本要素
3.应用:活动安排(算法、算法时间复杂度、算法正确性证明)、最小生成树、单源最短路径、最优装载问题(算法、算法时间复杂度、算法正确性证明)、分数背包问题(算法、算法时间复杂度、算法正确性证明)
活动安排
>#include<bits/stdc++.h>
#include<iostream>
using namespace std;
//如果活动已按非减序排好,时间复杂度为O(n),反之,时间复杂度为O(nlogn)
/*
算法可以得到最优解的证明:
证明贪心算法可以得到最优解要求:
1.证明第一次选择是正确的
2.证明第一次选择后,问题输入变为与第一个活动相容的活动的子问题(因为第二个选择的活动 i 是 E’ 中结束时间最早的,所以活动 i 是正确的依次类推所有的选择都是正确的)
*/
struct activity //活动结构体
{
int start;
int end;
};
bool cmp(activity a,activity b) //cmp参数为sort函数的排序准则,设置为按照活动的结束时间由小到大排序
{
return a.end<b.end;
}
void activity_select(int n,int selected[],activity act[])
{
int i=1;
selected[1]=1;
for(int j=2;j<=n;j++) //从结束时间倒数第二小的活动开始遍历
{
if(act[j].start>=act[i].end)
{
i=j;
selected[j]=1;
}
else
{
selected[j]=0;
}
}
}
int main()
{
cout<<"请输入活动的个数:";
int n;
cin>>n;
int selected[n]; //若活动i被选择,则selected[i]置1,否则置0
activity act[n];
cout<<"请输入活动的开始和结束时间:"<<endl;
for(int i=1;i<=n;i++)
{
cin>>act[i].start>>act[i].end;
}
act[0].start=-1;
act[0].end=-1;
sort(act+1,act+n+1,cmp); //act+1表示第2个元素(i=1,第1个活动)
activity_select(n,selected,act);
cout<<"有如下活动被选择:"<<endl;
for (int i=1; i<=n; i++)
{
if (selected[i]==1)
{
cout<<i<<" ";
}
}
return 0;
}
算法正确性证明:
最优装载问题(算法、算法时间复杂度、算法正确性证明)
#include<iostream>
#include<algorithm>
using namespace std;
/*时间复杂度: 首先按照物品体积进行排序,调用sort() 函数,其最优时间复杂度为O(n),
最差时间复杂度为 O(n logn),平均时间复杂度为O(n logn),
其中按照 贪心策略寻找最优解的 for 语句的时间复杂度为均为 O(n),因此时间复杂度为 O(n+nlogn)。
*/
int main(){
int n,c,i;//n个数据,载重量为c
int cnt=0,sum=0;
int w[100];
cin>>n>>c;
for(i=0;i<n;i++){
cin>>w[i];
}
sort(w,w+n);
for(i=0;i<n;i++){
if(sum+w[i]<c){
sum+=w[i];
cnt++;
}
else break;
}
cout<<"集装箱的个数是"<<cnt<<endl;
cout<<"装载的重量是"<<sum<<endl;
return 0;
}
算法正确性证明:
分数背包问题(算法、算法时间复杂度、算法正确性证明)
#include<bits/stdc++.h>
using namespace std;
//时间复杂度O(nlogn)
/**/
struct node
{
int w,v;//重量,价值
double p;
}a[1005];
int cmp(node a,node b)
{
return a.p>b.p;
}
int main()
{
int n,c;
double ans=0;
cin>>n>>c;
if(n==0 || c==0) return 0;
for(int i=0;i<n;i++)
cin>>a[i].v;
for(int i=0;i<n;i++)
cin>>a[i].w;
for(int i=0;i<n;i++)
a[i].p=(double)a[i].v/a[i].w;//物品的价值比上重量
sort(a,a+n,cmp);
for(int i=0;i<n;i++)
{
if(a[i].w<=c)
{
ans+=a[i].v;
c-=a[i].w;
}
else
{
ans+=a[i].p*c;
break;
}
}
cout<<ans;
}
算法正确性证明:
第四章
1.动态规划的基本思想
2.动态规划解题的四个步骤
3.动态规划的两个基本要素
4.备忘录法的特点
5.应用:矩阵连乘(递归关系、算法、重叠子问题)、
参考连接:矩阵连乘
0-1背包问题(递归关系、算法、最优子结构)、
最长公共子序列(递归关系、算法、最优子结构、重叠子问题)、
投资问题(递归关系、算法、最优子结构)
第五章
1.回溯法的基本思想
回溯法在问题的解空间树中,按深度优先策略,从根结点出发搜索解空间树。算法搜索至解空间树的任意一点时,先判断该结点是否包含问题的解。如果肯定不包含,则跳过对该结点为根的子树的搜索,逐层向其祖先结点回溯;否则,进入该子树,继续按深度优先策略搜索。为避免无效搜索,采用限界/剪枝函数;用约束函数(条件)在扩展结点处剪去不满足约束的子树,即剪去得不到可行解的子树;用目标函数剪去得不到最优解的子树。
2.回溯法解题步骤 (与下题合并)
3.剪枝函数的作用及分类
4.子集树和排列树:0-1背包和旅行商问题
5.应用:0-1背包问题(解空间树,画解空间树中的搜索过程,解的形式,约束,目标、算法)、n后问题(解的形式,约束,算法)、图的M着色(解的形式,约束,算法)、装载问题(解的形式,约束)。
0-1背包问题(解空间树,画解空间树中的搜索过程,解的形式,约束,目标、算法)
n后问题(解的形式,约束,算法)
第六章
1.分支限界法的基本思想及搜索方式
2.分支限界法与回溯法的比较
3.常见的两种分支限界法
4.应用:0-1背包问题、旅行商问题,装载问题
注:复习中注意对比各类算法策略的对比和综合应用。