很久以前搞算法的总结,罗列了一些很基本的算法。(深的我也不会……)
每种算法都只有几句话,但都是最核心的步骤和思想。
NO.1 贪心算法
1 在对问题求解时,总是作出在当前看来是最好的选择。也就是说,不从整体上加以考虑,它所作出的仅仅是在某种意义上的局部最优解(是否是全局最优,需要证明)。
2 基本步骤:
1、从问题的某个初始解出发。
2、采用循环语句,当可以向求解目标前进一步时,就根据局部最优策略,得到一个部分解,缩小问题的范围或规模。
3、将所有部分解综合起来,得到问题的最终解。
例题1: 活动安排问题
hd 2073今年暑假不AC ,2037
思路:
最短序列肯定包含结束时间最早的节目,然后从那个结束时间开始再找结束时间最早的.
例题2: 区间覆盖问题
用i来表示x轴上坐标为[i-1,i]的区间(长度为1),并给出M(1=<M=<200)个不同的整数,表示M个这样的区间。现在让你画几条线段覆盖住所有的区间,条件是:每条线段可以任意长,但是要求所画线段之和最小,并且线段的数目不超过N(1=<N=<50)。
从N=1开始思考,逐渐增加N,考虑从哪儿断开即可.(就是从线段的间歇考虑)
例题3: 哈夫曼编码(最小堆)
hdu 1053
例题4: 单源最短路径(Dijkstra算法) 2066
基本思想是,设置顶点集合S并不断地作贪心选择来扩充这个集合。一个顶点属于集合S当且仅当从源到该顶点的最短路径长度已知。
for( i = 1;i <= n-1;i++ )
{
id = choose(); //从s集中找到某个点,这个点发出的路径是与s集邻接的路径最短的一条
used[id] = true; //将其纳入s集
for( j = 1;j <= n;j++ )
if( dis[id] + mat[id][j] < dis[j] && ( !used[j] ) ) //更新源点到各点之间的距离
{
dis[j] = dis[id ] + mat[id][j]; //mat[][]为邻接矩阵,dis[i]为从指定点到i点的距离
}
}
例题5: 求最小生成树 1863 1233
(1) Prim算法
(2) Kruscal算法(并查集)
附录: 贪心算法和动态规划的比较.
贪心法和动态规划的条件都有最优子结构性质,但还是有所不同的:
例 背包问题: 给定n种物品和一个背包。物品i的重量是Wi,其价值为Vi,背包的容量为C。应如何选择装入背包的物品,使得装入背包中物品的总价值最大?
(1)不能将物品i装入背包多次,也不能只装入部分的物品i。
(2)不能将物品i装入背包多次,可以只装入部分的物品i。
答案:(2)可以用贪心算法解决,但(1)要用动态规划解决。
(2)首先计算每种物品单位重量的价值Vi/Wi,然后,依贪心选择策略放入。
(1)贪心选择之所以不能得到最优解是因为在这种情况下,它无法保证最终能将背包装满,部分闲置的背包空间使每公斤背包空间的价值降低了。事实上,在考虑0-1背包问题时,应比较选择该物品和不选择该物品所导致的最终方案,然后再作出最好选择。由此就导出许多互相重叠的子问题。这正是该问题可用动态规划算法求解的另一重要特征。
NO.2 求a的b次方的方法
用函数function(a,b)
double function( a,b )
{
int ret = 1;
while( b>0 )
{
if( b%2 == 1 ) //基本思想就是从b中提出2来先做a的次方.模2余1的在后面补上a相应的次方
ret *= a;
b /= 2;
a *= a;
}
return ret;
}
一般来说结果都较大难以表述,题目求的往往是结果%某个数m的值。将上程序作一点小改动
用函数function(a,b)
int function( a,b,m )
{
int ret = 1;
while( b>0 )
{
if( b%2 == 1 )
ret = ret * a % m;
b /= 2;
a = a * a % m;
}
return ret;
}
NO.3 不定义tmp变量交换a和b的值
void swap(int &a, int &b){
b = a - b;
a = a - b;
b = a + b;
}
当然这样做的缺点是,如果a-b超出整形范围就比较麻烦了。
这个时候改用
void swap(int &a, int &b){
a = a + b;
b = a - b;
a = a - b;
}
再geek一点?
void swap(int &a, int &b){
a = a ^ b;
b = a ^ b;
a = a ^ b;
}
这样做就没有正负和越界问题了。
NO.4 c/c++中写结构体的构造函数 和 重载运算符
struct AA
{
public:
int a;
int b;
private:
int a;
int b;
protected:
int GetA() const;
void SetA();
public:
int GetB() const;
AA & operator=(const AA & a);
public:
AA();
AA(const AA & a);
};
可以看到struct和class没有区别,唯一的区别是,如果没有写public、private等,struct缺省是公有成员,class缺省是私有的。
NO.5 KMP算法失效函数f()代码(两个字符串a,b返回a中第一次出现b的下标)
fail[h]为string[h]的标记
void f()
{
fail[0] = -1;
for( i = 1;i < l;;i++ )
{
j = fail[i-1];
while( p[j+1] != p[i] && j >= 0 )
{
j = fail[j];
}
if( p[j+1] == p[i] )
fail[i] = j+1;
else
fail[i] = -1;
}
}
NO.6 动态规划
1. 0-1背包问题
令m(i,j)是还有i,i+1,i+2......n个物品要放,背包空间为j时的最大价值
于是:若 j - wi >= 0, m(i,j) = max( m(i+1,j) , m(i+1,j-wi)+vi );
若 j - wi < 0,m(i,j) = m(i+1,j);
2. 最长子序列问题
和01背包问题大致相同,p[i][j]记录a串的前i和b串的前j项内的最大子序列数,若a[i]==b[j],p[i][j] = p[i-1][j-1];
若不等,p[i][j] = max { p[i][j-1],p[i-1][j] }
3 数串最大和矩阵最大和.
数串:b[i]为以a[i]结尾的和最大的子串,b[i] = max{a[i],b[i-1]+a[i]}
(b[i]无需记录的)sum = b[i],i递增,不断更新sum,将最大的sum作为答案.
矩阵:关键在于选子矩阵,对于列已经定了的子矩阵,可以用和求数串最大的方法,求出使得和最大的行,然后从所有可能的列组合中选出最小的即可。
NO.7 博弈问题入门
任何博弈问题(Impartial Combinatorial Games)(ICG)都可以转化为有向图问题。图上每一个节点代表一种局面,每一个节点都有一个SG函数值,一般末节点(游戏结束的局面)的SG函数值由题目推出,其余节点x的函数函数值满足sg(x) = mex{sg(y1),sg(y2),sg(y3)……}其中y1,y2,y3……为x的所有后继,mex{A}表示取不包含在集合A内的最小正整数。
例如NIM游戏,可以看作是有n个棋子,在有向图上。所有棋子都到终点为胜。算法:可以转化为有n一样的张图,每张图一个棋子。
某一点sg函数即是所有图上这点的sg函数值的异或。(^@^)
NO.8 递推问题之错排问题
编号为 1 , 2 ,……, n 的 n
个元素排成一列,若每个元素所处位置的序号都与它的编号不同,则称这个排列为 n
个不同元素的一个错排。 记 n 个不同元素的错排总数为 f(n) ,则
f(n) = n![1-1/1!+1/2!-1/3!+……+(-1)^n*1/n!]
下面用递推的方法推出这个公式:
n 个不同元素的一个错排可由下述两个步骤完成:
第一步,“错排” 1 号元素(将 1 号元素排在第 2 至第 n 个位置之一),有 n - 1
种方法。
第二步,“错排”其余 n - 1 个元素,按如下顺序进行。视第一步的结果,若 1
号元素落在第 k 个位置,第二步就先把 k 号元素“错排”好, k
号元素的不同排法将导致两类不同的情况发生:( 1 ) k 号元素排在第 1
个位置,留下的 n - 2 个元素在与它们的编号集相等的位置集上“错排”,有 f(n -2)
种方法;( 2 ) k 号元素不排第 1 个位置,这时可将第 1 个位置“看成”第 k
个位置,于是形成(包括 k 号元素在内的) n - 1 个元素的“错排”,有 f(n - 1)
种方法。据加法原理,完成第二步共有 f(n - 2)+f(n - 1) 种方法。
根据乘法原理, n 个不同元素的错排种数
f(n) = (n-1)[f(n-2)+f(n-1)] (n>2) 。