- 贪心算法:逐步建立一个解决方案,具体地优化一些局部准则。
- 分治:将一个问题分解成独立的子问题,求解每个子问题,并将子问题的解组合起来形成原问题的解。
- 动态规划:把一个问题分解成一系列相互重叠的子问题,并为越来越大的子问题建立解决方案。
一、weighted interval scheduling 加权区间调度
- 问题描述:每个job有开始时间、结束时间和权重,找job不overlap的最大权重。
- 解法1:最早结束时间优先。(若权重都一样,用贪心法是正确的,但在本题不对)。
- 以完成时间升序标记jobs。记p(j)=i,表示j>i,在选择job j后,可选的最大下标为i。
记OPT(j)表示由作业1,2,3…j组成的请求的最优解。
若OPT选择j,wight包括vj,包括之前的OPT:1,2,…p(j);
若OPT不选择j,一定包括OPT:1,2…j-1 - 解法2:暴力法
//伪代码
输入:n,s[n],f[n],v[n]
排序:根据f[n]
计算:p[n]
int computeOpt(int j){
if(j==0)
return 0;
else
return max(v[j]+computeOpt(p[j]),computeOpt(j-1));
}
分析:分层递归调用,时间复杂度指数级增长。
解决:存储每个子问题的计算结果,以备查找之需。
//伪代码. O(nlogn) time |
输入:n,s[n],f[n],v[n];
排序:根据f[n];//O(nlogn)
计算:p[n];
定义:m[n], m[0]=0;
int mComputeOpt(int j){
//每次调用O(1) time ,总共O(n) time
if(m[j] is empty)
m[j]=max( v[j]+mComputeOpt(p[j]) , mComputeOpt(j-1) );
return m[j];
}
分析:时间复杂度O(nlogn),若已由开始时间、结束时间排序O(n)。
- 解法3:用DP算法计算最优的区间集。
findSolution(int j){
if(j==0)
return {
};
else if(v[j]+m[p[j]]>m[j-1])
return {
j} ∪ findSolution(p[j]);
else
return findSolution(j-1);
}
分析:由于递归调用次数<=n ,因此时间复杂度为O(n)。
- 解法4:自底向上的动态规划,展开递归。
//伪代码
bottomUp(int n,int s[],int f[],int v[]){
sort(f[n]);
compute p[n];
int m[n];m[0]=0;
for(int i=1;i<=n;i++)
m[i]=max( v[i]+m[p[j]] , m[i-1] );
}
- 实现:
#include <iostream>
#include <cmath>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn = 205;
struct Job
{
int s, e, v, index;
Job(int s=0,int e=0,int v=0,int index=0)//构造函数特有语法,比=效率高,有默认参数
:s(s), e(e), v(v), index(index)
{
}
}job[maxn];
int p[maxn], m[maxn];//p记录job j前第一个不冲突的job i;m记录job 1,2..j的max value
bool cmpE(Job a, Job b)
{
return a.e < b.e;//将job 结束时间从小到大排序
}
int main()
{
//读入数据&初始化
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
memset(p, 0, sizeof(p));
memset(m, 0, sizeof(m));
int n; cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> job[i].s >> job[i].e >> job[i].v;
job[i].index = i;
}
//处理:1.排序 2. 计算p 3.计算 m
//1.
sort(job, job + n, cmpE);
//2.
for (int i = n; i >0 ; i--)
{
for (int j = n - 1; j > 0;j--)
if (job[i].s >= job[j].e)
{
p[i] = job[j].index;
break;
}
}
//3.
for (int i = 1; i <= n; i++)
{
m[i] = max(job[i].v + m[p[i]], m[i - 1]);
}
//输出:m[n] 即max value of n jobs
cout << m[n] << endl;
return 0;
}
二、segmented least squares 分段最小二乘法
- 最小二乘:统计学基本问题——给定n个点(坐标(xi,yi)),找到一条线y = ax + b使误差的积分到达最小。
- 问题:有n个点和一个常数c,找到一系例的线,满足min f(x)=E+cL.
E=每个线段的误差之和;L=线的数量。 - 动态规划解决思路:
记OPT(j)=点p1…pj的最小误差之和。
e(i,j)=点pi…pj的最小平方和。
- 算法:
selectedLeastSquares(int n,int p[n],int c){
for(int j=1;j<=n;j++)
for(int i=1;i<=j;i++)
计算e(i,j);
int m[maxn];m[0]=0;
for(int j=1;j<=n;j++)
m[j]=min{
eij+c+m[i-1]} ,1<=i<=j
return m[n]
}
- 分析:O(n3) time | O(n2) space
每次计算e(i,j) O(n2) time ,共计算n次
改进:通过预计算,改善为O(n2) time | O(n) space
三、knapsack problem 背包问题
- 问题描述:给定n个物品和1个背包。每个物品i有重量wi和价值vi;背包可承重W;目标:找背包可放入物品的最大价值。
- 贪心算法无法解决:①以价值从大到小放×②以重量从小到大放×③以vi/wi从大到小放×
- 动态规划解法:
定义OPT(i,w)=物品1,2…i的可放入承重w的背包的最大价值
对于第i个物品:
①若i不放入背包:OPT(i,w)=OPT(i-1,w)
②若i放入:OPT(i,w)=OPT(i-1,w-wi)+v[i]
//bottom-up
//O(n*W) time | O(n*W) space
int knapsack(int n,int W,int w[n],int v[n]){
int OPT[maxn][maxn];
//OPT(i,w)=0;if(i==0)
for(int j=0;j<=W;j++)
OPT[0][j]=0;
//OPT(i,w)=OPT(i-1,w);if(w<wi)
//OPT(i,w)=max( v[i]+OPT(i-1,w-wi) , OPT(i-1,w));else
for(int i=1;i<=n;i++){
for(int j=1;j<=W;j++){
if(w[i]>j)
OPT[i][j]=OPT[i-1][j];
else
OPT[i][j]=max( v[i]+OPT[i-1][j-w[i]] , OPT[i-1][j]);
}
}
return OPT[n][W];
}
- 算出最优值后,可以回溯找到解。
四、RNA secondary structure RNA二级结构
- RNA:string b;b由字母{A,C,G,U}组成。
- 二级结构:RNA是单链的,有环(每对的末端至少间隔4个字母)、与自身形成碱基对(A-U or G-C)。
- 问题描述:给定一个RNA序列,找到一个二级结构,使碱基对的数量最多。
- 记OPT(j)=b1,b2…bj中最多碱基对。
记OPT(i,j)=bi…bj中最多碱基对。 - 选择匹配bt-bn
查找结构(b1,b2…bt-1)=OPT(t-1)和bt+1,bt+2…bn-1。
五、sequence alignment 序列比对
P7-8
- 记gap 为 δ,没匹配(不一样)为αpq。
cost=δ+αCG+αTA 。 - 应用:语音识别、生物学计算、unix diff。
- 序列对比:给定两个string,找到Min cost。
- 定义:OPT(i,j)=Min cost of string x 1 x 2 … x i and y 1 y 2 … y j .
①若xi有对应yj,OPT(i,j)=( xi-yj ) +OPT(i-1,j-1)
②若xi无对应,OPT(i,j)=δi +OPT(i-1,j)
③若yj无对应,OPT(i,j)=δji +OPT(i,j-1) - 算法:
//伪代码
int sequenceAlignment(int m,int n,int x[m],int y[n],int δ[],int α[][]){
//if j=0
for(int i=0;i<m;i++)
OPT[i,0]=δ[xi];
//if i=0
for(int j=0;j<n;j++)
OPT[0,j]=δ[yj];
//otherwise
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
OPT[i][j]=min(α[xi][yi]+OPT[i-1][j-1],δ[xi]+OPT[i-1][j],δ[yj]+OPT[i][j+1]);
}
}
return OPT[m][n];
}
- 分析: Θ(mn) time | Θ(mn) space.
- 练习:C:Human Gene Functions
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std;
#define maxn 105
int dp[maxn][maxn];
int M[maxn][maxn];
//题目中的 scoring matrix
void matrix(){
M['A']['A'] = M['C']['C'] = M['G']['G'] = M['T']['T'] = 5;
M['A']['C'] = M['C']['A'] = M['A']['T'] = M['T']['A'] = -1;
M['-']['T'] = M['T']['-'] = -1;
M['A']['G'] = M['G']['A'] = M['C']['T'] = M['T']['C'] = -2;
M['T']['G'] = M['G']['T'] = M['-']['G'] = M['G']['-'] = -2;
M['C']['G'] = M['G']['C'] = M['-']['A'] = M['A']['-'] = -3;
M['-']['C'] = M['C']['-'] = -4;
}
int main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
matrix();
int t; cin >> t;
while (t--){
//初始化&读入
char s1[maxn], s2[maxn];
int len1, len2;
cin >> len1 >> s1+1; cin >> len2 >> s2+1;//将字符串输入到s1中,且从s1[1]开始输入
//scanf("%d %s", &len2, s2 + 1);
//处理
dp[0][0] = 0;
for (int i = 1; i <= len1; i++)
dp[i][0] = dp[i - 1][0] + M[s1[i]]['-'];
for (int j = 1; j <= len2; j++)
dp[0][j] = dp[0][j - 1] + M['-'][s2[j]];
for (int i = 1; i <= len1; i++){
for (int j = 1; j <= len2; j++){
dp[i][j] = max(dp[i - 1][j - 1] + M[s1[i]][s2[j]],
max(dp[i - 1][j] + M[s1[i]]['-'],
dp[i][j - 1] + M['-'][s2[j]]));
}
}
//输出
cout << dp[len1][len2] << endl;
}
return 0;
}
- 总结:1.C++数组下标可以是字符(ASCII码:a-97,A-65, - 45)
2.数组及赋值,可以放在一个函数中,main中再调用一次即可。
3.char c[5];
scanf("%s",c+1);//用指针的方式,传入一个字符串,直到遇到空格。
4.max对比多个数:max(a,max(b,c));
六、Hirschberg’s algorithm 赫施伯格算法
七、Bellman-Ford algorithm 贝尔曼-福特算法
八、distance vector protocols 距离向量协议
九、negative cycles in a digraph 有向图中的负圈
来源:oschina
链接:https://my.oschina.net/u/4399909/blog/4717786