算法知识点
采用广度优先产生状态空间树的结点,并使用剪枝函数的方法称为——分枝限界法。
分枝限界法的基本做法是:
以广度优先的方式搜索问题的状态空间树。每一个活结点只有一次机会成为扩展结点。
按照广度优先的原则,活结点一旦成为扩展结点(E结点)R后,就依次生成它的所有孩子结点。在这些孩子结点中,导致不可行解或导致非最优解的孩子结点被舍弃,其余孩子结点被一一加入活结点表中。
此后,R自身成为死结点,从活结点表中取下一结点成为当前扩展结点,并重复上述结点扩展过程。
这个过程一直持续到找到所需的解或活结点表为空时为止。
分枝限界法的种类
根据从活结点中选择下一个扩展结点的不同次序,将分枝限界法分为三类:
(1)队列式(FIFO)分枝限界法:将活结点表组织成一个队列,按队列的先进先出(FIFO)原则选取下一个结点为当前扩展结点;
(2)堆栈式(LIFO)分枝限界法:将活结点表组织成一个堆栈,按堆栈的后进先出(LIFO)原则选取下一个结点为当前扩展结点;
(3)优先队列式(LC)分枝限界法:将活结点表组织成一个优先队列,并按优先队列中规定的结点优先级选取优先级最高的下一个结点为当前扩展结点
算法题目来源
n个作业,每个作业i处理所需时间为ti,如果能够在时限di内完成将可收益pi。
将每个作业所需的处理时间、要求时限和收益用三元组(ti,di,pi)0≤i<n表示。求使得总收益最大的作业子集J。
算法题目描述
采用可变大小元组(x0,x1,…,xk)表示解,xi为作业编号。
设有带时限的作业排序实例:n=4,(p0,d0,t0)=(5,1,1),(p1,d1,t1)=(10,3,2),(p2,d2,t2)=(6,2,1),(p3,d3,t3)=(3,1,1),
求使得总收益最大的作业子集J。
模板代码
void BranchBound(Node *t) //t是指向状态空间树的根结点指针
{ do{ //lst为活结点表,表中元素为指针类型;
for (对结点E的每个不受限的孩子)
{ x=new Node;x->parent=E;//构造E的孩子结点x
if (x是一个答案结点)
{ 输出从x到t的一条路径; return; }//输出一个可行解后,算法终止
lst.Append(x);//否则(x不是答案结点)x进活结点表
}
if (lst.IsEmpty())//活结点表lst为空时
{ cout<<“没有答案结点”; return;} //搜索失败终止
lst.Serve(E);//从活结点表lst中输出一个活结点为E-结点
} while(1);
}
做题过程中遇到的bug及解决方案
目标函数:已入选子集J的所有作业所获总收益。
则使目标函数(总收益)最大的作业子集是最优解。
状态空间树的结点结构Node:
struct Node{ Node(Node *par,int k) {parent=par; j=k; } Node *parent; //指向该结点的双亲结点 int j; //该结点代表的解分量x[i]=j }
//活结点表的结点结构qNode: template<class T> struct qNode{ qNode(){ } qNode(T p,T los,int sd,int k,Node *pt) { prof=p;loss=los;d=sd;ptr=pt;j=k; } T prof, loss; //loss为当前结点下界函数(X) ,即已有损失 //prof为当前结点已获收益,上界函数u(X)=24-prof int j,d; //d是迄今为止的时间 //当前活结点所代表的解分量x[i]=j Node *ptr;//指向状态空间树中相应的结点 };
相关算法题型题目总结
分枝限界法解决0/1背包问题
解的结构、约束条件、目标函数和状态空间树的分析与回溯法相同。
.分枝限界法求解0/1背包问题的关键问题是设计上下界函数LBB(X)、UBB(X):
设p(i)/w(i)≥p(i+1)/w(i+1),0≤i<n-1
X是状态空间树上的结点,从根到X的部分向量为(x0,x1,…,xk-1)。
以X为根的子树可以看成背包载重为cu,由剩余物品组成物品集的0/1背包的状态空间树。
Node为状态空间树的结点类;
(采用双亲表示法,其中指针parent指向双亲结点,布尔变量left表示当前结点是其双亲的左孩子还是右孩子。)
.pqNode为优先权队列的元素类;
(按UBB值由大到小的顺序,存储那些自身已经生成、而孩子结点还未考察的结点。)
Knapsack为背包类;
stuct Node//状态空间树结点 { Node(Node *par, bool lft) { parent=par; left=lft; } Node * parent;//指向双亲结点的指针 bool left;//若当前结点是双亲的左孩子,则left为true; //否则为false。 } template <class T> stuct pqNode//活结点结构 { pqNode(){ } pqNode(float cap, T prof, T ub, int lev, Node *p) { cu=cap; profit=prof; level=lev; UBB=ub; ptr=p; } float cu;//背包的剩余载重cu T profit,UBB;//已获收益profit和上界函数值UBB int level;//当前结点的level(根结点为0) Node *ptr;//指向状态空间树上相应的结点 } template <class T> class Knapsack//背包类 { public: Knapsack(T *prof, float *wei, float mm, int len); T LCBB();//求最优解值 void GenerateAns(int *x);//产生最优解 private: voidLUBound(T cp, float cw, int k, T& LBB, T& UBB); //计算结点的上下界UBB和LBB T* p;//p保存n个物品价值 float* w, m;//w保存n个物品重量,m为背包载重 int n;//物品数目 Node *ans, *root;//状态空间树的最优解结点和根 }
相关算法总结
分枝限界法与回溯法的异同
一、分枝限界法与回溯法的共同点
都是在问题的状态空间树上搜索问题解的算法,都通过活结点表实现。都用约束函数剪去不含答案结点的分枝,用限界函数剪去不含最优解的分枝.
二、分枝限界法与回溯法的区别
(1)求解目标不同:回溯法的求解目标是找出解空间树中满足约束条件的所有可行解;而分枝限界法的求解目标则是找出满足约束条件的一个可行解,或某种意义下的最优解。
(2)搜索方式不同:回溯法以深度优先的方式搜索解空间树,而分枝限界法则以广度优先的方式搜索解空间树。
(3)对当前扩展结点的扩展方式不同:回溯法中的每个活结点可能多次成为当前扩展结点,纵深方向扩展其一个儿子,然后再回溯后扩展其他儿子;而分枝限界法中每一个活结点只有一次机会成为扩展结点,一次产生所有孩子结点,自身成为死结点,之后无需再返回该结点处。