1、分支限界法
(1)描述:采用广度优先产生状态空间树的结点,并使用剪枝函数的方法称为分枝限界法。
所谓“分支”是采用广度优先的策略,依次生成扩展结点的所有分支(即:儿子结点)。
所谓“限界”是在结点扩展过程中,计算结点的上界(或下界),边搜索边减掉搜索树的某些分支,从而提高搜索效率。
(2)原理:按照广度优先的原则,一个活结点一旦成为扩展结点(E-结点)R后,算法将依次生成它的全部孩子结点,将那些导致不可行解或导致非最优解的儿子舍弃,其余儿子加入活结点表中。然后,从活结点表中取出一个结点作为当前扩展结点。重复上述结点扩展过程,直至找到问题的解或判定无解为止。
(3)分支限界法与回溯法
1)求解目标:回溯法的求解目标是找出解空间树中满足约束条件的所有解,而分支限界法的求解目标则是找出满足约束条件的一个解,或是在满足约束条件的解中找出在某种意义下的最优解。
2)搜索方式的不同:回溯法以深度优先的方式搜索解空间树,而分支限界法则以广度优先或以最小耗费优先的方式搜索解空间树。
(4)常见的分支限界法
1)FIFO分支限界法(队列式分支限界法)
基本思想:按照队列先进先出(FIFO)原则选取下一个活结点为扩展结点。
搜索策略:一开始,根结点是唯一的活结点,根结点入队。从活结点队中取出根结点后,作为当前扩展结点。对当前扩展结点,先从左到右地产生它的所有儿子,用约束条件检查,把所有满足约束函数的儿子加入活结点队列中。再从活结点表中取出队首结点(队中最先进来的结点)为当前扩展结点,……,直到找到一个解或活结点队列为空为止。
2)LC(least cost)分支限界法(优先队列式分支限界法)
基本思想:为了加速搜索的进程,应采用有效地方式选择活结点进行扩展。按照优先队列中规定的优先级选取优先级最高的结点成为当前扩展结点。
搜索策略:对每一活结点计算一个优先级(某些信息的函数值),并根据这些优先级;从当前活结点表中优先选择一个优先级最高(最有利)的结点作为扩展结点,使搜索朝着解空间树上有最优解的分支推进,以便尽快地找出一个最优解。再从活结点表中下一个优先级别最高的结点为当前扩展结点,……,直到找到一个解或活结点队列为空为止。
(5)分支限界法搜索应用举例
1)0-1背包问题,当n=3时,w={16,15,15}, p={45,25,25}, c=30
队列式分支限界法(处理法则:先进先出):{}—>{A}—>{B,C}—>{C,D,E}(D是不可行解,舍弃)—>{C,E}—>{E,F,G}—>{F,G,J,K}(J是不可行解,舍弃)—>{F,G,K}—>{G,K,L,M}—>{K,L,M,N,O}—>{}
优先队列式分支限界法(处理法则:价值大者优先):{}—>{A}—>{B,C}—>{C,D,E}—>{C,E}—>{C,J,K}—>{C}—>{F,G}—>{G,L,M}—>{G,M}—>{G}—>{N,O}—>{O}—>{}
2)旅行员售货问题
队列式分支限界法(节点B开始):{ }—{B}—{C,D,E}—{D,E,F,G}—{E,F,G,H,I}—{F,G,H,I,J,K}—{G,H,I,J,K,L}—{H,I,J,K,L,M}—{I,J,K,L,M,N}—{J,K,L,M,N,O}—{K,L,M,N,O,P}—{L,M,N,O,P,Q}—{M,N,O,P,Q}—{N,O,P,Q}—{O,P,Q}—{P,Q}—{Q}—{ }
优先队列式分支限界法:优先级是结点的当前费用:{ }—{B}—{C,D,E}—{C,D,J,K}—{C,J,K,H,I}—{C,J,K,I,N}—{C,K,I,N,P}—{C,I,N,P,Q}—{C,N,P,Q,O}—{C,P,Q,O}—{C,Q,O}—{Q,O,F,G}—{Q,O,G,L}—{Q,O,L,M}—{O,L,M}—{O,M}—{M}—{ }
2、单源最短路径问题
问题描述
在下图所给的有向图G中,每一边都有一个非负边权。要求图G的从源顶点s到目标顶点t之间的最短路径。
下图是用优先队列式分支限界法解有向图G的单源最短路径问题产生的解空间树。其中,每一个结点旁边的数字表示该结点所对应的当前路长。
算法设计
算法从图G的源顶点s和空优先队列开始。结点s被扩展后,它的儿子结点被依次插入堆中。此后,算法从堆中取出具有最小当前路长的结点作为当前扩展结点,并依次检查与当前扩展结点相邻的所有顶点。如果从当前扩展结点i到顶点j有边可达,且从源出发,途经顶点i再到顶点j的所相应的路径的长度小于当前最优路径长度,则将该顶点作为活结点插入到活结点优先队列中。这个结点的扩展过程一直继续到活结点优先队列为空时为止。
在算法扩展结点的过程中,一旦发现一个结点的下界不小于当前找到的最短路长,则算法剪去以该结点为根的子树。
在算法中,利用结点间的控制关系进行剪枝。从源顶点s出发,2条不同路径到达图G的同一顶点。由于两条路径的路长不同,因此可以将路长长的路径所对应的树中的结点为根的子树剪去。
算法具体代码如下:
1、MinHeap2.h
- #include <iostream>
- template<class Type>
- class Graph;
- template<class T>
- class MinHeap
- {
- template<class Type>
- friend class Graph;
- public:
- MinHeap(int maxheapsize = 10);
- ~MinHeap(){delete []heap;}
- int Size() const{return currentsize;}
- T Max(){if(currentsize) return heap[1];}
- MinHeap<T>& Insert(const T& x);
- MinHeap<T>& DeleteMin(T &x);
- void Initialize(T x[], int size, int ArraySize);
- void Deactivate();
- void output(T a[],int n);
- private:
- int currentsize, maxsize;
- T *heap;
- };
- template <class T>
- void MinHeap<T>::output(T a[],int n)
- {
- for(int i = 1; i <= n; i++)
- cout << a[i] << " ";
- cout << endl;
- }
- template <class T>
- MinHeap<T>::MinHeap(int maxheapsize)
- {
- maxsize = maxheapsize;
- heap = new T[maxsize + 1];
- currentsize = 0;
- }
- template<class T>
- MinHeap<T>& MinHeap<T>::Insert(const T& x)
- {
- if(currentsize == maxsize)
- {
- return *this;
- }
- int i = ++currentsize;
- while(i != 1 && x < heap[i/2])
- {
- heap[i] = heap[i/2];
- i /= 2;
- }
- heap[i] = x;
- return *this;
- }
- template<class T>
- MinHeap<T>& MinHeap<T>::DeleteMin(T& x)
- {
- if(currentsize == 0)
- {
- cout<<"Empty heap!"<<endl;
- return *this;
- }
- x = heap[1];
- T y = heap[currentsize--];
- int i = 1, ci = 2;
- while(ci <= currentsize)
- {
- if(ci < currentsize && heap[ci] > heap[ci + 1])
- {
- ci++;
- }
- if(y <= heap[ci])
- {
- break;
- }
- heap[i] = heap[ci];
- i = ci;
- ci *= 2;
- }
- heap[i] = y;
- return *this;
- }
- template<class T>
- void MinHeap<T>::Initialize(T x[], int size, int ArraySize)
- {
- delete []heap;
- heap = x;
- currentsize = size;
- maxsize = ArraySize;
- for(int i = currentsize / 2; i >= 1; i--)
- {
- T y = heap[i];
- int c = 2 * i;
- while(c <= currentsize)
- {
- if(c < currentsize && heap[c] > heap[c + 1])
- c++;
- if(y <= heap[c])
- break;
- heap[c / 2] = heap[c];
- c *= 2;
- }
- heap[c / 2] = y;
- }
- }
- template<class T>
- void MinHeap<T>::Deactivate()
- {
- heap = 0;
- }
- //单源最短路径问题 分支 限界法求解
- #include "stdafx.h"
- #include "MinHeap2.h"
- #include <iostream>
- #include <fstream>
- using namespace std;
- ifstream fin("6d2.txt");
- template<class Type>
- class Graph
- {
- friend int main();
- public:
- void ShortesPaths(int);
- private:
- int n, //图G的顶点数
- *prev; //前驱顶点数组
- Type **c, //图G的领接矩阵
- *dist; //最短距离数组
- };
- template<class Type>
- class MinHeapNode
- {
- friend Graph<Type>;
- public:
- operator int ()const{return length;}
- private:
- int i; //顶点编号
- Type length; //当前路长
- };
- template<class Type>
- void Graph<Type>::ShortesPaths(int v)//单源最短路径问题的优先队列式分支限界法
- {
- MinHeap<MinHeapNode<Type>> H(1000);
- MinHeapNode<Type> E;
- //定义源为初始扩展节点
- E.i=v;
- E.length=0;
- dist[v]=0;
- while (true)//搜索问题的解空间
- {
- for (int j = 1; j <= n; j++)
- if ((c[E.i][j]!=0)&&(E.length+c[E.i][j]<dist[j])) {
- // 顶点i到顶点j可达,且满足控制约束
- dist[j]=E.length+c[E.i][j];
- prev[j]=E.i;
- // 加入活结点优先队列
- MinHeapNode<Type> N;
- N.i=j;
- N.length=dist[j];
- H.Insert(N);
- }`
- try
- {
- H.DeleteMin(E); // 取下一扩展结点
- }
- catch (int)
- {
- break;
- }
- if (H.currentsize==0)// 优先队列空
- {
- break;
- }
- }
- }
- int main()
- {
- int n=11;
- int prev[12] = {0,0,0,0,0,0,0,0,0,0,0,0};
- int dist[12]={1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000};
- cout<<"单源图的邻接矩阵如下:"<<endl;
- int **c = new int*[n+1];
- for(int i=1;i<=n;i++)
- {
- c[i]=new int[n+1];
- for(int j=1; j<=n; j++)
- {
- fin>>c[i][j];
- cout<<c[i][j]<<" ";
- }
- cout<<endl;
- }
- int v=1;
- Graph<int> G;
- G.n=n;
- G.c=c;
- G.dist=dist;
- G.prev=prev;
- G.ShortesPaths(v);
- cout<<"从S到T的最短路长是:"<<dist[11]<<endl;
- for (int i = 2; i <= n; i++)
- {
- cout<<"prev("<<i<<")="<<prev[i]<<" "<<endl;
- }
- for (int i = 2; i <= n; i++)
- {
- cout<<"从1到"<<i<<"的最短路长是:"<<dist[i]<<endl;
- }
- for(int i=1;i<=n;i++)
- {
- delete []c[i];
- }
- delete []c;
- c=0;
- return 0;
- }
问题描述
有一批共个集装箱要装上2艘载重量分别为C1和C2的轮船,其中集装箱i的重量为Wi,且装载问题要求确定是否有一个合理的装载方案可将这个集装箱装上这2艘轮船。如果有,找出一种装载方案。
容易证明:如果一个给定装载问题有解,则采用下面的策略可得到最优装载方案。
(1)首先将第一艘轮船尽可能装满;
(2)将剩余的集装箱装上第二艘轮船。
1、队列式分支限界法求解
在算法的循环体中,首先检测当前扩展结点的左儿子结点是否为可行结点。如果是则将其加入到活结点队列中。然后将其右儿子结点加入到活结点队列中(右儿子结点一定是可行结点)。2个儿子结点都产生后,当前扩展结点被舍弃。
活结点队列中的队首元素被取出作为当前扩展结点,由于队列中每一层结点之后都有一个尾部标记-1,故在取队首元素时,活结点队列一定不空。当取出的元素是-1时,再判断当前队列是否为空。如果队列非空,则将尾部标记-1加入活结点队列,算法开始处理下一层的活结点。
节点的左子树表示将此集装箱装上船,右子树表示不将此集装箱装上船。设bestw是当前最优解;ew是当前扩展结点所相应的重量;r是剩余集装箱的重量。则当ew+r<bestw时,可将其右子树剪去,因为此时若要船装最多集装箱,就应该把此箱装上船。另外,为了确保右子树成功剪枝,应该在算法每一次进入左子树的时候更新bestw的值。
为了在算法结束后能方便地构造出与最优值相应的最优解,算法必须存储相应子集树中从活结点到根结点的路径。为此目的,可在每个结点处设置指向其父结点的指针,并设置左、右儿子标志。
找到最优值后,可以根据parent回溯到根节点,找到最优解。
算法具体代码实现如下:
1、Queue.h
- #include<iostream>
- using namespace std;
- template <class T>
- class Queue
- {
- public:
- Queue(int MaxQueueSize=50);
- ~Queue(){delete [] queue;}
- bool IsEmpty()const{return front==rear;}
- bool IsFull(){return ( ( (rear+1) %MaxSize==front )?1:0);}
- T Top() const;
- T Last() const;
- Queue<T>& Add(const T& x);
- Queue<T>& AddLeft(const T& x);
- Queue<T>& Delete(T &x);
- void Output(ostream& out)const;
- int Length(){return (rear-front);}
- private:
- int front;
- int rear;
- int MaxSize;
- T *queue;
- };
- template<class T>
- Queue<T>::Queue(int MaxQueueSize)
- {
- MaxSize=MaxQueueSize+1;
- queue=new T[MaxSize];
- front=rear=0;
- }
- template<class T >
- T Queue<T>::Top()const
- {
- if(IsEmpty())
- {
- cout<<"queue:no element,no!"<<endl;
- return 0;
- }
- else return queue[(front+1) % MaxSize];
- }
- template<class T>
- T Queue<T> ::Last()const
- {
- if(IsEmpty())
- {
- cout<<"queue:no element"<<endl;
- return 0;
- }
- else return queue[rear];
- }
- template<class T>
- Queue<T>& Queue<T>::Add(const T& x)
- {
- if(IsFull())cout<<"queue:no memory"<<endl;
- else
- {
- rear=(rear+1)% MaxSize;
- queue[rear]=x;
- }
- return *this;
- }
- template<class T>
- Queue<T>& Queue<T>::AddLeft(const T& x)
- {
- if(IsFull())cout<<"queue:no memory"<<endl;
- else
- {
- front=(front+MaxSize-1)% MaxSize;
- queue[(front+1)% MaxSize]=x;
- }
- return *this;
- }
- template<class T>
- Queue<T>& Queue<T> ::Delete(T & x)
- {
- if(IsEmpty())cout<<"queue:no element(delete)"<<endl;
- else
- {
- front=(front+1) % MaxSize;
- x=queue[front];
- }
- return *this;
- }
- template<class T>
- void Queue <T>::Output(ostream& out)const
- {
- for(int i=rear%MaxSize;i>=(front+1)%MaxSize;i--)
- out<<queue[i];
- }
- template<class T>
- ostream& operator << (ostream& out,const Queue<T>& x)
- {x.Output(out);return out;}
- //装载问题 队列式分支限界法求解
- #include "stdafx.h"
- #include "Queue.h"
- #include <iostream>
- using namespace std;
- const int N = 4;
- template<class Type>
- class QNode
- {
- template<class Type>
- friend 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>
- friend Type MaxLoading(Type w[],Type c,int n,int bestx[]);
- private:
- 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 = 70;
- float w[] = {0,20,10,26,15};//下标从1开始
- int x[N+1];
- float bestw;
- cout<<"轮船载重为:"<<c<<endl;
- cout<<"待装物品的重量分别为:"<<endl;
- for(int i=1; i<=N; i++)
- {
- cout<<w[i]<<" ";
- }
- cout<<endl;
- bestw = MaxLoading(w,c,N,x);
- cout<<"分支限界选择结果为:"<<endl;
- for(int i=1; i<=4; i++)
- {
- cout<<x[i]<<" ";
- }
- cout<<endl;
- cout<<"最优装载重量为:"<<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.Add(b);
- }
- template<class Type>
- Type MaxLoading(Type w[],Type c,int n,int bestx[])
- {//队列式分支限界法,返回最优装载重量,bestx返回最优解
- //初始化
- Queue<QNode<Type>*> Q; //活节点队列
- Q.Add(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; //当前最优扩展节点
- //搜索子集空间树
- 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);
- }
- Q.Delete(E);//取下一扩展节点
- if(!E)//同层节点尾部
- {
- if(Q.IsEmpty())
- {
- break;
- }
- Q.Add(0); //同层节点尾部标识
- Q.Delete(E); //取下一扩展节点
- i++; //进入下一层
- r-=w[i]; //剩余集装箱重量
- }
- Ew =E->weight; //新扩展节点所对应的载重量
- }
- //构造当前最优解
- for(int j=n-1; j>0; j--)
- {
- bestx[j] = bestE->LChild;
- bestE = bestE->parent;
- }
- return bestw;
- }
2、优先队列式分支限界法求解
解装载问题的优先队列式分支限界法用最大优先队列存储活结点表。活结点x在优先队列中的优先级定义为从根结点到结点x的路径所相应的载重量再加上剩余集装箱的重量之和。
优先队列中优先级最大的活结点成为下一个扩展结点。以结点x为根的子树中所有结点相应的路径的载重量不超过它的优先级。子集树中叶结点所相应的载重量与其优先级相同。
在优先队列式分支限界法中,一旦有一个叶结点成为当前扩展结点,则可以断言该叶结点所相应的解即为最优解。此时可终止算法。
算法具体代码实现如下:
1、MaxHeap.h
- template<class T>
- class MaxHeap
- {
- public:
- MaxHeap(int MaxHeapSize = 10);
- ~MaxHeap() {delete [] heap;}
- int Size() const {return CurrentSize;}
- T Max()
- { //查
- if (CurrentSize == 0)
- {
- throw OutOfBounds();
- }
- return heap[1];
- }
- MaxHeap<T>& Insert(const T& x); //增
- MaxHeap<T>& DeleteMax(T& x); //删
- void Initialize(T a[], int size, int ArraySize);
- private:
- int CurrentSize, MaxSize;
- T *heap; // element array
- };
- template<class T>
- MaxHeap<T>::MaxHeap(int MaxHeapSize)
- {// Max heap constructor.
- MaxSize = MaxHeapSize;
- heap = new T[MaxSize+1];
- CurrentSize = 0;
- }
- template<class T>
- MaxHeap<T>& MaxHeap<T>::Insert(const T& x)
- {// Insert x into the max heap.
- if (CurrentSize == MaxSize)
- {
- cout<<"no space!"<<endl;
- return *this;
- }
- // 寻找新元素x的位置
- // i——初始为新叶节点的位置,逐层向上,寻找最终位置
- int i = ++CurrentSize;
- while (i != 1 && x > heap[i/2])
- {
- // i不是根节点,且其值大于父节点的值,需要继续调整
- heap[i] = heap[i/2]; // 父节点下降
- i /= 2; // 继续向上,搜寻正确位置
- }
- heap[i] = x;
- return *this;
- }
- template<class T>
- MaxHeap<T>& MaxHeap<T>::DeleteMax(T& x)
- {// Set x to max element and delete max element from heap.
- // check if heap is empty
- if (CurrentSize == 0)
- {
- cout<<"Empty heap!"<<endl;
- return *this;
- }
- x = heap[1]; // 删除最大元素
- // 重整堆
- T y = heap[CurrentSize--]; // 取最后一个节点,从根开始重整
- // find place for y starting at root
- int i = 1, // current node of heap
- ci = 2; // child of i
- while (ci <= CurrentSize)
- {
- // 使ci指向i的两个孩子中较大者
- if (ci < CurrentSize && heap[ci] < heap[ci+1])
- {
- ci++;
- }
- // y的值大于等于孩子节点吗?
- if (y >= heap[ci])
- {
- break; // 是,i就是y的正确位置,退出
- }
- // 否,需要继续向下,重整堆
- heap[i] = heap[ci]; // 大于父节点的孩子节点上升
- i = ci; // 向下一层,继续搜索正确位置
- ci *= 2;
- }
- heap[i] = y;
- return *this;
- }
- template<class T>
- void MaxHeap<T>::Initialize(T a[], int size,int ArraySize)
- {// Initialize max heap to array a.
- delete [] heap;
- heap = a;
- CurrentSize = size;
- MaxSize = ArraySize;
- // 从最后一个内部节点开始,一直到根,对每个子树进行堆重整
- for (int i = CurrentSize/2; i >= 1; i--)
- {
- T y = heap[i]; // 子树根节点元素
- // find place to put y
- int c = 2*i; // parent of c is target
- // location for y
- while (c <= CurrentSize)
- {
- // heap[c] should be larger sibling
- if (c < CurrentSize && heap[c] < heap[c+1])
- {
- c++;
- }
- // can we put y in heap[c/2]?
- if (y >= heap[c])
- {
- break; // yes
- }
- // no
- heap[c/2] = heap[c]; // move child up
- c *= 2; // move down a level
- }
- heap[c/2] = y;
- }
- }
- //装载问题 优先队列式分支限界法求解
- #include "stdafx.h"
- #include "MaxHeap.h"
- #include <iostream>
- using namespace std;
- const int N = 4;
- class bbnode;
- template<class Type>
- class HeapNode
- {
- 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[]);
- public:
- operator Type() const{return uweight;}
- private:
- 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 = 70;
- float w[] = {0,20,10,26,15};//下标从1开始
- int x[N+1];
- float bestw;
- cout<<"轮船载重为:"<<c<<endl;
- cout<<"待装物品的重量分别为:"<<endl;
- for(int i=1; i<=N; i++)
- {
- cout<<w[i]<<" ";
- }
- cout<<endl;
- bestw = MaxLoading(w,c,N,x);
- cout<<"分支限界选择结果为:"<<endl;
- for(int i=1; i<=4; i++)
- {
- cout<<x[i]<<" ";
- }
- cout<<endl;
- cout<<"最优装载重量为:"<<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;
- for(int j=n-1; j>0; j--)
- {
- r[j] = r[j+1] + w[j+1];
- }
- //初始化
- int i = 1;//当前扩展节点所处的层
- bbnode *E = 0;//当前扩展节点
- Type Ew = 0; //扩展节点所相应的载重量
- //搜索子集空间树
- 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];
- }
- //构造当前最优解
- for(int j=n; j>0; j--)
- {
- bestx[j] = E->LChild;
- E = E->parent;
- }
- return Ew;
- }
问题描述
给定无向图G=(V, E),其中V是非空集合,称为顶点集;E是V中元素构成的无序二元组的集合,称为边集,无向图中的边均是顶点的无序对,无序对常用圆括号“( )”表示。如果U∈V,且对任意两个顶点u,v∈U有(u, v)∈E,则称U是G的完全子图(完全图G就是指图G的每个顶点之间都有连边)。G的完全子图U是G的团当且仅当U不包含在G的更大的完全子图中。G的最大团是指G中所含顶点数最多的团。
如果U∈V且对任意u,v∈U有(u, v)不属于E,则称U是G的空子图。G的空子图U是G的独立集当且仅当U不包含在G的更大的空子图中。G的最大独立集是G中所含顶点数最多的独立集。
对于任一无向图G=(V, E),其补图G'=(V', E')定义为:V'=V,且(u, v)∈E'当且仅当(u, v)∈E。
如果U是G的完全子图,则它也是G'的空子图,反之亦然。因此,G的团与G'的独立集之间存在一一对应的关系。特殊地,U是G的最大团当且仅当U是G'的最大独立集。
例:如图所示,给定无向图G={V, E},其中V={1,2,3,4,5},E={(1,2), (1,4), (1,5),(2,3), (2,5), (3,5), (4,5)}。根据最大团(MCP)定义,子集{1,2}是图G的一个大小为2的完全子图,但不是一个团,因为它包含于G的更大的完全子图{1,2,5}之中。{1,2,5}是G的一个最大团。{1,4,5}和{2,3,5}也是G的最大团。右侧图是无向图G的补图G'。根据最大独立集定义,{2,4}是G的一个空子图,同时也是G的一个最大独立集。虽然{1,2}也是G'的空子图,但它不是G'的独立集,因为它包含在G'的空子图{1,2,5}中。{1,2,5}是G'的最大独立集。{1,4,5}和{2,3,5}也是G'的最大独立集。
算法设计
最大团问题的解空间树也是一棵子集树。子集树的根结点是初始扩展结点,对于这个特殊的扩展结点,其cliqueSize的值为0。 算法在扩展内部结点时,首先考察其左儿子结点。在左儿子结点处,将顶点i加入到当前团中,并检查该顶点与当前团中其它顶点之间是否有边相连。当顶点i与当前团中所有顶点之间都有边相连,则相应的左儿子结点是可行结点,将它加入到子集树中并插入活结点优先队列,否则就不是可行结点。
接着继续考察当前扩展结点的右儿子结点。当upperSize>bestn时,右子树中可能含有最优解,此时将右儿子结点加入到子集树中并插入到活结点优先队列中。算法的while循环的终止条件是遇到子集树中的一个叶结点(即n+1层结点)成为当前扩展结点。
对于子集树中的叶结点,有upperSize=cliqueSize。此时活结点优先队列中剩余结点的upperSize值均不超过当前扩展结点的upperSize值,从而进一步搜索不可能得到更大的团,此时算法已找到一个最优解。
算法具体实现如下:
1、MaxHeap.h
- template<class T>
- class MaxHeap
- {
- public:
- MaxHeap(int MaxHeapSize = 10);
- ~MaxHeap() {delete [] heap;}
- int Size() const {return CurrentSize;}
- T Max()
- { //查
- if (CurrentSize == 0)
- {
- throw OutOfBounds();
- }
- return heap[1];
- }
- MaxHeap<T>& Insert(const T& x); //增
- MaxHeap<T>& DeleteMax(T& x); //删
- void Initialize(T a[], int size, int ArraySize);
- private:
- int CurrentSize, MaxSize;
- T *heap; // element array
- };
- template<class T>
- MaxHeap<T>::MaxHeap(int MaxHeapSize)
- {// Max heap constructor.
- MaxSize = MaxHeapSize;
- heap = new T[MaxSize+1];
- CurrentSize = 0;
- }
- template<class T>
- MaxHeap<T>& MaxHeap<T>::Insert(const T& x)
- {// Insert x into the max heap.
- if (CurrentSize == MaxSize)
- {
- cout<<"no space!"<<endl;
- return *this;
- }
- // 寻找新元素x的位置
- // i——初始为新叶节点的位置,逐层向上,寻找最终位置
- int i = ++CurrentSize;
- while (i != 1 && x > heap[i/2])
- {
- // i不是根节点,且其值大于父节点的值,需要继续调整
- heap[i] = heap[i/2]; // 父节点下降
- i /= 2; // 继续向上,搜寻正确位置
- }
- heap[i] = x;
- return *this;
- }
- template<class T>
- MaxHeap<T>& MaxHeap<T>::DeleteMax(T& x)
- {// Set x to max element and delete max element from heap.
- // check if heap is empty
- if (CurrentSize == 0)
- {
- cout<<"Empty heap!"<<endl;
- return *this;
- }
- x = heap[1]; // 删除最大元素
- // 重整堆
- T y = heap[CurrentSize--]; // 取最后一个节点,从根开始重整
- // find place for y starting at root
- int i = 1, // current node of heap
- ci = 2; // child of i
- while (ci <= CurrentSize)
- {
- // 使ci指向i的两个孩子中较大者
- if (ci < CurrentSize && heap[ci] < heap[ci+1])
- {
- ci++;
- }
- // y的值大于等于孩子节点吗?
- if (y >= heap[ci])
- {
- break; // 是,i就是y的正确位置,退出
- }
- // 否,需要继续向下,重整堆
- heap[i] = heap[ci]; // 大于父节点的孩子节点上升
- i = ci; // 向下一层,继续搜索正确位置
- ci *= 2;
- }
- heap[i] = y;
- return *this;
- }
- template<class T>
- void MaxHeap<T>::Initialize(T a[], int size,int ArraySize)
- {// Initialize max heap to array a.
- delete [] heap;
- heap = a;
- CurrentSize = size;
- MaxSize = ArraySize;
- // 从最后一个内部节点开始,一直到根,对每个子树进行堆重整
- for (int i = CurrentSize/2; i >= 1; i--)
- {
- T y = heap[i]; // 子树根节点元素
- // find place to put y
- int c = 2*i; // parent of c is target
- // location for y
- while (c <= CurrentSize)
- {
- // heap[c] should be larger sibling
- if (c < CurrentSize && heap[c] < heap[c+1])
- {
- c++;
- }
- // can we put y in heap[c/2]?
- if (y >= heap[c])
- {
- break; // yes
- }
- // no
- heap[c/2] = heap[c]; // move child up
- c *= 2; // move down a level
- }
- heap[c/2] = y;
- }
- }
- //最大团问题 优先队列分支限界法求解
- #include "stdafx.h"
- #include "MaxHeap.h"
- #include <iostream>
- #include <fstream>
- using namespace std;
- const int N = 5;//图G的顶点数
- ifstream fin("6d6.txt");
- class bbnode
- {
- friend class Clique;
- private:
- bbnode *parent; //指向父节点的指针
- bool LChild; //左儿子节点标识
- };
- class CliqueNode
- {
- friend class Clique;
- public:
- operator int() const
- {
- return un;
- }
- private:
- int cn, //当前团的顶点数
- un, //当前团最大顶点数的上界
- level; //节点在子集空间树中所处的层次
- bbnode *ptr; //指向活节点在子集树中相应节点的指针
- };
- class Clique
- {
- friend int main(void);
- public:
- int BBMaxClique(int []);
- private:
- void AddLiveNode(MaxHeap<CliqueNode>&H,int cn,int un,int level,bbnode E[],bool ch);
- int **a, //图G的邻接矩阵
- n; //图G的顶点数
- };
- int main()
- {
- int bestx[N+1];
- int **a = new int *[N+1];
- for(int i=1;i<=N;i++)
- {
- a[i] = new int[N+1];
- }
- cout<<"图G的邻接矩阵为:"<<endl;
- for(int i=1; i<=N; i++)
- {
- for(int j=1; j<=N; j++)
- {
- fin>>a[i][j];
- cout<<a[i][j]<<" ";
- }
- cout<<endl;
- }
- Clique c;
- c.a = a;
- c.n = N;
- cout<<"图G的最大团顶点个数为:"<<c.BBMaxClique(bestx)<<endl;
- cout<<"图G的最大团解向量为:"<<endl;
- for(int i=1;i<=N;i++)
- {
- cout<<bestx[i]<<" ";
- }
- cout<<endl;
- for(int i=1;i<=N;i++)
- {
- delete[] a[i];
- }
- delete []a;
- return 0;
- }
- //将活节点加入到子集空间树中并插入到最大堆中
- void Clique::AddLiveNode(MaxHeap<CliqueNode> &H, int cn, int un, int level, bbnode E[], bool ch)
- {
- bbnode *b = new bbnode;
- b->parent = E;
- b->LChild = ch;
- CliqueNode N;
- N.cn = cn;
- N.ptr = b;
- N.un = un;
- N.level = level;
- H.Insert(N);
- }
- //解最大团问题的优先队列式分支限界法
- int Clique::BBMaxClique(int bestx[])
- {
- MaxHeap<CliqueNode> H(1000);
- //初始化
- bbnode *E = 0;
- int i = 1,
- cn = 0,
- bestn = 0;
- //搜集子集空间树
- while(i!=n+1)//非叶节点
- {
- //检查顶点i与当前团中其他顶点之间是否有边相连
- bool OK = true;
- bbnode *B = E;
- for(int j=i-1; j>0; B=B->parent,j--)
- {
- if(B->LChild && a[i][j]==0)
- {
- OK = false;
- break;
- }
- }
- if(OK)//左儿子节点为可行结点
- {
- if(cn+1>bestn)
- {
- bestn = cn + 1;
- }
- AddLiveNode(H,cn+1,cn+n-i+1,i+1,E,true);
- }
- if(cn+n-i>=bestn)//右子树可能含有最优解
- {
- AddLiveNode(H,cn,cn+n-i,i+1,E,false);
- }
- //取下一扩展节点
- CliqueNode N;
- H.DeleteMax(N); //堆非空
- E = N.ptr;
- cn = N.cn;
- i = N.level;
- }
- //构造当前最优解
- for(int j=n; j>0; j--)
- {
- bestx[j] = E->LChild;
- E = E->parent;
- }
- return bestn;
- }
问题描述
将n块电路板以最佳排列方式插入带有n个插槽的机箱中。n块电路板的不同排列方式对应于不同的电路板插入方案。设B={1, 2, …, n}是n块电路板的集合,L={N1, N2, …, Nm}是连接这n块电路板中若干电路板的m个连接块。Ni是B的一个子集,且Ni中的电路板用同一条导线连接在一起。设x表示n块电路板的一个排列,即在机箱的第i个插槽中插入的电路板编号是x[i]。x所确定的电路板排列Density (x)密度定义为跨越相邻电路板插槽的最大连线数。
例:如图,设n=8, m=5,给定n块电路板及其m个连接块:B={1, 2, 3, 4, 5, 6, 7, 8},N1={4, 5, 6},N2={2, 3},N3={1, 3},N4={3, 6},N5={7, 8};其中两个可能的排列如图所示,则该电路板排列的密度分别是2,3。
左上图中,跨越插槽2和3,4和5,以及插槽5和6的连线数均为2。插槽6和7之间无跨越连线。其余插槽之间只有1条跨越连线。在设计机箱时,插槽一侧的布线间隙由电路板的排列的密度确定。因此,电路板排列问题要求对于给定的电路板连接条件(连接块),确定电路板的最佳排列,使其具有最小密度。
算法思路
电路板排列问题的解空间是一颗排列树。采用优先队列式分支限界法找出所给电路板的最小密度布局。算法中采用最小堆表示活节点优先级队列。最小堆中元素类型是BoradNode,每一个BoardNode类型的节点包含域x,表示节点所相应的电路板排列;s表示该节点已确定的电路板排列x[1:s];cd表示当前密度,now[j]表示x[1:s]中所含连接块j中的电路板数。
算法开始时,将排列树的根结点置为当前扩展结点。在do-while循环体内算法依次从活结点优先队列中取出具有最小cd值的结点作为当前扩展结点,并加以扩展。算法将当前扩展节点分两种情形处理:
1)首先考虑s=n-1的情形,当前扩展结点是排列树中的一个叶结点的父结点。x表示相应于该叶结点的电路板排列。计算出与x相应的密度并在必要时更新当前最优值和相应的当前最优解。
2)当s<n-1时,算法依次产生当前扩展结点的所有儿子结点。对于当前扩展结点的每一个儿子结点node,计算出其相应的密度node.cd。当node.cd<bestd时,将该儿子结点N插入到活结点优先队列中。
算法具体实现如下:
1、MinHeap2.h
- #include <iostream>
- template<class Type>
- class Graph;
- template<class T>
- class MinHeap
- {
- template<class Type>
- friend class Graph;
- public:
- MinHeap(int maxheapsize = 10);
- ~MinHeap(){delete []heap;}
- int Size() const{return currentsize;}
- T Max(){if(currentsize) return heap[1];}
- MinHeap<T>& Insert(const T& x);
- MinHeap<T>& DeleteMin(T &x);
- void Initialize(T x[], int size, int ArraySize);
- void Deactivate();
- void output(T a[],int n);
- private:
- int currentsize, maxsize;
- T *heap;
- };
- template <class T>
- void MinHeap<T>::output(T a[],int n)
- {
- for(int i = 1; i <= n; i++)
- cout << a[i] << " ";
- cout << endl;
- }
- template <class T>
- MinHeap<T>::MinHeap(int maxheapsize)
- {
- maxsize = maxheapsize;
- heap = new T[maxsize + 1];
- currentsize = 0;
- }
- template<class T>
- MinHeap<T>& MinHeap<T>::Insert(const T& x)
- {
- if(currentsize == maxsize)
- {
- return *this;
- }
- int i = ++currentsize;
- while(i != 1 && x < heap[i/2])
- {
- heap[i] = heap[i/2];
- i /= 2;
- }
- heap[i] = x;
- return *this;
- }
- template<class T>
- MinHeap<T>& MinHeap<T>::DeleteMin(T& x)
- {
- if(currentsize == 0)
- {
- cout<<"Empty heap!"<<endl;
- return *this;
- }
- x = heap[1];
- T y = heap[currentsize--];
- int i = 1, ci = 2;
- while(ci <= currentsize)
- {
- if(ci < currentsize && heap[ci] > heap[ci + 1])
- {
- ci++;
- }
- if(y <= heap[ci])
- {
- break;
- }
- heap[i] = heap[ci];
- i = ci;
- ci *= 2;
- }
- heap[i] = y;
- return *this;
- }
- template<class T>
- void MinHeap<T>::Initialize(T x[], int size, int ArraySize)
- {
- delete []heap;
- heap = x;
- currentsize = size;
- maxsize = ArraySize;
- for(int i = currentsize / 2; i >= 1; i--)
- {
- T y = heap[i];
- int c = 2 * i;
- while(c <= currentsize)
- {
- if(c < currentsize && heap[c] > heap[c + 1])
- c++;
- if(y <= heap[c])
- break;
- heap[c / 2] = heap[c];
- c *= 2;
- }
- heap[c / 2] = y;
- }
- }
- template<class T>
- void MinHeap<T>::Deactivate()
- {
- heap = 0;
- }
- //电路板排列问题 优先队列分支限界法求解
- #include "stdafx.h"
- #include "MinHeap2.h"
- #include <iostream>
- #include <fstream>
- using namespace std;
- ifstream fin("6d8.txt");
- class BoardNode
- {
- friend int BBArrangement(int **,int,int,int *&);
- public:
- operator int() const
- {
- return cd;
- }
- private:
- int *x, //x[1:n]记录电路板排列
- s, //x[1:s]是当前节点所相应的部分排列
- cd, //x[1:s]的密度
- *now; //now[j]是x[1:s]所含连接块j中电路板数
- };
- int BBArrangement(int **B,int n,int m,int *&bestx);
- int main()
- {
- int m = 5,n = 8;
- int *bestx;
- //B={1,2,3,4,5,6,7,8}
- //N1={4,5,6},N2={2,3},N3={1,3},N4={3,6},N5={7,8}
- cout<<"m="<<m<<",n="<<n<<endl;
- cout<<"N1={4,5,6},N2={2,3},N3={1,3},N4={3,6},N5={7,8}"<<endl;
- cout<<"二维数组B如下:"<<endl;
- //构造B
- int **B = new int*[n+1];
- for(int i=1; i<=n; i++)
- {
- B[i] = new int[m+1];
- }
- for(int i=1; i<=n; i++)
- {
- for(int j=1; j<=m ;j++)
- {
- fin>>B[i][j];
- cout<<B[i][j]<<" ";
- }
- cout<<endl;
- }
- cout<<"当前最优密度为:"<<BBArrangement(B,n,m,bestx)<<endl;
- cout<<"最优排列为:"<<endl;
- for(int i=1; i<=n; i++)
- {
- cout<<bestx[i]<<" ";
- }
- cout<<endl;
- for(int i=1; i<=n; i++)
- {
- delete[] B[i];
- }
- delete[] B;
- return 0;
- }
- //解电路板排列问题的优先队列式分支限界法
- int BBArrangement(int **B,int n,int m,int *&bestx)
- {
- MinHeap<BoardNode> H(1000);//活节点最小堆
- BoardNode E;
- E.x = new int[n+1];
- E.s = 0;
- E.cd = 0;
- E.now = new int[m+1];
- int *total = new int[m+1];
- //now[i] = x[1:s]所含连接块i中电路板数
- //total[i] = 连接块i中的电路板数
- for(int i=1; i<=m; i++)
- {
- total[i] = 0;
- E.now[i] = 0;
- }
- for(int i=1; i<=n; i++)
- {
- E.x[i] = i;//初始排列为1,2,3……n
- for(int j=1;j<=m;j++)
- {
- total[j] += B[i][j];//连接块中电路板数
- }
- }
- int bestd = m + 1;
- bestx = 0;
- do//节点扩展
- {
- if(E.s == n-1)//仅一个儿子节点
- {
- int ld = 0;//最后一块电路板的密度
- for(int j=1; j<=m; j++)
- {
- ld += B[E.x[n]][j];
- }
- if(ld<bestd)//密度更小的电路排列
- {
- delete[] bestx;
- bestx = E.x;
- bestd = max(ld,E.cd);
- }
- else
- {
- delete []E.x;
- }
- delete []E.now;
- }
- else//产生当前扩展节点的所有儿子节点
- {
- for(int i=E.s+1;i<=n;i++)
- {
- BoardNode N;
- N.now = new int[m+1];
- for(int j=1; j<=m; j++)
- {
- //新插入的电路板
- N.now[j] = E.now[j] + B[E.x[i]][j];
- }
- int ld = 0;//新插入的电路板密度
- for(int j=1; j<=m; j++)
- {
- if(N.now[j]>0 && total[j]!=N.now[j])
- {
- ld++;
- }
- }
- N.cd = max(ld,E.cd);
- if(N.cd<bestd)//可能产生更好的叶子节点
- {
- N.x = new int[n+1];
- N.s = E.s + 1;
- for(int j=1;j<=n;j++)
- {
- N.x[j] = E.x[j];
- }
- N.x[N.s] = E.x[i];
- N.x[i] = E.x[N.s];
- H.Insert(N);
- }
- else
- {
- delete []N.now;
- }
- }
- delete []E.x;
- }//完成当前节点扩展
- if(H.Size() == 0)
- {
- return bestd;//无扩展节点
- }
- H.DeleteMin(E);
- }while(E.cd<bestd);
- //释放做小堆中所有节点
- do
- {
- delete []E.x;
- delete []E.now;
- if(H.Size() == 0)
- {
- break;
- }
- H.DeleteMin(E);
- }while(true);
- return bestd;
- }
问题描述
给定n个作业的集合{J1,J2,…,Jn}。每个作业必须先由机器1处理,然后由机器2处理。作业Ji需要机器j的处理时间为tji。对于一个确定的作业调度,设Fji是作业i在机器j上完成处理的时间。所有作业在机器2上完成处理的时间和称为该作业调度的完成时间和。
批处理作业调度问题要求对于给定的n个作业,制定最佳作业调度方案,使其完成时间和达到最小。
例:设n=3,考虑以下实例:
这3个作业的6种可能的调度方案是1,2,3;1,3,2;2,1,3;2,3,1;3,1,2;3,2,1;它们所相应的完成时间和分别是19,18,20,21,19,19。易见,最佳调度方案是1,3,2,其完成时间和为18。
限界函数
批处理作业调度问题要从n个作业的所有排列中找出具有最小完成时间和的作业调度,所以如图,批处理作业调度问题的解空间是一颗排列树。
在作业调度问相应的排列空间树中,每一个节点E都对应于一个已安排的作业集。以该节点为根的子树中所含叶节点的完成时间和可表示为:
设|M|=r,且L是以节点E为根的子树中的叶节点,相应的作业调度为{pk,k=1,2,……n},其中pk是第k个安排的作业。如果从节点E到叶节点L的路上,每一个作业pk在机器1上完成处理后都能立即在机器2上开始处理,即从pr+1开始,机器1没有空闲时间,则对于该叶节点L有:
注:(n-k+1)t1pk,因为是完成时间和,所以,后续的(n-k+1)个作业完成时间和都得算上t1pk。
如果不能做到上面这一点,则s1只会增加,从而有:。
类似地,如果从节点E开始到节点L的路上,从作业pr+1开始,机器2没有空闲时间,则:
同理可知,s2是的下界。由此得到在节点E处相应子树中叶节点完成时间和的下界是:
注意到如果选择Pk,使t1pk在k>=r+1时依非减序排列,S1则取得极小值。同理如果选择Pk使t2pk依非减序排列,则S2取得极小值。
这可以作为优先队列式分支限界法中的限界函数。
算法描述
算法中用最小堆表示活节点优先队列。最小堆中元素类型是MinHeapNode。每一个MinHeapNode类型的节点包含域x,用来表示节点所相应的作业调度。s表示该作业已安排的作业时x[1:s]。f1表示当前已安排的作业在机器1上的最后完成时间;f2表示当前已安排作业在机器2上的完成时间;sf2表示当前已安排的作业在机器2上的完成时间和;bb表示当前完成时间和下界。二维数组M表示所给的n个作业在机器1和机器2所需的处理时间。在类Flowshop中用二维数组b存储排好序的作业处理时间。数组a表示数组M和b的对应关系。算法Sort实现对各作业在机器1和2上所需时间排序。函数Bound用于计算完成时间和下界。
函数BBFlow中while循环完成对排列树内部结点的有序扩展。在while循环体内算法依次从活结点优先队列中取出具有最小bb值(完成时间和下界)的结点作为当前扩展结点,并加以扩展。 算法将当前扩展节点E分两种情形处理:
1)首先考虑E.s=n的情形,当前扩展结点E是排列树中的叶结点。E.sf2是相应于该叶结点的完成时间和。当E.sf2 < bestc时更新当前最优值bestc和相应的当前最优解bestx。
2)当E.s<n时,算法依次产生当前扩展结点E的所有儿子结点。对于当前扩展结点的每一个儿子结点node,计算出其相应的完成时间和的下界bb。当bb < bestc时,将该儿子结点插入到活结点优先队列中。而当bb bestc时,可将结点node舍去。
算法具体实现如下:
1、MinHeap2.h
- #include <iostream>
- template<class Type>
- class Graph;
- template<class T>
- class MinHeap
- {
- template<class Type>
- friend class Graph;
- public:
- MinHeap(int maxheapsize = 10);
- ~MinHeap(){delete []heap;}
- int Size() const{return currentsize;}
- T Max(){if(currentsize) return heap[1];}
- MinHeap<T>& Insert(const T& x);
- MinHeap<T>& DeleteMin(T &x);
- void Initialize(T x[], int size, int ArraySize);
- void Deactivate();
- void output(T a[],int n);
- private:
- int currentsize, maxsize;
- T *heap;
- };
- template <class T>
- void MinHeap<T>::output(T a[],int n)
- {
- for(int i = 1; i <= n; i++)
- cout << a[i] << " ";
- cout << endl;
- }
- template <class T>
- MinHeap<T>::MinHeap(int maxheapsize)
- {
- maxsize = maxheapsize;
- heap = new T[maxsize + 1];
- currentsize = 0;
- }
- template<class T>
- MinHeap<T>& MinHeap<T>::Insert(const T& x)
- {
- if(currentsize == maxsize)
- {
- return *this;
- }
- int i = ++currentsize;
- while(i != 1 && x < heap[i/2])
- {
- heap[i] = heap[i/2];
- i /= 2;
- }
- heap[i] = x;
- return *this;
- }
- template<class T>
- MinHeap<T>& MinHeap<T>::DeleteMin(T& x)
- {
- if(currentsize == 0)
- {
- cout<<"Empty heap!"<<endl;
- return *this;
- }
- x = heap[1];
- T y = heap[currentsize--];
- int i = 1, ci = 2;
- while(ci <= currentsize)
- {
- if(ci < currentsize && heap[ci] > heap[ci + 1])
- {
- ci++;
- }
- if(y <= heap[ci])
- {
- break;
- }
- heap[i] = heap[ci];
- i = ci;
- ci *= 2;
- }
- heap[i] = y;
- return *this;
- }
- template<class T>
- void MinHeap<T>::Initialize(T x[], int size, int ArraySize)
- {
- delete []heap;
- heap = x;
- currentsize = size;
- maxsize = ArraySize;
- for(int i = currentsize / 2; i >= 1; i--)
- {
- T y = heap[i];
- int c = 2 * i;
- while(c <= currentsize)
- {
- if(c < currentsize && heap[c] > heap[c + 1])
- c++;
- if(y <= heap[c])
- break;
- heap[c / 2] = heap[c];
- c *= 2;
- }
- heap[c / 2] = y;
- }
- }
- template<class T>
- void MinHeap<T>::Deactivate()
- {
- heap = 0;
- }
- //批作业调度问题 优先队列分支限界法求解
- #include "stdafx.h"
- #include "MinHeap2.h"
- #include <iostream>
- using namespace std;
- class Flowshop;
- class MinHeapNode
- {
- friend Flowshop;
- public:
- operator int() const
- {
- return bb;
- }
- private:
- void Init(int);
- void NewNode(MinHeapNode,int,int,int,int);
- int s, //已安排作业数
- f1, //机器1上最后完成时间
- f2, //机器2上最后完成时间
- sf2, //当前机器2上完成时间和
- bb, //当前完成时间和下界
- *x; //当前作业调度
- };
- class Flowshop
- {
- friend int main(void);
- public:
- int BBFlow(void);
- private:
- int Bound(MinHeapNode E,int &f1,int &f2,bool **y);
- void Sort(void);
- int n, //作业数
- ** M, //各作业所需的处理时间数组
- **b, //各作业所需的处理时间排序数组
- **a, //数组M和b的对应关系数组
- *bestx, //最优解
- bestc; //最小完成时间和
- bool **y; //工作数组
- };
- template <class Type>
- inline void Swap(Type &a, Type &b);
- int main()
- {
- int n=3,bf;
- int M1[3][2]={{2,1},{3,1},{2,3}};
- int **M = new int*[n];
- int **b = new int*[n];
- int **a = new int*[n];
- bool **y = new bool*[n];
- int *bestx = new int[n];
- for(int i=0;i<=n;i++)
- {
- M[i] = new int[2];
- b[i] = new int[2];
- a[i] = new int[2];
- y[i] = new bool[2];
- }
- cout<<"各作业所需要的时间处理数组M(i,j)值如下:"<<endl;
- for(int i=0;i<n;i++)
- {
- for(int j=0;j<2;j++)
- {
- M[i][j]=M1[i][j];
- }
- }
- for(int i=0;i<n;i++)
- {
- cout<<"(";
- for(int j=0;j<2;j++)
- cout<<M[i][j]<<" ";
- cout<<")";
- }
- cout<<endl;
- Flowshop flow;
- flow.n = n;
- flow.M = M;
- flow.b = b;
- flow.a = a;
- flow.y = y;
- flow.bestx = bestx;
- flow.bestc = 1000;//给初值
- flow.BBFlow();
- cout<<"最优值是:"<<flow.bestc<<endl;
- cout<<"最优调度是:";
- for(int i=0;i<n;i++)
- {
- cout<<(flow.bestx[i]+1)<<" ";
- }
- cout<<endl;
- for(int i=0;i<n;i++)
- {
- delete[] M[i];
- delete[] b[i];
- delete[] a[i];
- delete[] y[i];
- }
- return 0;
- }
- //最小堆节点初始化
- void MinHeapNode::Init(int n)
- {
- x = new int[n];
- for(int i=0; i<n; i++)
- {
- x[i] = i;
- }
- s = 0;
- f1 = 0;
- f2 = 0;
- sf2 = 0;
- bb = 0;
- }
- //最小堆新节点
- void MinHeapNode::NewNode(MinHeapNode E,int Ef1,int Ef2,int Ebb,int n)
- {
- x = new int[n];
- for(int i=0; i<n; i++)
- {
- x[i] = E.x[i];
- }
- f1 = Ef1;
- f2 = Ef2;
- sf2 = E.sf2 + f2;
- bb = Ebb;
- s = E.s + 1;
- }
- //对各作业在机器1和2上所需时间排序
- void Flowshop::Sort(void)
- {
- int *c = new int[n];
- for(int j=0; j<2; j++)
- {
- for(int i=0; i<n; i++)
- {
- b[i][j] = M[i][j];
- c[i] = i;
- }
- for(int i=0; i<n-1; i++)
- {
- for(int k=n-1; k>i; k--)
- {
- if(b[k][j]<b[k-1][j])
- {
- Swap(b[k][j],b[k-1][j]);
- Swap(c[k],c[k-1]);
- }
- }
- }
- for(int i=0; i<n; i++)
- {
- a[c[i]][j] = i;
- }
- }
- delete []c;
- }
- //计算完成时间和下界
- int Flowshop::Bound(MinHeapNode E,int &f1,int &f2,bool **y)
- {
- for(int k=0; k<n; k++)
- {
- for(int j=0; j<2; j++)
- {
- y[k][j] = false;
- }
- }
- for(int k=0; k<=E.s; k++)
- {
- for(int j=0; j<2; j++)
- {
- y[a[E.x[k]][j]][j] = true;
- }
- }
- f1 = E.f1 + M[E.x[E.s]][0];
- f2 = ((f1>E.f2)?f1:E.f2)+M[E.x[E.s]][1];
- int sf2 = E.sf2 + f2;
- int s1 = 0,s2 = 0,k1 = n-E.s,k2 = n-E.s,f3 = f2;
- //计算s1的值
- for(int j=0; j<n; j++)
- {
- if(!y[j][0])
- {
- k1--;
- if(k1 == n-E.s-1)
- {
- f3 = (f2>f1+b[j][0])?f2:f1+b[j][0];
- }
- s1 += f1+k1*b[j][0];
- }
- }
- //计算s2的值
- for(int j=0; j<n; j++)
- {
- if(!y[j][1])
- {
- k2--;
- s1 += b[j][1];
- s2 += f3 + k2*b[j][1];
- }
- }
- //返回完成时间和下界
- return sf2 +((s1>s2)?s1:s2);
- }
- //解批处理作业调度问题的优先队列式分支限界法
- int Flowshop::BBFlow(void)
- {
- Sort();//对各作业在机器1和2上所需时间排序
- MinHeap<MinHeapNode> H(1000);
- MinHeapNode E;
- //初始化
- E.Init(n);
- //搜索排列空间树
- while(E.s<=n)
- {
- //叶节点
- if(E.s == n)
- {
- if(E.sf2<bestc)
- {
- bestc = E.sf2;
- for(int i=0; i<n; i++)
- {
- bestx[i] = E.x[i];
- }
- }
- delete []E.x;
- }
- else//产生当前扩展节点的儿子节点
- {
- for(int i=E.s; i<n; i++)
- {
- Swap(E.x[E.s],E.x[i]);
- int f1,f2;
- int bb = Bound(E,f1,f2,y);
- if(bb<bestc)
- {
- //子树可能含有最优解
- //节点插入最小堆
- MinHeapNode N;
- N.NewNode(E,f1,f2,bb,n);
- H.Insert(N);
- }
- Swap(E.x[E.s],E.x[i]);
- }
- delete []E.x;//完成节点扩展
- }
- if(H.Size() == 0)
- {
- break;
- }
- H.DeleteMin(E);//取下一扩展节点
- }
- return bestc;
- }
- template <class Type>
- inline void Swap(Type &a, Type &b)
- {
- Type temp=a;
- a=b;
- b=temp;
- }