1. 程式人生 > >【C】貪心演算法

【C】貪心演算法

所謂的“貪心演算法”,就是每一次面臨選擇時,選擇最優、最先、最X的一項,反正就是突出一個“最”字。比如有1,4,3,2,讓你選兩個數,令選出來的數最大,你肯定按照每次選擇都選擇,剩餘數中最大的一個數,第一次選擇4,剩下還有1,3,2,這時傻子都會選擇3啊,從而得出在1,4,3,2這四個數中,選出的兩數和是最大的。此時,你就不知不覺地運用到了貪心演算法,以最大選擇為貪心。

那麼,“貪心演算法”真正用到程式設計中是如何呢?下面用一道2013上半年軟體設計師的軟考題來說明這個問題。

題目是這樣的:

設有M臺完全相同的機器執行N個獨立的任務,執行任務i所需要的時間為ti,要求確定一個排程方案,使得完成所有任務所需要的時間最短。假設任務已經按照其執行的時間從大到小來排序,演算法基於最長執行時間作業優先的策略:按順序先把每個任務分配到一臺機器上,然後將剩餘的任務一次放入最先空閒的機器。

這裡要求定義的變數如下,所有陣列的下標皆從0開始:

設,m是機器數,n是任務數,t[]的長度為n,其中每個元素表示任務的執行時間。s[][]長度為mn,下標從0開始,其中s[i][j]表示機器i執行的任務j的編號。d[]長度為m,其中勻速d[i]表示機器i執行的時間,count[]長度為m,其中元素count[i]表示機器i執行的任務數。max為完成所有任務的時間。min,i,j,k為臨時定義變數,無意義。

考慮例項m=3(編號0-2),n=7(編號0-6),各任務的執行時間為{16,14,6,5,4,3,2}(這裡其實就是指t[i]定義為{16,14,6,5,4,3,2},題目沒有說)則求各個機器上的執行任務,從任務開始執行到完成所需要的時間。

這道題目,非常長,看起來非常讓人頭暈,實際上,根本一點都不難。如果你還學過《作業系統》的話,就更簡單的。

意思就是將{16,14,6,5,4,3,2}這些數字扔到3個數組裡面,使最終,這3個數組裡面的資料之和的最大的一個,最小。你也可以想像倒水到3個杯子,要求你每次倒的水的多少隻能從{16,14,6,5,4,3,2}這7個數字選一個,必須倒水倒7次,也就是這7個數字選完,最終儘可能讓任一一個杯子都不溢位。

你當然是平均化倒水啊,每次倒水的時候,看哪個杯子水量最小倒哪個啊!

這時,就運用到所謂的“貪心演算法”了。

整個過程如下圖所示:


最後求出來的結果是17。順理成章就得到如下的程式碼,每一次選擇都會有一個求最小值的過程:

#include<stdio.h>
void schedule(int m,int n,int *t){
	//初始化
	int i,j,k,max=0;
	int d[100],s[100][100],count[100];
	for(i=0;i<m;i++){
		d[i]=0;
		for(j=0;j<n;j++){
			s[i][j]=-1;//-1代表不執行任何任務,不與第0號任務混淆
		}
	}
	//分配前m個任務
	//必然是每個機器先分別接受1個任務
	for(i=0;i<m;i++){
		s[i][0]=i;
		d[i]=d[i]+t[i];
		count[i]=1;
	}
	//之後判斷哪個機器任務耗時最少,讓其接受任務
	//儘可能地並行,平均分配任務
	for(i=m;i<n;i++){
		int min=d[0];
		k=0;
		for(j=1;j<m;j++){//確定空閒機器,實質是在求當期任務總時間最少的機器
			if(min>d[j]){
				min=d[j];
				k=j;//機器k空閒
			}
		}
		s[k][count[k]]=i;//在機器k的執行佇列新增第i號任務
		count[k]=count[k]+1;//機器k的任務數+1
		d[k]=d[k]+t[i];//機器k的任務執行時間+t[i],也就是+第i號任務的耗時		
	}
	
	for(i=0;i<m;i++){//確定完成所有任務需要的時間,實質是求分配完所有任務之後,耗時最多的機器
		if(max<d[i]){
			max=d[i];
		}			
	}
	printf("完成所有任務需要的時間:%d\n",max);
	printf("各個機器執行的耗時一覽:\n");
	for(i=0;i<m;i++){
		printf("%d:",i);
		for(j=0;j<n;j++){
			if(s[i][j]==-1){
				break;
			}
			printf("%d\t",t[s[i][j]]);
		}
		printf("\n");
	}
}
void main(){//測試用例
	int time[7]={16,14,6,5,4,3,2};
	schedule(3,7,time);	
}

最終執行結果如下:


這裡,原題目還要對上述程式碼進行,除去初始化、列印那兩步,演算法核心的時間複雜度的分析。這個很好求的,不算初始化、列印那兩步,分配前m個任務,一個for迴圈,終止於i<m,這裡複雜度為m,之後判斷哪個機器任務耗時最小,兩層for迴圈,外層終結於i<m,裡層為i<n,因此為mn,最後確定所有任務需要的時間,一個for迴圈,終止於i<m,這裡複雜度為m,也就是m+mn+m,最高維度,用現在的話來說,就是最高次元為mn,因此mn+2m~mn(此乃高等數學的內容,不會面壁去~),也就是時間複雜度為O(mn)。

然而,實際上,由於貪心演算法每一次選擇都要找一個最優項,遍歷陣列的每一項都出現一個排序,不像這裡一上來就已知一個排好順序的陣列,因此時間複雜度往往是O(n2),而要是這題給出的陣列沒有排好序,估計要去到O(m2n2)了。