0x09算法设计与分析复习(二):算法设计策略-分枝限界法2

参考书籍:算法设计与分析——C++语言描述(第二版)

算法设计策略-分枝限界法

带时限的作业排序

问题描述

对于单机处理机的带时限作业排序问题,如果每一个作业具有相同的处理时间,则可以用贪心法求解。如果每个作业的处理时间允许不同,带时限的作业排序问题可描述为:设有n个作业和一台处理器,每个作业所需的处理时间、要求的时限和收益可用三元组\((t_i,d_i,p_i),0\leq i<n\)表示,其中,作业i的所需时间为\(t_i\),如果作业i能够在时限\(d_i\)内完成,将可收益\(p_i\),求使得总收益最大的作业子集J

分枝限界法求解

带时限的作业排序问题的解结构可以采用固定大小元组或者可变大小元组。

下面采用可变大小元组\((x_0,x_1,\cdots,x_k)\)表示解,\(x_i\)为作业编号。问题的显式约束为\(x_i\in\{0,1,\cdots,n-1\}\)\(x_i<x_{i+1}(0\leq i<n-1)\)隐式约束:对于入选子集J的作业\((x_0,x_1,\cdots,x_k)\),存在一种作业排序使J中作业能够如期完成。问题的目标函数使作业子集J中所有作业的收益之和\(\sum_{i\in J}p_i\),使得总收益最大的作业子集是问题的最优解。如果希望以最小值作为最优解,则可以适当改变目标函数,将其改为未入选子集J的作业所导致的损失:

\[\sum_{i=0}^{n-1}p_i-\sum_{i\in J}p_i=\sum_{i\notin J,i=0,1,2,\cdots,n-1}p_i \]

如果采用带上下界函数的分枝限界法求解,必须设计上下界函数。设X是状态空间树上的一个结点,\(J=(x_0,x_1,\cdots,x_k)\)是已入选的作业子集,它代表一条从根到X的路径。

结点X的下界函数值定义为

\[\hat{c}(X)=\sum_{i\notin J,i<x_k}p_i \]

\(\hat{c}(X)\)实际上是由作业子集\((0,1,\cdots,x_k)\)中,未能入选J的作业所造成的损失。它必定是最终损失的下界,即\(\hat{c}(X)\leq c(X)\)

结点X的上界函数可以定义为

\[u(X)=\sum_{i\notin J,i=0,1,2,\cdots,n-1}p_i=\sum_{i\notin J,i<x_k}p_i+\sum_{i=(x_k+1),\cdots,n-1}p_i \]

\(u(X)\)值由两部分组成:一是迄今为止未入选J的作业所造成的损失,而是假定作业\(x_k\)以后所有作业都未入选所造成的损失。显然,此值是在X处可以预计的最大损失,必有\(c(X)\leq u(X)\)

综上所定义的上下界函数必定满足:\(\hat{c}(X)\leq c(X)\leq u(X)\)

带时限的作业排序算法

//带时限的作业排序
struct Node{
	//状态空间树的结点结构
	Node(Node* par, int k)
	{
		parent=par;
        j=k;
    }
    Node* parent;//指向该结点的双亲结点
	int j;//该结点代表的解分量x[i]=j
};

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;//当前结点X的下界函数\hat{c}(X)=loss,上界函数u(X)=24-prof
	int j,d;//当前活结点所代表的解的分量x[i]=j,d是迄今为止的时间
	Node *ptr;//指向状态空间树中相应的结点
};

template<class T>
class JS{
public:
	JS(T *prof,int *de,int *time,int size);
	T JSFIFOBB();//求最优解值
	void GenerateAns(int *x,int &k);//根据函数JSFIFOBB所生成的状态空间树,产生问题的最优解(x[0],x[1],...,x[k])
  
private:
	T *p,total;//p为收益数组,total初值为n个作业收益之和
	int *t,*d,n;//t为作业处理时间数组,d为按非减次序排列的作业时限数组
	Node *ans,*root;//root指向状态空间树的根,ans指向最优解答案结点
};

template<class T>
T JS<T>::JSFIFOBB()
{
	//要求作业事先已按时限的非减次序排列
  	//函数返回最优解值
	Node *E,*child;
    Queue<qNode<T>> q(mSize);//生成一个FIFO队列实例q
    E=root=new Node(NULL,-1);//构造状态空间树的根结点root
    qNode<T> ep(0,0,0,-1,root),ec;//ep为扩展结点
    T U=total+epsilon;//上界变量U赋初值,total为作业收益和,epsilon为小量
    while(1){
        T loss=ep.los, prof=ep.prof;
        E=ep.ptr;
        //loss为已造成的损失,prof为已获利益
        for(int j=ep.j+1;j<n;j++){
            //考察所有孩子
            if(ep.d+t[j]<=d[j] && loss<U){
                child=new Node(E,j);//构造E的孩子结点
                ec.prof=prof+p[j];
                ec.d=ep.d+t[j];
                ec.ptr=child;
                ec.loss=loss;
                ec.j=j;

                q.Append(ec);//活结点进队列
                T cost=total-ec.prof;//计算上界函数值
                if(cost<U){
                    //修改上界变量
                    U=cost;
                    ans=child;
                }
            }
            loss=loss+p[j];
        }
        do{
            if(q.IsEmpty())
                return total=U;
            ep.q.Front();
            q.Serve();//选择下一个扩展结点
        }while(ep.loss >=U);
    }
}

函数JSFIFOBB()采用分枝限界法的算法框架,构造状态空间树,求最优解的值。要求作业事先已按时限的非减次序排列。

函数GenerateAns()根据JSFIFOBB()生成的状态空间树,产生问题的最优解(x[0],x[1],…,x[k]),k是解向量长度。

0/1背包

问题描述

分枝限界法求解

0/1背包算法

旅行商问题

问题描述

旅行商问题(travelling salesperson)描述为:一个旅行商准备到n个村庄售货,他从A村出发经过其他n-1个村庄,又回到出发地A村,现要求一条最短路径,使得每个村庄都经过一次且仅经过一次。

分枝限界法求解

设有向图\(G=(V,E),|V|=n\),表示连接n个村庄的道路网络,边上的权值为两个村庄间的路程,\(c[n][n]\)是该图的邻接矩阵,也称代价矩阵。

旅行商问题的解是一条回路:
\((x_0,x_1,\cdots,x_{n-1},x_n),x_0=x_n,0\leq x_i<n;x_i\neq x_j,i\neq j且i,j\neq 0或 n\)\(<x_i,x_{i+1}>\in E,0\leq i<n-1\)

一个问题适合用分枝限界法求解的关键是设计有效的上下界函数。设函数\(u(\cdot)\)\(\hat{c}(\cdot)\)分别是代价函数\(c(\cdot)\)的上界和下界函数,要求对状态空间树的每个结点X,总有:\(\hat{c}(X)\leq c(X)\leq u(X)\)

posted @ 2018-01-09 11:11  main_c  阅读(602)  评论(0)    收藏  举报