1. 程式人生 > >0040演算法筆記——【分支限界法】批處理作業排程問題

0040演算法筆記——【分支限界法】批處理作業排程問題

      問題描述

     給定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)個作業完成時間和都得算上t

1pk

如果不能做到上面這一點,則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; 
} 
    2、6d9.cpp
//批作業排程問題 優先佇列分支限界法求解 
#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;
}
     程式執行結果如圖:



相關推薦

0040演算法筆記——分支限界處理作業排程問題

      問題描述      給定n個作業的集合{J1,J2,…,Jn}。每個作業必須先由機器1處理,然後由機器2處理。作業Ji需要機器j的處理時間為tji。對於一個確定的作業排程,設Fji是作業i在機器j上完成處理的時間。所有作業在機器2上完成處理的時間和稱為該作業排程

0038演算法筆記——分支限界旅行員售貨問題

問題描述 某售貨員要到若干城市去推銷商品,已知各城市之間的路程(旅費),他要選定一條從駐地出發,經過每個城市一遍,最後回到駐地的路線,使總的路程(總旅費)最小。     演算法思路 旅行售貨員問題的解空間可以組織成一棵樹,從樹的根結點到任一葉結點的路徑定義了圖的一條周遊

0033演算法筆記——分支限界分支限界與單源最短路徑問題

      1、分支限界法 (1)描述:採用廣度優先產生狀態空間樹的結點,並使用剪枝函式的方法稱為分枝限界法。 所謂“分支”是採用廣度優先的策略,依次生成擴充套件結點的所有分支(即:兒子結點)。所謂“限界”是在結點擴充套件過程中,計算結點的上界(或下界),邊搜尋邊減掉搜尋

NOJ1571演算法實驗三分支限界八數碼

1571.八數碼 時限:5000ms 記憶體限制:20000K  總時限:10000ms 描述 在九宮格里放在1到8共8個數字還有一個是空格,與空格相鄰的數字可以移動到空格的位置,問給定的狀態最少需要幾步能到達目標狀態(用0表示空格): 1 2 3 4 5 6 7 8

NOJ1541分支限界加1乘2平方

1541.加1乘2平方 時限:1000ms 記憶體限制:10000K  總時限:3000ms 描述 給定兩個正整數m、n,問只能做加1、乘2和平方這三種變化,從m變化到n最少需要幾次 輸入 輸入兩個10000以內的正整數m和n,且m小於n 輸出 輸出從m變化到n

NOJ1042分支限界電子老鼠走迷宮

1042.電子老鼠闖迷宮 時限:1000ms 記憶體限制:10000K  總時限:3000ms 描述 有一隻電子老鼠被困在如下圖所示的迷宮中。這是一個12*12單元的正方形迷宮,黑色部分表示建築物,白色部分是路。電子老鼠可以在路上向上、下、左、右行走,每一步走一個格子。

NOJ1044分支限界獨輪車

1044.獨輪車 時限:1000ms 記憶體限制:10000K  總時限:3000ms 描述 獨輪車的輪子上有紅、黃、藍、白、綠(依順時針序)5種顏色 在一個如下圖所示的20*20的迷宮內每走一個格子,輪子上的顏色變化一次(原地轉向則不變色) 獨輪車只能向前推或在原

回溯實現處理作業排程

排列數搜尋問題很簡單,沒做優化。 #include <iostream> #include <vector> #include <algorithm> using namespace std; int work[4][

演算法java實現--分支限界--單源最短路徑問題

單源最短路徑問題的java實現(分支限界法) 具體問題描述以及C/C++實現參見網址 http://blog.csdn.net/liufeng_king/article/details/8900872import java.util.Collections; impo

五大常用演算法之五--分支限界

一、基本描述 類似於回溯法,也是一種在問題的解空間樹T上搜索問題解的演算法。 但在一般情況下,分支限界法與回溯法的求解目標不同。 回溯法的求解目標是找出T中滿足約束條件的所有解 分支限界法的求解目標則是找出滿足約束條件的一個解,或是在滿足約束

分枝限界解帶期限作業排程問題

這是在國科大的演算法課上學到的演算法,同時也是這門課上的一個作業。在做這個作業時遇到了很多的坑,網上查到的資料也很少,而且很多都是錯的,因此在部落格上記錄下解決問題的過程。 問題描述 演算法概述 這個虛擬碼中的幾個函式解釋如下: - cost(T): 表達該答案節

第五章 回溯-處理作業排程

http://blog.csdn.net/wzq153308/article/details/46365177 問題描述 給定 n 個作業的集合 j = {j1, j2, ..., jn}。每一個作業 j[i]  都有兩項任務分別在兩臺機器上完成。每一個作業必須先由機器1&nbs

回溯--處理作業排程

http://www.cnblogs.com/xing901022/archive/2012/10/23/2734983.html 問題描述:   給定n個作業,集合J=(J1,J2,J3)。每一個作業Ji都有兩項任務分別在2臺機器上完成。每個作業必須先有機器1處理,然後再由機器2處理

演算法設計例題:處理作業排程(回溯)

給定n個作業的集合 J = { J1,J2,…,Jn }。每一個作業Ji都有兩項任務分別在兩臺機器上完成。每個作業必須先由機器1處理,然後由機器2處理。作業Ji需要機器j 的處理時間為tji,其實 i=1, 2, …, n,j=1, 2。對於一個確定的作業排程,設Fji是 作

演算法 處理作業排程

題目 給定n個作業的集合J={J1,J2,…,Jn}。每一個作業有兩項任務分別在兩臺機器上完成。每個作業必須先由機器1處理,再由機器2處理。作業Ji需要機器j的處理時間為tji,i=1,2,…n,j=1,2。對於一個確定的作業排程,設Fji是作業i在機器j上完

0031演算法筆記——回溯旅行員售貨問題和圓排列問題

     1、旅行員售貨問題 問題描述 某售貨員要到若干城市去推銷商品,已知各城市之間的路程(旅費),他要選定一條從駐地出發,經過每個城市一遍,最後回到駐地的路線,使總的路程(總旅費)最小。     問題分析 旅行售貨員問題的解空間是一棵排列樹。對於排列樹的回溯法與生成

演算法筆記字串迴文串

  題目連結:http://www.codeup.cn/problem.php?cid=100000580&pid=8 題目描述 讀入一串字元,判斷是否是迴文串。“迴文串”是一個正讀和反讀都一樣的字串,比如“level”或者“noon”等等就是迴文串。 輸入

六中常用演算法設計:窮舉、分治法、動態規劃、貪心、回溯分支限界

演算法設計之六種常用演算法設計方法 1.直接遍歷態(窮舉法)        程式執行狀態是可以遍歷的,遍歷演算法執行每一個狀態,最終會找到一個最優的可行解;適用於解決極小規模或者複雜度線性增長,而線

分支限界:單源最短路徑--dijkstra演算法

單源最短路徑–dijkstra演算法 前面已經多次介紹過dijkstra演算法是貪心演算法,是動態規劃,實際上可以從分支限界的角度來理解; 分支限界法 分支限界法,實際上就是回溯法,一般意義的回溯法是基於深度優先搜尋,也可以配合限界函式剪枝,通常分支限界法基於寬度優先搜尋,通過佇

演算法入門——分支限界

用淺顯的話說就是一開始就大致性地找好方向:拿本人來說,專業選擇的軟體工程--然後從軟體工程細分到移動開發--然後又分到遊戲開發--然後又分到前端遊戲開發。或者也可以這樣,當你需要在一個學生管理系統的資料夾手動查詢ID為1208的學生資訊,這時候,你就要提前為每個資料夾寫好相關的策略(比如我要查詢