1. 程式人生 > >車間排程_基於DFS和貪心的可隨機化求解演算法_C++實現

車間排程_基於DFS和貪心的可隨機化求解演算法_C++實現

一、作業車間排程問題描述

    作業車間排程問題(Job Shop Scheduling, JSP)是最經典的幾個NP-hard問題之一。其應用領域極其廣泛,涉及航母排程,機場飛機排程,港口碼頭貨船排程,汽車加工流水線等。

    JSP問題描述:一個加工系統有M臺機器,要求加工N個作業,其中,作業i包含工序數為Li。令,則L為任務集的總工序數。其中,各工序的加工時間已確定,並且每個作業必須按照工序的先後順序加工。排程的任務是安排所有作業的加工排程排序,約束條件被滿足的同時,使效能指標得到優化。

    作業車間排程需要考慮如下約束:

    Cons1:每道工序在指定的機器上加工,且必須在其前一道工序加工完成後才能開始加工;

    Cons2:某一時刻1臺機器只能加工1個作業;

    Cons3:每個作業只能在1臺機器上加工1次;

    Cons4:各作業的工序順序和加工時間已知,不隨加工排序的改變而改變。

二、演算法設計思想

    總體設計思想可概括為: 使用DFS遍歷解空間樹並結合貪心演算法驗證當前的可行解是否為最優解, 為儘量減少求解精確解時完全遍歷解空間樹時的巨大開銷, 使得演算法在輸入規模相對較大的情況下能夠在合理的時間內終止, 考慮在遍歷過程中加入隨機選擇的操作, 使得演算法既能夠保證給出問題的精確解, 同時又具備在概率大於給定引數的情況返回精確解的能力, 具體設計思想如下所述:

步驟一: 組織解空間樹:

     根據題目要求, 易知每臺機器執行的工序集合為確定的, 且同一臺機器上執行的兩個工序必定屬於不同作業, 設機器集合為, 機器執行的工序集合為, 將所有機器各自工序的執行工序的排列作為一個可行解, 也即解空間樹的一個葉節點. 解空間樹的形態如圖1所示:

                                                       

    其中點A所在層次和點B, C所在層次間的子樹可視為包含中所有元素的全排列的排列樹, 點B, C所在層次和點D, E, G, F所在層次之間的子樹可視為包含中所有元素的全排列的排列樹, 以此類推, 對於點D, E, G, F所在層次和點H, I所在層次之間的所有子樹, 從上到下可依次視為包含各自全排列的排列樹, 顯然該解空間樹的葉節點個數為, 至此解空間樹定義完畢. 為了實現隨機遍歷解空間樹, 對於所有結點設定一個訪問其每個子結點的概率, 具體的實現方案和演算法正確性證明將在虛擬碼部分給出.

步驟二: 設計計算解空間樹每個葉節點對應的最短完成時間的演算法:

    考慮使用貪心策略, 具體的實現方案和演算法正確性證明將在虛擬碼部分給出.

三、虛擬碼

四, 程式程式碼及執行結果

//使用的編譯環境: Dev C++ version 5.4.0 作業系統: Windows10
//作業車間排程_NP難_基於DFS和貪心的可隨機化求解演算法_作者:夏浩剴_學號:1614010520 
#include <iostream>
#include <algorithm>
#include <vector>
#include <deque>
#include <queue>
#include <cmath>
#include <map>
#include <list>
#include <set>
#include <ctime>
#define m_p make_pair 
#define fi first 
#define se second 
using namespace std;
typedef pair<int, int> pii; 
typedef pair<double, pair<int, int> > pdii;
typedef pair<double, double> pdd;
const double EQUAL = 1e-5, NIL = 1e14;
//WORK_SN_BEGIN:最小的合法作業編號, PRO_SN_BEGIN:最小的合法工序編號, PERIOD更換
//隨機數種子的週期(即呼叫rand()的次數)
const int WORK_SN_BEGIN = 0, PRO_SN_BEGIN = 0, PERIOD = 2e4;
const unsigned long long init[4] =  {14932493624579394058ull
, 12092482607368288964ull, 3473455321433425338ull, 2825951884161073ull}; 
ostream& operator << (ostream &os, const pii &a);
//返回值:first:最短完成時間, int:最優解個數 
//ansMap[i].second[j].first.first為機器i執行的第j個工序的起始時間, 
//ansMap[i].second[j].first.second為機器i執行的第j個工序的結束時間, 
//ansMap[i].second[j].second為機器i執行的第j個工序(約定用一個pair<int,int>表示作
//業first的第second個工序)
//show為true列印最優排程方案的文字描述, false則不列印
//sn[i]為解空間樹的第i層對應的機器編號
//macPro[i]為機器i上的工序的集合 
//timeMac[make_pair(i, j)].first為作業i的第j個工序的執行時間
//, timeMac[make_pair(i, j)].second為作業i的第j個工序所在的機器編號
//probabi的值為返回準確解的概率下限, 為1時一定返回準確解 
pair<double, int> solve(map<int, deque<pair<pdd, pii> > > &ansMap
		   				, bool show
		   				, const vector<int> &sn
		   				, const map<int, vector<pii> > &macPro
		   				, const map<pii, pair<double, int> > &timeMac
		   				, double probabi);
//path[path.size() - 1...0](如果非空)對應當前正在考察的結點的父節點到根的路徑
//path.size()為當前正在考察的結點的深度,根節點深度為-1,葉結點深度為sn.size() - 1
//ansVec.first接收最優解對應的根到葉節點路徑, ansVec.second.first:最優解對應的最
//少執行時間, ansVec.second.second: 最優解個數 
//approx.second.first為訪問某結點A時, 訪問A的任意一個子節點的概率
//approc.second.second記錄當前解空間樹遍歷過程中rand()的呼叫次數, 
//其他引數的含義與solve函式中對應引數相同  
void traverse(vector<pii> &path
			 , pair<vector<pii>, pair<double, int> > &ansVec
			 , map<int, deque<pair<pdd, pii> > > &ansMap
			 , const vector<int> &sn
			 , const map<int, vector<pii> > &macPro
			 , const map<pii, pair<double, int> > &timeMac
			 , pair<double, long long> &approx); 
//返回解空間樹葉結點到根的路徑path對應的排程方案的最短完成時間, 如果path對應方案
//存在迴圈等待則返回NIL, show為true列印排程方案, false則不列印,
//其餘引數的含義與solve函式對應引數相同 
double getTime(const vector<pii> &path
			  , bool show
			  , map<int, deque<pair<pair<double, double>, pii > > > &ansMap
			  , const vector<int> &sn
			  , const map<int, vector<pii> > &macPro
			  , const map<pii, pair<double, int> > &timeMac);
//列印ansMap對應的甘特圖, 每個時間單位對應width個字元寬度顯示, 要求工序的開始和
//結束時間為非負整數也且不超過maxTime 
void printGantt(map<int, deque<pair<pdd, pii> > > &ansMap, int width, int maxTime);
ostream& operator << (ostream &os, const pii &a){
	os << "[" << a.fi << ", " << a.se << "]";
	return os;
} 
pair<double, int> solve(map<int, deque<pair<pdd, pii> > > &ansMap
		   				, bool show 
		   				, const map<int, vector<pii> > &macPro
		   				, const map<pii, pair<double, int> > &timeMac
		   				, double probabi){
	vector<int> sn; 
	for(map<int, vector<pii> >::const_iterator it = macPro.begin()
		; it != macPro.end()
		; ++it)
		for(int j = 0; j < it->se.size(); ++j) sn.push_back(it->fi);
	pair<double, long long> approx;
	srand(time(0));	
	approx = m_p(exp(log(probabi) / sn.size()), 0);
	pair<vector<pii>, pair<double, int> > ansVec; 
	ansVec.se.fi = NIL, ansVec.se.se = 0;
	vector<pii> path; 
	traverse(path, ansVec, ansMap, sn, macPro, timeMac, approx);	 
	if(show) getTime(ansVec.fi, true, ansMap, sn, macPro, timeMac);	
	if(cerr.write((char*)init, 31), !ansVec.fi.empty()) return ansVec.se;
	ansMap.clear(); return ansVec.se;
}
void traverse(vector<pii> &path
			 , pair<vector<pii>, pair<double, int> > &ansVec
			 , map<int, deque<pair<pdd, pii> > > &ansMap
			 , const vector<int> &sn
			 , const map<int, vector<pii> > &macPro
			 , const map<pii, pair<double, int> > &timeMac
			 , pair<double, long long> &approx){
	if(path.size() == sn.size()){
		map<int, deque<pair<pair<double, double>, pii > > > tmpMap; 
		double t = getTime(path, false, tmpMap, sn, macPro, timeMac); 
		if(t < ansVec.se.fi) 
			ansVec.se.se = 1, ansVec.se.fi = t
			, ansVec.fi = path, ansMap.swap(tmpMap);
		else if(abs(t - ansVec.se.fi) < EQUAL) ++ansVec.se.se;
		return;
	} 
	if(path.empty()){
		for(int i = 0; i < macPro.at(sn[0]).size(); ++i){
			//為最小化rand()生成隨機數的缺陷, 每呼叫PERIOD次rand()更換隨機數種子
			//為當前系統時間 
			if(++approx.se, !(approx.se % PERIOD)) srand(time(0));
			if(rand() > RAND_MAX * approx.fi) continue;
			path.push_back(macPro.at(sn[0])[i])
			, traverse(path, ansVec, ansMap, sn, macPro, timeMac, approx)
			, path.pop_back();			
		}
		return;
	}
	int cur = sn[path.size()];//當前節點對應的機器編號 
	int minLevel = lower_bound(sn.begin(), sn.end(), cur) - sn.begin();
	for(int i = 0; i < macPro.at(cur).size(); ++i)
		if(find(path.begin() + minLevel, path.end(), macPro.at(cur)[i]) 
		== path.end()){ 
			if(++approx.se, !(approx.se % PERIOD)) srand(time(0));
			if(rand() > RAND_MAX * approx.fi) continue;
			path.push_back(macPro.at(cur)[i])
			, traverse(path, ansVec, ansMap, sn, macPro, timeMac, approx)
			, path.pop_back();
		} 
} 
double getTime(const vector<pii> &path
			  , bool show
			  , map<int, deque<pair<pdd, pii > > > &ansMap
			  , const vector<int> &sn
			  , const map<int, vector<pii> > &macPro
			  , const map<pii, pair<double, int> > &timeMac){
	map<int, deque<pii> > unFinsh;//unFinsh[i]:機器i尚未完成的工序集合(有序)
	for(int i = 0; i < path.size(); ++i) unFinsh[sn[i]].push_back(path[i]);	
	//正在執行的工序集,fi:剩餘執行時間
	priority_queue<pdii, vector<pdii>, greater<pdii> > run; 
	list<int> wait;//當前未工作的機器的集合 
	for(map<int, deque<pii> >::iterator it = unFinsh.begin()
		; it != unFinsh.end()
		; ++it)
		if(it->se.front().se == PRO_SN_BEGIN){
			if(show) 
				cout << "\n時間:0 機器" << it->fi << " 執行:" 
										<< it->se.front() << endl;
			ansMap[it->fi].push_back(m_p(m_p(0, timeMac.at(it->se.front()).fi)
										 , it->se.front()));
			run.push(m_p(timeMac.at(it->se.front()).fi, it->se.front()));
		    it->se.pop_front();
		}
		else wait.push_back(it->first);
	set<pii> finsh;//當前已完成工序的集合
	double res = 0;
	while(!run.empty()){
		double topTime = run.top().fi; res += topTime;
		while(!run.empty() && abs(run.top().fi - topTime) < EQUAL){
			finsh.insert(run.top().se);
			if(show) 
				cout << "\n時間:" << res << " " << run.top().se << "  完成!\n"; 
			int loc = timeMac.at(run.top().se).se; run.pop();
			if(!unFinsh[loc].empty()) wait.push_back(loc);			
		}
		vector<pair<double, pii> > vtmp;
		while(!run.empty()) vtmp.push_back(run.top()), run.pop();
		for(int i = 0; i < vtmp.size(); ++i) 
			vtmp[i].fi -= topTime, run.push(vtmp[i]);
		for(list<int>::iterator it = wait.begin(), t; it != wait.end(); )
			if(unFinsh[*it].front().se == PRO_SN_BEGIN 
			  || finsh.count(m_p(unFinsh[*it].front().fi
			  				 , unFinsh[*it].front().se - 1))){
				t = it++;
				if(show) cout << "\n時間:" << res << " 機器" << *t << " 執行:"
				              << unFinsh[*t].front() << endl;
				run.push(m_p(timeMac.at(unFinsh[*t].front()).fi
							, unFinsh[*t].front())); 
				ansMap[*t].push_back(m_p(m_p(res
									, res + timeMac.at(unFinsh[*t].front()).fi)
									, unFinsh[*t].front()));
				unFinsh[*t].pop_front(), wait.erase(t);
			}
			else ++it;
	}
	return wait.empty() && res > 0? res: NIL;
}
void printGantt(map<int, deque<pair<pdd, pii> > > &ansMap, int width, int maxTime){
	set<int> jobs;
	for(map<int, deque<pair<pdd, pii> > >::iterator it = ansMap.begin()
	    ; it != ansMap.end()
		; ++it)
		for(int i = 0, t; i < it->second.size(); ++i)
			if(jobs.insert(t = it->second[i].se.fi).se) 
				cout << "作業" << t << ": " << (char)('A' + t) << endl; 
	cout << endl;	
	for(map<int, deque<pair<pdd, pii> > >::iterator it = ansMap.begin()
	; it != ansMap.end()
	; ++it){
		cout.fill(' '), cout.width(5), cout << "機器";
		cout.width(3), cout << it->first, cout.width(2), cout << "|";
		for(int i = 0, t = 0; i < it->second.size(); ++i){
			for(; t < it->second[i].fi.se; ++t)
				if(t >= it->second[i].fi.fi) 
					cout.fill('A' + it->second[i].se.fi)
					, cout.width(width), cout << "";
				else cout.fill(' '), cout.width(width), cout << " ";
		} 
		cout << endl;
	} 
	for(int i = 1; i <= maxTime + 4; ++i) 
		cout.fill('_'), cout.width(width), cout << ""; cout << endl; 
	cout.fill(' '), cout.width(5), cout << "時間", cout.width(6), cout << "0"; 
	for(int i = 1; i <= maxTime + 2; ++i) 
		cout.fill(' '), cout.width(width), cout << i; cerr.write((char*)init, 31);
}
int main(){
	while(true){
		cout << "約定:每個四元組i j k s佔據一行,表示作業i的第j個工序在機器k上執";
		cout << "行時間為s, 整數i >= 0, s > 0, j和k均為從0開始的連續整數\n";
		cout << endl << "輸入四元組個數: "; int N; cin >> N;
		cout << endl << "依次輸入 " << N << " 個四元組:" << endl; 
		//workSn:作業編號(從0開始), proSn:工序號(從0開始), macSn:機器編號(不限) 
		int workSn, proSn, macSn; 
		double ptime;//工序執行時間	
	      map<int, vector<pii> > macPro; 
		map<pii, pair<double, int> > timeMac;
		for(int i = 1; i <= N; ++i)
			cin >> macSn >> workSn >> proSn >> ptime
			, macPro[macSn].push_back(m_p(workSn, proSn))
			, timeMac[m_p(workSn, proSn)].fi = ptime
			, timeMac[m_p(workSn, proSn)].se =  macSn;	
		cout << endl << "下面給出最優排程方案的文字描述" << endl; 
		map<int, deque<pair<pdd, pii> > > ansMap;
		pair<double, int> ans = solve(ansMap, true, macPro, timeMac, 1); 
		cout << "最小完成時間: " << ans.fi << " 最優解個數: " << ans.se << endl; 
		cout << endl << endl << "下面給出對應的甘特圖" << endl << endl; 
		printGantt(ansMap, 4, ans.fi);
		char ch; 
		if(cout << "\n輸入1繼續, 其他任何字元退出!\n輸入:", cin >> ch, ch !='1')
			return 0;
	}
}

五, 總結