分支限界法及其拓展 educoder

本文介绍了使用分支限界法解决0-1背包问题和旅行商问题的C++实现。首先详细展示了0-1背包问题的分支限界算法,接着探讨了旅行商问题的分支限界解法,包括确定上界和下界的策略。这两个问题都是经典的组合优化问题,分支限界法提供了一种有效寻找最优解的途径。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

无语呀,这章代码太难写了,写的我差点直接抓起电脑从四楼跳下去,我就是菜鸡呜呜,同志们必须关注点赞转发三连,送飞机送轮船,呜呜,写的我差点想收费,不能让你们轻易剽窃我捣腾了那么久的成果呜呜

分支限界法

第1关:0-1背包

//4
//0 30 6 4
//30 0 5 10
//6 5 0 20
//4 10 20 0
#include <bits/stdc++.h>
#include <fstream>
#include <iostream>
using namespace std;
class Object
{
public:
    int id;//编号 
    int weight;//重量 
    int price;//价值 
    float d;//单位重量价值 
};
class MaxHeapQNode
{
public:
    MaxHeapQNode *parent;//父结点,可以记录下路径 
    int lchild;//左子结点 
    int upprofit;//值  就是bound 优先级按照这个来 
    int profit;//当前价值 
    int weight;//重量 
    int lev;//层次 
};
//建立优先队列时使用的 
struct cmp
{
    bool operator()(MaxHeapQNode *&a, MaxHeapQNode *&b) const
    {
        return a->upprofit < b->upprofit;
    }
};
 
//用于预处理的重排 
bool compare(const Object &a, const Object &b)
{
    return a.d >= b.d;
}
 
int n;//物品件数 
int c;//背包容量 
int cw;//当前重量 
int cp;//当前解 
int bestp;//最优解的值 
Object obj[100];//物品集合 
int bestx[100];//最优解的物品集合 
 
//添加节点到优先队列 
void AddAliveNode(priority_queue<MaxHeapQNode *, vector<MaxHeapQNode *>, cmp> &q, MaxHeapQNode *E, int up, int wt, int curp, int i, int ch)
{
    MaxHeapQNode *p = new MaxHeapQNode;
    p->parent=E;//父节点 
    p->lchild=ch;//ch=1左子节点 
    p->weight=wt;
    p->upprofit=up;//bound 
    p->profit=curp;
    p->lev=i;//层次 
    q.push(p);//将节点p加入队列q 
}
int Bound(int i,int cleft,int cv)
{
//	cout<<"cleft:"<<cleft<<" cv:"<<cv<<" i:"<<i<<endl;
	float b=cv;
	while(i<=n&&obj[i].weight<=cleft)
	{
		cleft-=obj[i].weight;
		b+=obj[i].price;
		i++;
	}
	if(i<=n)
		b+=(obj[i].d*cleft);
	return b;
}
//分支限界法求解 
void MaxKnapsack()
{
	//优先队列,以cmp来确定优先级 ,maxheapqnode*是类型,还可以是int string.... 
    priority_queue<MaxHeapQNode *, vector<MaxHeapQNode *>, cmp > q;  
    //初始化 
    MaxHeapQNode *E=NULL;
    cw=cp=bestp=0;
    int i=1;
    int up=Bound(1,c-cw,0);
//    cout<<"bound:"<<up<<endl;
    //当处理的层次没有达到叶子结点,不断处理队列中的结点 
    while(i!=n+1)
    {
    	//左子结点 :加入后不超出容量就可以加入 
        int wt=cw+obj[i].weight; 
        if(wt<=c)
        {
            if(bestp<cp+obj[i].price)
                bestp=cp+obj[i].price;
            AddAliveNode(q,E,up,cw+obj[i].weight,cp+obj[i].price,i+1,1);
            //参数顺序:优先队列q 节点E 当前重量 bound 当前价值 层数  1表示左节点 
        }
        
        //右子结点,如果可能产生最优解,可以加入 
        up=Bound(i + 1,c-cw,cp);
//        cout<<" bound right"<<up<<endl;
        if(up>=bestp) //(注意这里必须是大于等于) 
        {
            AddAliveNode(q,E,up,cw,cp,i+1,0);
        }
        //取出队首结点给下一次循环来处理 
        E=q.top();
//        cout<<" Ew:"<<E->weight<<" Ep:"<<E->profit<<" Eup:"<<E->upprofit<<endl;
        q.pop();
        cw=E->weight;//(结点的重量) 
        cp=E->profit;//(结点的价值) 
        up=E->upprofit;//(结点的值) 就是bound 
        i=E->lev;//(结点的层次) 
        
//        cout<<"当前层次:"<<i<<endl;
    }
    //构造最优解的物品集合 
    for(int j = n; j > 0; --j)
    {
        bestx[obj[E->lev-1].id] = E->lchild;
        E = E->parent;
    }
}

//void OutPut()
//{
//    cout<<bestp<<endl;
//    for(int i = 1; i <= n; ++i)
//        if(bestx[i] == 1)
//           cout<<i<<" ";
//	cout<<endl; 
//}
// 

 
int main()
{
    cin>>n>>c;
	for(int i=1;i<=n;i++)
		cin>>obj[i].weight;
	for(int i=1;i<=n;i++)
	{
		cin>>obj[i].price;
		obj[i].d=1.0*obj[i].price/obj[i].weight;
	}
	sort(obj+1,obj+n+1,compare);
	MaxKnapsack();
	cout<<bestp<<endl;
    return 0;
}
 

第2关:TSP问题

这个我对不起,我写不出来,我看了书上的代码又臭又长,我实在看不下去,我试图再网上找个简单的代码,没有.....,我自己写,每次写到一半就卡住把自己否了,这是我嫖的人家的代码

//分支限界法
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<queue>
const int INF = 100000;
const int MAX_N = 22;
using namespace std;
//n*n的一个矩阵
int n;
int cost[MAX_N][MAX_N];//最少3个点,最多MAX_N个点
struct Node
{
    bool visited[MAX_N];//标记哪些点走了
    int s;//第一个点
    int s_p;//第一个点的邻接点
    int e;//最后一个点
    int e_p;//最后一个点的邻接点
    int k;//走过的点数
    int sumv;//经过路径的距离
    int lb;//目标函数的值(目标结果)
    bool operator <(const Node &p)const
    {
        return p.lb < lb;//目标函数值小的先出队列
    }
};
priority_queue<Node> pq;//创建一个优先队列
int low, up;//下界和上界
bool dfs_visited[MAX_N];//在dfs过程中搜索过

                        //确定上界,利用dfs(属于贪心算法),贪心法的结果是一个大于实际值的估测结果
int dfs(int u, int k, int l)//当前节点,目标节点,已经消耗的路径
{
    if (k == n) return l + cost[u][1];//如果已经检查了n个节点,则直接返回路径消耗+第n个节点回归起点的消耗
    int minlen = INF, p;
    for (int i = 1; i <= n; i++)
    {
        if (!dfs_visited[i] && minlen > cost[u][i])//取与所有点的连边中最小的边
        {
            minlen = cost[u][i];//找出对于每一个节点,其可达节点中最近的节点
            p = i;
        }
    }
    dfs_visited[p] = true;//以p为下一个节点继续搜索
    return dfs(p, k + 1, l + minlen);
}
void get_up()
{
    dfs_visited[1] = true;//以第一个点作为起点
    up = dfs(1, 1, 0);
}
//用这种简单粗暴的方法获取必定小于结果的一个值
void get_low()
{
    //取每行最小值之和作为下界
    low = 0;
    for (int i = 1; i <= n; i++)
    {
        //创建一个等同于map的临时数组,可用memcpy
        int tmpA[MAX_N];
        for (int j = 1; j <= n; j++)
        {
            tmpA[j] = cost[i][j];
        }
        sort(tmpA + 1, tmpA + 1 + n);//对临时的数组进行排序
        low += tmpA[1];
    }
}
int get_lb(Node p)
{
    int ret = p.sumv * 2;//路径上的点的距离的二倍
    int min1 = INF, min2 = INF;//起点和终点连出来的边
    for (int i = 1; i <= n; i++)
    {
        //cout << p.visited[i] << endl;
        if (!p.visited[i] && min1 > cost[i][p.s])
        {
            min1 = cost[i][p.s];
        }
        //cout << min1 << endl;
    }
    ret += min1;
    for (int i = 1; i <= n; i++)
    {
        if (!p.visited[i] && min2 > cost[p.e][i])
        {
            min2 = cost[p.e][i];
        }
        //cout << min2 << endl;
    }
    ret += min2;
    for (int i = 1; i <= n; i++)
    {
        if (!p.visited[i])
        {
            min1 = min2 = INF;
            for (int j = 1; j <= n; j++)
            {
                if (min1 > cost[i][j])
                    min1 = cost[i][j];
            }
            for (int j = 1; j <= n; j++)
            {
                if (min2 > cost[j][i])
                    min2 = cost[j][i];
            }
            ret += min1 + min2;
        }
    }
    return (ret + 1) / 2;
}

int solve()
{
    //贪心法确定上界
    get_up();
    //取每行最小的边之和作为下界
    //cout << up << endl;//test
    get_low();
    //cout << low << endl;//test
    //设置初始点,默认从1开始
    Node star;
    star.s = 1;//起点为1
    star.e = 1;//终点为1
    star.k = 1;//走过了1个点
    for (int i = 1; i <= n; i++)
    {
        star.visited[i] = false;
    }
    star.visited[1] = true;
    star.sumv = 0;//经过的路径距离初始化
    star.lb = low;//让目标值先等于下界    
    int ret = INF;//ret为问题的解
    pq.push(star);//将起点加入队列
    while (pq.size())
    {
        
        Node tmp = pq.top();pq.pop();
        if (tmp.k == n - 1)//如果已经走过了n-1个点
        {
            //找最后一个没有走的点
            int p;
            for (int i = 1; i <= n; i++)
            {
                if (!tmp.visited[i])
                {
                    p = i;//让没有走的那个点为最后点能走的点
                    break;
                }
            }
            int ans = tmp.sumv + cost[p][tmp.s] + cost[tmp.e][p];//已消耗+回到开始消耗+走到P的消耗
            //如果当前的路径和比所有的目标函数值都小则跳出
            if (ans <= tmp.lb)
            {
                ret = min(ans, ret);
                break;
            }
            //否则继续求其他可能的路径和,并更新上界
            else
            {
                up = min(up, ans);//上界更新为更接近目标的ans值
                ret = min(ret, ans);
                continue;
            }
        }
        //当前点可以向下扩展的点入优先级队列
        Node next;
        for (int i = 1; i <= n; i++)
        {
            if (!tmp.visited[i])
            {
                //cout << "test" << endl;
                next.s = tmp.s;//沿着tmp走到next,起点不变            
                next.sumv = tmp.sumv + cost[tmp.e][i];//更新路径和                
                next.e = i;//更新最后一个点                
                next.k = tmp.k + 1;//更新走过的顶点数                
                for (int j = 1; j <= n; j++) next.visited[j] = tmp.visited[j];//tmp经过的点也是next经过的点
                next.visited[i] = true;//自然也要更新当前点
                //cout << next.visited[i] << endl;
                next.lb = get_lb(next);//求目标函数
                //cout << next.lb << endl;
                if (next.lb > up) continue;//如果大于上界就不加入队列
                pq.push(next);//否则加入队列
                //cout << "test" << endl;
            }
        }
        //cout << pq.size() << endl;BUG:测试为0
    }
    return ret;
}
int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            cin >> cost[i][j];
            if (i == j)
            {
                cost[i][j] = INF;
            }
        }
    }
    cout << solve() << endl;
    return 0;
}

分支限界法拓展

第1关:装载问题 (FIFO 优先队列法)

#include <bits/stdc++.h>
using namespace std;
 
int N = 0;
 
template<class Type>
class QNode
{
	public:
		QNode *parent;	//指向父节点的指针
		bool LChild;    //左儿子标识
		Type weight;    //节点所相应的载重量
};
 
template<class Type>
void EnQueue(queue<QNode<Type>*>&Q,Type wt,int i,int n,Type bestw,QNode<Type>*E,QNode<Type> *&bestE,int bestx[],bool ch);
 
template<class Type>
Type MaxLoading(Type w[],Type c,int n,int bestx[]);
 
int main()
{
	float c = 0;  
    float w[100] = {0};//下标从1开始  
    int x[100];  
	float bestw;
	
	cin>>N;
	cin >>c;
	for(int i=1; i<=N; i++)  
    {  
        cin>>w[i];  
    }  
    cout<<"Ship load:"<<c<<endl;  
    cout<<"The weight of the goods to be loaded is:"<<endl;  
    for(int i=1; i<=N; i++)  
    {  
        cout<<w[i]<<" ";  
    }  
    cout<<endl;  
   
    bestw = MaxLoading(w,c,N,x);  
  
    cout<<"Result:"<<endl;  
    for(int i=1; i<=N; i++)  
    {  
        cout<<x[i]<<" ";  
    }  
    cout<<endl;  
	cout<<"The optimal loading weight is:"<<bestw<<endl;
  
    return 0;  
}
 
//将活节点加入到活节点队列Q中
template<class Type>
void EnQueue(queue<QNode<Type>*> &Q,Type wt,int i,int n,Type bestw,QNode<Type>*E,QNode<Type> *&bestE,int bestx[],bool ch)
{
	if(i == n)//可行叶节点
	{
		if(wt == bestw)
		{
			//当前最优装载重量
			bestE = E;
			bestx[n] = ch;			
		}
		return;
	}
	//非叶节点
	QNode<Type> *b;
	b = new QNode<Type>;
	b->weight = wt;
	b->parent = E;
	b->LChild = ch;
	Q.push(b);
}
 
template<class Type>
Type MaxLoading(Type w[],Type c,int n,int bestx[])
{//队列式分支限界法,返回最优装载重量,bestx返回最优解
 //初始化
	queue<QNode<Type>*> Q;		//活节点队列
	Q.push(0);					//同层节点尾部标识
	int i = 1;					//当前扩展节点所处的层
	Type Ew = 0,				//扩展节点所相应的载重量
		 bestw = 0,				//当前最优装载重量
		 r = 0;					//剩余集装箱重量
 
	for(int j=2; j<=n; j++)
	{
		r += w[j];
	}
	
	QNode<Type> *E = 0,			//当前扩展节点
				*bestE;			//当前最优扩展节点
 
	//搜索子集空间树
	//**************begin************/
	while(true)
    {
        Type wt=Ew+w[i];
        if(wt<=c)
        {
            if(wt>bestw)
                bestw=wt;
            EnQueue(Q,wt,i,n,bestw,E,bestE,bestx,true);
        }

        //检查右节点
        if(Ew+r>bestw)
            EnQueue(Q,Ew,i,n,bestw,E,bestE,bestx,false);
        E=Q.front();
        Q.pop();
        if(!E)
        {
            if(Q.empty())
                break;
            Q.push(0);
            E=Q.front();
            Q.pop();
            i++;
            r-=w[i];
        }
        Ew=E->weight;
    }
	
	//**************end**************/
 
	//构造当前最优解
	//**************begin************/
	for(int j=n-1;j>0;j--)
    {
        bestx[j]=bestE->LChild;
        bestE=bestE->parent;
    }
	
	//**************end**************/
	return bestw;
}

第2关:装载问题 (最优队列法) 

//装载问题 优先队列式分支限界法求解 
#include "MaxHeap.h"

int N;
 
class bbnode;
 
template<class Type>
class HeapNode
{

	public:
		operator Type() const{return uweight;}
		bbnode *ptr;		//指向活节点在子集树中相应节点的指针
		Type uweight;		//活节点优先级(上界)
		int level;			//活节点在子集树中所处的层序号
};
 
class bbnode
{
	template<class Type>
	friend void AddLiveNode(MaxHeap<HeapNode<Type> >& H,bbnode *E,Type wt,bool ch,int lev);
	template<class Type>
	friend Type MaxLoading(Type w[],Type c,int n,int bestx[]);
	friend class AdjacencyGraph;
 
	private:
		bbnode *parent;		//指向父节点的指针
		bool LChild;		//左儿子节点标识
};
 
template<class Type>
void AddLiveNode(MaxHeap<HeapNode<Type> >& H,bbnode *E,Type wt,bool ch,int lev);
 
template<class Type>
Type MaxLoading(Type w[],Type c,int n,int bestx[]);
 
 
int main()
{
	float c = 0;  
    float w[100] = {0};//下标从1开始  
    int x[100];  
	float bestw;
	
	cin>>N;
	cin >>c;
	for(int i=1; i<=N; i++)  
    {  
        cin>>w[i];  
    }  
    cout<<"Ship load:"<<c<<endl;  
    cout<<"The weight of the goods to be loaded is:"<<endl;  
    for(int i=1; i<=N; i++)  
    {  
        cout<<w[i]<<" ";  
    }  
    cout<<endl;  
	
    bestw = MaxLoading(w,c,N,x);  
  
    cout<<"Result:"<<endl; 
    for(int i=1; i<=N; i++)  
    {  
        cout<<x[i]<<" ";  
    }  
    cout<<endl;  
	cout<<"The optimal loading weight is:"<<bestw<<endl;
  
    return 0; 
}
 
//将活节点加入到表示活节点优先队列的最大堆H中
template<class Type>
void AddLiveNode(MaxHeap<HeapNode<Type> >& H,bbnode *E,Type wt,bool ch,int lev)
{
	bbnode *b = new bbnode;
	b->parent = E;
	b->LChild = ch;
	HeapNode<Type> N;
 
	N.uweight = wt;
	N.level = lev;
	N.ptr = b;
	H.Insert(N);
}
 
//优先队列式分支限界法,返回最优载重量,bestx返回最优解
template<class Type>
Type MaxLoading(Type w[],Type c,int n,int bestx[])
{
	//定义最大的容量为1000
	MaxHeap<HeapNode<Type> > H(1000);
 
	//定义剩余容量数组
	Type *r = new Type[n+1];
	r[n] = 0;
 
	//**************begin************/
	for(int j=n-1;j>0;j--)
		r[j]=r[j+1]+w[j+1];
	//**************end**************/
	
 
	//初始化
	int i = 1;//当前扩展节点所处的层
	bbnode *E = 0;//当前扩展节点
	Type Ew = 0; //扩展节点所相应的载重量
 
	//搜索子集空间树
	//**************begin************/
	
	while(i!=n+1)
	{
		if(Ew+w[i]<=c)
			AddLiveNode(H,E,Ew+w[i]+r[i],true,i+1);
		AddLiveNode(H,E,Ew+r[i],false,i+1);
		HeapNode<Type> N;
		H.DeleteMax(N);
		i=N.level;
		E=N.ptr;
		Ew=N.uweight-r[i-1];
	}
	
	//**************end**************/
 
	//构造当前最优解
	//**************begin************/
	for(int j=n;j>0;j--)
	{
		bestx[j]=E->LChild;
		E=E->parent;
	}
	
	//**************end**************/
 
	return Ew;
}

//4
//70
//20 10 26 15

 

#include #include #include #include using namespace std; ifstream infile; ofstream outfile; class Node { friend int func(int*, int, int, int*); public: int ID; double weight;//物品的重量 }; bool comp1(Node a, Node b) //定义比较规则 { return a.weight > b.weight; } class Load; class bbnode; class Current { friend Load; friend struct Comp2; private: int upweight;//重量上界 int weight;//结点相应的重量 int level;//活结点在子集树中所处的层次 bbnode* ptr;//指向活结点在子集树中相应结点的指针 }; struct Comp2 { bool operator () (Current *x, Current *y) { return x->upweightupweight; } }; class Load { friend int func(int*, int, int, int*); public: int Max0(); private: priority_queue<Current*, vector, Comp2>H;//利用优先队列(最大堆)储存 int limit(int i); void AddLiveNode(int up, int cw, bool ch, int level); bbnode *P;//指向扩展结点的指针 int c;//背包的容量 int n;//物品的数目 int *w;//重量数组 int cw;//当前装载量 int *bestx;//最优解方案数组 }; class bbnode { friend Load; friend int func( int*, int, int, int*); bbnode* parent; bool lchild; }; //结点中有双亲指针以及左儿子标志 int Load::limit(int i) //计算结点所相应重量的上界 { int left,a; left= c - cw;//剩余容量 a = cw; //b是重量上界,初始值为已经得到的重量 while (i <= n && w[i] parent = P; b->lchild = ch; Current* N = new Current; N->upweight = up; N->weight = cw; N->level = level; N->ptr = b; H.push(N); } int Load::Max0() { int i = 1; P = 0; cw = 0; int bestw = 0; int up = limit(1); while (i != n + 1) { int wt = cw + w[i]; //检查当前扩展结点的左儿子结点 if (wt bestw) bestw =wt; AddLiveNode(up,wt, true, i + 1); } up = limit(i + 1); //检查当前扩展结点的右儿子结点 if (up >= bestw)//如果右儿子可行 { AddLiveNode(up,cw, false, i + 1); } Current* N = H.top(); //取队头元素 H.pop(); P = N->ptr; cw = N->weight; up = N->upweight; i = N->level; } bestx = new int[n + 1]; for (int j = n; j > 0; --j) { bestx[j] = P->lchild; P = P->parent; } return cw; } int func(int *w, int c, int n, int *bestx) //调用Max0函数对子集树的优先队列式进行分支限界搜索 { int W = 0; //初始化装载的总质量为0 Node* Q = new Node[n]; for (int i = 0; i < n; ++i) { Q[i].ID = i + 1; Q[i].weight = w[i+1]; W += w[i+1]; } if (W <= c)//如果足够装,全部装入 return W; sort(Q, Q + n, comp1); //首先,将各物品按照重量从大到小进行排序; Load K; K.w = new int[n + 1]; for (int j = 0; j < n; j++) K.w[j + 1] = w[Q[j].ID]; K.cw = 0; K.c = c; K.n = n; int bestp = K.Max0(); for (int k = 0; k < n; k++) { bestx[Q[k].ID] = K.bestx[k + 1]; } delete []Q; delete []K.w; delete []K.bestx; return bestp; } int main() { int*w,*Final; int c,n,i,best; infile.open("input.txt",ios::in); if(!infile) { cerr<<"open error"<>c; infile>>n; w=new int[n+1]; for(i=1;i>w[i]; infile.close(); Final = new int[n+1]; best = func( w, c, n, Final); outfile.open("output.txt",ios::out); if(!outfile) { cerr<<"open error"<<endl; exit(1); } outfile << best << endl; for (int i = 1; i <= n; ++i) { outfile<<Final[i]<<" "; } outfile.close(); return 0; }
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值