1. 程式人生 > >演算法設計與分析:第五章 回溯法 5.8流水作業車間排程

演算法設計與分析:第五章 回溯法 5.8流水作業車間排程

/*
流水作業車間排程:
n個作業要在兩臺機器M1和M2組成的流水線上完成加工。每個作業加工的順序都是現在M1上加工,然後在
M2上加工。M1和M2加工作業i所需的時間分別為ai和bi。流水作業排程問題要求確定這n個作業的最優
加工順序,使得從第一個作業在機器M1上開始加工,到最後一個作業在機器M2上加工完成所需的時間
最少。作業在機器M1和M2的加工順序相同。

分析:
解空間:是排列樹,用陣列x[](初值為1,2,3,...,n)模擬不同排列
如何計算完成時間?
對機器M1進行加工,加工時間f1是固定的。f1[i] = f1[i-1] + M1[x[i]]
對機器M2加工分為兩種情況
1)有空閒
##a1........##a2...........##
			##b1....       ##b2.........##
空閒時,f2[i] = f1[i] + M2[x[i]]

2)積壓時
##a1........##a2...........##
			##b1..................##b2.........##
空閒時,f2[i] = f2[i-1] + M2[x[i]]

如何獲取最優解?
最優排程應使及其M1沒有空閒時間,機器M2空閒時間最少。
在搜尋排列樹的同時,不斷更新最優解,最後找到問題的最優解。
搜尋過程中,當某一排列的前幾步的加工時間已經大於當前的最小值,就無需搜尋計算。

資料結構設計:
二維陣列job[100][2]儲存作業在M1,M2的加工時間。
由於f1在計算中,只需當前值,用變數儲存即可;f2在計算時,還依賴前一個作業的資料,
所以用陣列儲存。
變數f儲存當前加工需要的全部時間。

輸入:
3(作業數)
2 1
3 1 
2 3
輸出:
8

演算法步驟:
1 如果搜尋到葉節點(必然是最優的),得到排程方案,更新
2 對當前作業直到最後作業做迭代
3 計算機器1工作時間 , 判斷機器2處於積壓或者空閒 並更新f2
4 更新直到當前結點的累加值
5 若累加值小於最優解,則進入該結點的葉子結點,交換兩個作業,回溯下一個結點,再交換回來
6 將f1設定為回溯前的值,將累加值還原為回溯前的值
7 這是排列樹問題,初始化應該給與一個排列
		//這是回溯中的排列樹問題:需要賦予一個初始排列。排列樹:選取的n個元素滿足某種排列性質,時間複雜度為O(n!)
		for(int k = 1 ; k <= n ; k++)
		{
			//設定一個初始排程為排程作業1,2,...,n的順序
			x[k] = k;
		}
8 初始最優值應該置為無窮大
bestf = 1000000000;//易錯,最優值應該初始化為無窮大
*/

#include <iostream>

using namespace std;

int n;//作業數
int bestf;//存放最優排程時間
int f1;//機器1的呼叫時間
int f2[100];//機器2的呼叫時間
int f;//直至當前結點的機器呼叫累加時間
int job[100][3];//作業的執行時間
int x[100];//易錯,當前排程
int bestx[100];//易錯,存放當前作業最佳排程

void swap(int* p1 , int* p2)
{
	int iTemp = *p1 ; 
	*p1 = *p2;
	*p2 = iTemp;
}

void backTrace(int i)
{
	if(i == n + 1)//如果到達葉子結點,則說明產生了排程方案,更新
	{
		for(int j = 1 ; j <= n ; j++)
		{
			bestx[j] = x[j];
		}
		bestf = f;
	}
	else
	{
		//回溯的標準格式
		for(int j = i ; j <= n  ; j++)
		{
			f1 = f1 + job[ x[j] ][1];
			//如果機器2存在積壓
			if(f2[i-1] > f1)
			{
				f2[i] = f2[i-1] + job[ x[j] ][2];
			}
			else
			{
				f2[i] = f1 + job[ x[j] ][2];
			}
			f = f + f2[i];
			//判斷累加和是否小於最優排程值,是就進行繼續嘗試下一個結點
			if(f < bestf)
			{
				swap(&x[i] , &x[j]);
				backTrace(i+1);
				swap(&x[i] , &x[j]);
			}
			//清理現場
			f1 = f1 - job[ x[j] ][1];
			f = f - f2[i];
		}
	}
}

void process()
{
	while(cin >> n)
	{
		//輸入
		memset(job , 0 , sizeof(job));
		for(int i = 1 ; i <= n ; i++)
		{
			for(int j = 1 ; j <= 2 ; j++)
			{
				cin >> job[i][j];
			}
		}
		//計算
		f = 0;
		f1 = 0;
		bestf = 1000000000;//易錯,最優值應該初始化為無窮大
		memset(f2 , 0 , sizeof(f2));
		//易錯,這是回溯中的排列樹問題:需要賦予一個初始排列。排列樹:選取的n個元素滿足某種排列性質,時間複雜度為O(n!)
		for(int k = 1 ; k <= n ; k++)
		{
			//設定一個初始排程為排程作業1,2,...,n的順序
			x[k] = k;
		}
		backTrace(1);
		cout << f2[n] << endl;
	}
}

int main(int argc , char* argv[])
{
	process();
	getchar();
	return 0;
}