1、背包问题: O(nlogn)
与0-1背包问题类似,所不同的是在选择物品i装入背包时,可以选择物品i的一部分,而不一定要全部装入背包,1< i < n
①形式化描述:
0-1背包问题:给定C>0,wi>0,vi>0,1<=i<=n,要求找出一个n元0-1向量(x1, x2, … , xn),xi∈{0,1},1<=i<=n,使得(累加wixi(i从1到n))<=C,而且(累加vixi(i从1到n))达到最大。
②算法思想:(用贪心算法解背包问题的基本步骤:)
首先计算每种物品单位重量的价值Vi/Wi,然后,依贪心选择策略,将尽可能多的单位重量价值最高的物品装入背包。
若将这种物品全部装入背包后,背包内的物品总重量未超过C,则选择单位重量价值次高的物品并尽可能多地装入背包。依此策略一直地进行下去,直到背包装满为止。
public static float knapsack(float c,float [] w, float [] v,float [] x)
{
int n=v.length;
Element [] d = new Element [n];
for (int i = 0; i < n; i++) d[i] = new Element(w[i],v[i],i);
MergeSort.mergeSort(d);
int i;
float opt=0;
for (i=0;i0;
for (i=0;iif (d[i].w>c) break;
x[d[i].i]=1;
opt+=d[i].v;
c-=d[i].w;
}
if (ireturn opt;
}
2、动态规划和贪心算法的异同:
同:两个算法都要求问题具有最优子结构性质
异:(1)贪心算法解决的问题具有贪心选择性质,即所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到
(2)动态规划算法通常以自底向上的方式解各子问题,而贪心算法则通常以自顶向下的方式进行,以迭代的方式作出相继的贪心选择.
3、 贪心算法的两个重要性质:贪心选择性质、最优子结构性质
4、 Haffman编码可以通过贪心算法来解决。
5、单源最短路径:计算从源到所有其它各顶点的最短路长度。
①问题描述:给定带权有向图G =(V,E),其中每条边的权是非负实数。另外,还给定V中的一个顶点,称为源。现在要计算从源到所有其它各顶点的最短路长度。这里路的长度是指路上各边权之和。这个问题通常称为单源最短路径问题。
②算法基本思想(Dijkstra算法是解单源最短路径问题的贪心算法。):
设置顶点集合S并不断地作贪心选择来扩充这个集合。一个顶点属于集合S当且仅当从源到该顶点的最短路径长度已知。初始时,S中仅含有源。设u是G的某一个顶点,把从源到u且中间只经过S中顶点的路称为从源到u的特殊路径,并用数组dist记录当前每个顶点所对应的最短特殊路径长度。Dijkstra算法每次从V-S中取出具有最短特殊路长度的顶点u,将u添加到S中,同时对数组dist作必要的修改。一旦S包含了所有V中顶点,dist就记录了从源到所有其它顶点之间的最短路径长度。
例如,对下图中的有向图,应用Dijkstra算法计算从源顶点1到其它顶点间最短路径的过程列在下页的表中。
Dijkstra算法的迭代过程如下表所示:
有许多问题,当需要找出它的解集或者要求回答什么解是满足某些约束条件的最佳解时,往往要使用回溯法。回溯法的基本做法是搜索,回溯法在问题的解空间树中,按深度优先策略,从根结点出发搜索解空间树。具有限界函数的深度优先生成法称为回溯法。
1、子集树与排列数是回溯法解题时遇到的两类典型的解空间树。
子集树:当所给问题是从n个元素的集合S中找出S满足某种性质的子集时,相应的解空间树称为子集树。遍历子集树的算法需O(2n)计算时间。
排列数:当所给问题是确定n个元素满足某种性质的排列时,相应的解空间树称为排列树。遍历排列树需O(n!)计算时间。
2、批处理作业调度(排列树)(O(n!))
①问题描述:给定n个作业的集合{J1,J2,…,Jn}。每个作业必须先由机器1处理,然后由机器2处理。作业Ji需要机器j的处理时间为tji。对于一个确定的作业调度,设Fji是作业i在机器j上完成处理的时间。所有作业在机器2上完成处理的时间和称为该作业调度的完成时间和。批处理作业调度问题要求对于给定的n个作业,制定最佳作业调度方案,使其完成时间和达到最小。
②批处理作业调度问题的回溯算法描述如下:
public class FlowShop
static int n, // 作业数
f1, // 机器1完成处理时间
f, // 完成时间和
bestf; // 当前最优值
static int [][] m; // 各作业所需的处理时间
static int [] x; // 当前作业调度
static int [] bestx; // 当前最优作业调度
static int [] f2; // 机器2完成处理时间
private static void backtrack(int i)
{
if (i > n) {
for (int j = 1; j <= n; j++) bestx[j] = x[j];
bestf = f;
}
else
for (int j = i; j <= n; j++) {
f1+=m[x[j]][1];
f2[i]=((f2[i-1]>f1)?f2[i-1]:f1)+m[x[j]][2];
f+=f2[i];
if (f < bestf) {
MyMath.swap(x,i,j);
backtrack(i+1);
MyMath.swap(x,i,j);
}
f1-=m[x[j]][1];
f-=f2[i];
}
}
3、n后问题
①问题描述:在n×n格的棋盘上放置彼此不受攻击的n个皇后。按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。n后问题等价于在n×n格的棋盘上放置n个皇后,任何2个皇后不放在同一行或同一列或同一斜线上。
解向量:(x1, x2, … , xn)
显约束:xi=1,2, … ,n
隐约束:
不同列:xi ≠ xj
不处于同一正、反对角线: |i-j| ≠ |xi-xj|
public class NQueen1{
static int n; //皇后个数
static int []x; //当前解
static long sum; //当前已找到的可行方案数
public static long nQueen(int nn){
n=nn;
sum=0;
x=new int [n+1];
for(int i=0;i<=n;i++) x[i]=0;
backtrack(1); //解n后问题的非递归迭代回溯法此处是backtrack();
return sum;
}
private static boolean place (int k){
for(int j=1;jif((Math.abs(k-j)==Math.abs(x[j]-x[k]))||(x[j]==x[k]))return false;
return true;
}
private static void backtrack (int t){
if (t>n) sum++;
else
for (int i=1;i<=n;i++){
x[t]=i;
if (place(t)) backtrack(t+1);
}
}
/*public static void backtrack(){//解n后问题的非递归迭代回溯法
x[1]=0;
int k=1;
while(k>0){
x[k]+=1;
while((x[k]<=n)&&!(place(k)))x[k]+=1;
if(x[k]<=n)
if(k==n) sum++;
else{
k++;
x[k]=0;
}
else k--;
}
}*/
}
4、0-1背包问题
static double c; //背包容量
static int n; //物品数
static double [] w; //背包重量数组
static double [] p; //背包价值数组
static double cw; //当前重量
static double cp; //当前价值
static double bestp; 当前最优价值
public static double knapsack(double [] pp, double [] ww, double cc){
c=cc;
n=pp.length-1;
cw=0.0;
cp=0.0;
bestp=0.0;
Element [] q=new Element [n]; //q为单位重量价值数组
for(int i=1;i<=n;i++) q[i-1]=new Element(i, pp[i]/ww[i]); //初始化q[0:n-1]
MergeSort.mergeSort(q); //将各物品以单位重量价值从大到小排序
p=new double[n+1];
w=new double[n+1];
for(int i=1;i<=n;i++){
p[i]=pp[q[n-i].id];
w[i]=ww[q[n-i].id];
}
backtrack(1); //回溯探索
return bestp;
}
private static void backtrack(int i){
if(i>n){ //到达叶结点
bestp=cp;
return;
}
//搜索子树
if(cw+w[i]<=c){ //进入左子树
cw+=w[i];
cp+=p[i];
backtrack(i+1);
cw-=w[i];
cp-=p[i];
}
if(bound(i+1)>bestp) backtrack(i+1); //进入右子树
}
5、最大团问题、图的m着色问题、旅行售货员问题 属于回溯法
1.从活节点表中选择下一扩展节点的不同方式导致不同的分支限界法,最常见的下面两种方式:
(1)队列式(FIFO)分支限界法;(2)优先队列分支限界法
1、概率算法可分为4类:
①数值概率算法常用于数值问题的求解。(这类算法往往得到的是近似解,且近似解的精度随时间的增加不断提高)
②蒙特卡罗算法用于求问题的准确解(对于许多问题来说,近似解毫无意义,用蒙特卡罗能求一个算法的解,但解未必正确,正确解的概率依赖于算法所用的时间,缺点:一般情况下,无法有效的判定所得的的解是否肯定正确)
③拉斯维加斯算法不会得到不正确的解(但有时拉斯维加斯找不到解,与蒙特卡罗类似,拉斯维加斯找到正确解的概率随计算时间的增加而提高。对于所求解问题的同一实例,用同一拉斯维加斯算法反复对该实例求解足够多次,可使求解失败的概率任意小)
④舍伍德算法总能求得问题的一个解,且所求的解总是正确的(当一个确定性算法在最坏情况下的计算复杂性与其在平均情况下的计算复杂性有较大差别时,就可以用舍伍德算法,减少或消除问题的好坏实例之间的差别,它的精髓不是避免算法最坏情况行为,而是消除算法最坏情形行为与特定实例之间的关联性)
1、通常将可在多项式时间内解决的问题看作是“易”解问题,而将需要指数函数时间解决的问题看作是“难”问题。
2.P类问题:所有可以在多项式时间内求解的判定问题构成P类问题;
NP类问题:所有的非确定性的多项式时间可解的判定问题构成NP类问题。
3、下面定义两个重要的语言类P和NP如下:
P={L|L是一个能在多项式时间内被一台DTM所接受的语言}
NP={L|L是一个能在多项式时间内被一台NDTM所接受的语言}(P ⊆ NP)
4.非确定性算法:把问题分解为猜测(非确定性)和验证(确定性)两个阶段,
5.NPC问题:NP中的某些问题的复杂性与整个类的复杂性相关联,这些问题中任何一个如果存在多项式时间的算法,那么所有的NP问题都是多项式时间可解的。这些问题被称为NP-完全问题(NPC问题)。
假设P ≠ NP的图解。若P = NP则三类相同。