1. 程式人生 > >貪心演算法 -- 最小延遲排程

貪心演算法 -- 最小延遲排程

總結:

  首先,證明貪心的時候交換論證是萬能的!其次,這一點如果要滿足,也就是,如果你要用交換論證法,那麼首先要保證交換逆序後,對其他的沒有影響!如果有影響,那就只能像【POJ - 3253】Fence Repair 這道題一樣,用優先佇列去解決了。

1. 單區間排程問題 問題定義:存在單一資源,有一組以時間區間形式表示的資源請求reqs={req-1, req-2, …, req-n},第i個請求希望佔用資源一段時間來完成某些任務,這段時間開始於begin(i)終止於end(i)。如果兩個請求req-i和req-j在時間區間上沒有重疊,則說這兩個請求是相容的,求出這組請求的最大相容子集(最優子集)。舉個例子:有一間多媒體課室,某一個週末有多個社團想要申請這間課室去舉辦社團活動,每個社團都有一個對應的申請時間段,比如週六上午8:00-10:00。求出這間課室在這個週末最多能滿足幾個社團的需求。

解決方案:貪心演算法,優先選擇最早結束的需求,確保資源儘可能早地被釋放,把留下來滿足其他需求的時間最大化。具體虛擬碼如下所示,演算法結束後集合A中會保留所有相容請求,A的大小即是最大相容數量。

初始化R是所有需求的集合,A為空集 對R中的需求Ri,根據結束時間從早到晚排序 for Ri in R, do   if Ri與A中的請求相容     A = A並Ri   endIf endFor return A 上述虛擬碼的C++實現如下,

#include <iostream> #include <algorithm> #include <vector> using namespace std;

const int MAX_SIZE = 100;

struct Request {   int begin, end; } req[MAX_SIZE];

bool operator<(const Request& req1, const Request& req2) {   return req1.end < req2.end; }

int main() {   int requestNum;   cin >> requestNum;   if (requestNum > MAX_SIZE) {     cout << "請求數量過多" << endl;     return 0;   }   for (int i = 0; i < requestNum; ++i) {     cin >> req[i].begin >> req[i].end;   }

  sort(req, req + requestNum);

  vector<Request> rvec;   rvec.push_back(req[0]);   for (int i = 1; i < requestNum; ++i) {     if (rvec[rvec.size() - 1].end <= req[i].begin) {       rvec.push_back(req[i]);     }   }

  cout << "最大相容量: " << rvec.size() << endl;   return 0; } 2. 多區間排程問題 問題定義:存在多個(或者無限多個)相同的資源,有一組以時間區間形式表示的資源請求reqs={req-1, req-2, …, req-n},第i個請求希望佔用資源一段時間來完成某些任務,這段時間開始於begin(i)終止於end(i)。如果兩個請求req-i和req-j在時間區間上沒有重疊,則說這兩個請求是相容的,用盡可能少的資源滿足所有請求(求最優資源數量)。舉個例子:有很多間課室,某個週末有多個社團需要申請課室辦活動,每個社團都有一個對應的申請時間,求最少需要多少間課室才能夠滿足所有社團的需求(在這個問題之中時間重疊的社團需要安排在其他課室,即會使用到多個資源,需要考慮多個資源上的排程安排,故稱為多區間排程)。

解決方案:貪心演算法,將需求按照開始時間的早晚進行排序,然後開始為這些資源打標籤,每個標籤代表都一個資源,需求req-i被打上標籤k表示該請求分配到的資源是k。遍歷排序後的需求,如果一個需求與某個已分配資源上的其他安排不衝突,則把該需求也放進該資源的安排考慮中;如果衝突,那麼應該要給此需求分配新的資源,已用資源數量加一。具體操作的虛擬碼如下所示。

對n個需求按照開始時間從早到晚進行排序 假設排序後的需求記為{R1, R2, ..., Rn} 初始化tagSize = 1; for i=1 to n, do:   tags = {1,2,...,tagSize};   for j = 1 to i-1, do:     if Rj與Ri時間區間重疊產生衝突:       tags = tags - {Rj的標籤};     endIf   endFor   if tags為空集:     tagSize += 1;     將標籤tagSize貼在Ri上   EndIf   else:     在tags剩下的標籤中隨便挑一個貼給Ri   endElse endFor 此時每個請求上都貼有標籤,每個標籤對應其申請的資源編號,此時的tagSize就是至少需要的資源數量 return tagSize; 上述虛擬碼的C++實現如下:

#include <iostream> #include <algorithm> #include <cstring> using namespace std;

const int MAX_SIZE = 100;

struct Request {   int begin, end, tag; } req[MAX_SIZE];

bool operator<(const Request& req1, const Request& req2) {   return req1.begin < req2.begin; }

int main() {   int requestNum;   cin >> requestNum;   if (requestNum > MAX_SIZE) {     cout << "請求數量過多" << endl;     return 0;   }   for (int i = 0; i < requestNum; ++i) {     cin >> req[i].begin >> req[i].end;   }

  sort(req, req + requestNum);

  int tagSize = 1;   req[0].tag = 0;   bool tags[MAX_SIZE];   for (int i = 1; i < requestNum; ++i) {     memset(tags, 1, sizeof(tags));     for (int j = 0; j < i; ++j) {       if (req[j].end > req[i].begin) {         tags[req[j].tag] = false;       }     }     bool isTagsEmpty = true;     int tag;     for (int j = 0; j < tagSize; ++j) {       if (tags[j]) {         isTagsEmpty = false;         tag = j;         break;       }     }     if (isTagsEmpty) {       req[i].tag = tagSize;       ++tagSize;     } else {       req[i].tag = tag;     }   }

  cout << "最小資源使用量: " << tagSize << endl;   return 0; } 3. 最小延遲排程問題 問題定義:存在單一資源和一組資源請求reqs={req-1, req-2, …, req-n},與前面兩個問題不同,這裡的資源從時刻0開始有效(開始接受申請,開始可以被使用),每個請求req-i都有一個截止時間ddl(i),每個請求都要佔用資源一段連續的時間來完成任務,佔用時間為time(i)。每個請求都希望自己能在ddl之前完成任務,不同需求必須被分在不重疊的時間區間(單一資源,同一時刻只能滿足一個請求)。假設我們計劃滿足每個請求,但是允許某些請求延遲(即某個請求在ddl之後完成,延誤工期),確定一種合理的安排,使得所有請求的延期時間中的最大值,是所有可能的時間安排情況中最小的。從時刻0開始,為每個請求req-i分配一個長度time(i)的時間區間,把區間標記為[begin(i), end(i)],其中end(i) = begin(i) + time(i)。如果end(i) > ddl(i),則請求req-i被延遲,延遲時間為delay(i) = end(i) - ddl(i);否則delay(i) = 0。合理安排需求,使得maxDelay = max{delay(1), delay(2), …, delay(n)}是所有可能的安排中最小的。

解決方案:貪心演算法,按照截止時間ddl排序,越早截止的任務越早完成。該演算法是一個沒有空閒的最優排程,即從時刻0開始都有在處理請求,直到最後一個請求執行完釋放資源之後才空閒。虛擬碼如下所示。

將需求按照截止時間進行排序 假設排序後的截止時間為ddl[1]<=...<=ddl[n] start = 0; maxDelay = 0; for i = 1 to n, do:   begin[i] = start;   end[i] = start + time[i];   start = end[i] + time[i];   if maxDelay < end[i] - ddl[i]:     L = end[i] - ddl[i];   endIf endFor 則每個任務安排的時間區間為[begin[i], end[i]],所有任務中最大的延遲為maxDelay,maxDelay為所有可能的任務安排中最小的延遲 return maxDelay; 上述程式碼的C++實現如下:

#include <iostream> #include <algorithm> using namespace std;

const int MAX_SIZE = 100;

struct Request {   int time, ddl;   int begin, end; } req[MAX_SIZE];

bool operator<(const Request& req1, const Request& req2) {   return req1.ddl < req2.ddl; }

int main() {   int requestNum;   cin >> requestNum;   if (requestNum > MAX_SIZE) {     cout << "請求數量過多" << endl;     return 0;   }   for (int i = 0; i < requestNum; ++i) {     cin >> req[i].time >> req[i].ddl;   }

  sort(req, req + requestNum);

  int start = 0, maxDelay = 0;   for (int i = 0; i < requestNum; ++i) {     req[i].begin = start;     req[i].end = start + req[i].time;     start += req[i].time;     if (maxDelay < req[i].end - req[i].ddl) {       maxDelay = req[i].end - req[i].ddl;     }   }

  cout << "最小的最大延遲: " << maxDelay << endl;   return 0; }

程式碼格式不做調整,詳情請去原博主部落格中看。