2018華爲軟件精英挑戰賽1

本次華爲2018軟件精英挑戰賽,趁着空餘時間參加了一下,很幸運江山賽區初賽36名壓線進了江山賽區決賽,決賽名次是25名,應該說已經達到預期了,因爲畢竟也沒有花很長時間和很大的精力來做這個比賽。現在放上部分代碼以及本人的一些思考。

直接從複賽賽題開始吧,因爲複賽的賽題和初賽其實區別不大。代碼很大程度上參考了github上的https://github.com/Root-lee/Put_Flavors_SA和https://github.com/lipiji/JRNN,在此非常感謝!

賽題:http://codecraft.devcloud.huaweicloud.com/home/detail

這次比賽無非是分爲預測和放置部分。

一、放置部分

放置部分無非是一個尋找最優解的過程。尋找最優解無非是遺傳、蟻羣和模擬退火三大算法。窮舉法由於計算時間和資源的限制是不可能採用的,而首次自適應也得不到很好的結果。我們最終採用的是模擬退火算法。模擬退火算法的思想是,每次隨機出一個解,不斷用隨機出的更優解來代替上一個解。而在搜索過程中,賦予一種逐漸趨近於零的概率突跳性,避免陷入局部極小。這樣就能在全局的範圍內逐步接近全局的最優解。這就很像大自然中的固體退火過程。一個溫度很高的固體,它的粒子結構是不穩定的,無序的。然而在慢慢降溫的過程中,粒子卻可以逐漸接近有序,最後在常溫時達到某種穩定的狀態。現在把代碼和解析放上來,如下:

1、定義的虛擬機和服務器類。這裏應該很好理解。每個虛擬機都有自己的名字和內存、CPU資源,每個服務器也有一個自己的名字、總資源和剩餘資源的屬性。

class Flavor {
	public String name;
	public int mem;
	public int cpu;
	public Flavor(String name,int mem,int cpu){
		this.name=name;
		this.mem=mem;
		this.cpu=cpu;
	}
}
class Server {
	public ArrayList<Flavor> flavors=new ArrayList<Flavor>();
	public String name;
	public int total_mem;
	public int total_cpu;
	public int free_mem;
	public int free_cpu;
	public Server(String name,int mem,int cpu){
		this.name=name;
		total_cpu=cpu;
		total_mem=mem;
		free_cpu=cpu;
		free_mem=mem;
	}
	public boolean put_flavor(Flavor flavor){
		if(this.free_cpu>=flavor.cpu&&this.free_mem>=flavor.mem){
			this.free_cpu-=flavor.cpu;
			this.free_mem-=flavor.mem;
			this.flavors.add(new Flavor(flavor.name, flavor.mem, flavor.cpu));
			return true;
		}
		else
			return false;
	}
}
2、一些使用的輔助判斷函數:判斷分數、判斷某種類型虛擬機的內存和CPU資源是多少。
        public static double judgeScore(ArrayList<Server> serverList){
		double score=0;
		double servercpusum=0,servermemsum=0,flavorcpusum=0,flavormemsum=0;
		for(int i=0;i<serverList.size();i++){
			servercpusum+=serverList.get(i).total_cpu;
			servermemsum+=serverList.get(i).total_mem;
			flavorcpusum+=serverList.get(i).free_cpu;
			flavormemsum+=serverList.get(i).free_mem;
		}
		flavorcpusum=servercpusum-flavorcpusum;
		flavormemsum=servermemsum-flavormemsum;
		score=(flavorcpusum/servercpusum+flavormemsum/servermemsum)/2;
		return score;
	}
	public static int judgeCPU(String s) {//返回虛擬機CPU的消耗
		if(s.equals("flavor1")||s.equals("flavor2")||s.equals("flavor3"))
			return 1;
		else if(s.equals("flavor4")||s.equals("flavor5")||s.equals("flavor6"))
			return 2;
		else if(s.equals("flavor7")||s.equals("flavor8")||s.equals("flavor9"))
			return 4;
		else if(s.equals("flavor10")||s.equals("flavor11")||s.equals("flavor12"))
			return 8;
		else if(s.equals("flavor13")||s.equals("flavor14")||s.equals("flavor15"))
			return 16;
		else if(s.equals("flavor16")||s.equals("flavor17")||s.equals("flavor18"))
			return 32;
		else 
			return 0;
	}
	public static int judgeMemory(String s) {//返回虛擬機Memory的消耗
		if(s.equals("flavor1"))
			return 1;
		else if(s.equals("flavor2")||s.equals("flavor4"))
			return 2;
		else if(s.equals("flavor3")||s.equals("flavor5")||s.equals("flavor7"))
			return 4;
		else if(s.equals("flavor6")||s.equals("flavor8")||s.equals("flavor10"))
			return 8;
		else if(s.equals("flavor9")||s.equals("flavor11")||s.equals("flavor13"))
			return 16;
		else if(s.equals("flavor12")||s.equals("flavor14")||s.equals("flavor16"))
			return 32;
		else if(s.equals("flavor15")||s.equals("flavor17"))
			return 64;
		else if(s.equals("flavor18"))
			return 128;
		else
			return 0;
	}

3、模擬退火部分。在放置的時候,想象一下,對於一個最優解,爲了組合出最優解,無非是兩個考慮:第一,此時我應該用什麼型號的服務器來放置?第二,此時應該放哪個虛擬機進去?所以,爲了有效進行模擬退火,需要設置兩個隨機的值。這裏的模擬退火,是與首次自適應結合的。也就是,每次都生成一個新服務器,每次都從服務器列表的第一個服務器開始詢問是否可以放置。但是,進行詢問的虛擬機和新生成的服務器是隨機產生的。

	public static ArrayList<Server> putFlavor(ArrayList<Integer> predictFlavorList,String[] serverType,Integer[] serverCPU,Integer[] serverMEM,Double[] everyserverp){//輸入的參數是用arraylist表示的預測出來的flavor,0爲總數,1-18爲數量
	//然後是服務器和其參數的列表
		ArrayList<String> serverlist=new ArrayList<String>();
		ArrayList<Integer> servercpulist=new ArrayList<Integer>();
		ArrayList<Integer> servermemlist=new ArrayList<Integer>();
		for(int i=0;i<serverType.length;i++) {//把可以使用的服務器類型加進去
			serverlist.add(serverType[i]);
			servercpulist.add(serverCPU[i]);
			servermemlist.add(serverMEM[i]);
		}
		
		ArrayList<Flavor> vec_flavors=new ArrayList<Flavor>();//預測出來的虛擬機全部放進去
		//預測出的虛擬機共有18種,PredictFlavorList的index爲0是總數,下標1-18爲個數,值-1爲不需要預測的種類
		for(int i=1;i<=18;i++){
			if(predictFlavorList.get(i)>0){
				int num=predictFlavorList.get(i);
				for(int j=0;j<num;j++)
					vec_flavors.add(new Flavor("flavor"+i, judgeMemory("flavor"+i), judgeCPU("flavor"+i)));
			}
		}
		
		double referencescore=0;//評價分數
		
		ArrayList<Server> res_servers=new ArrayList<Server>();//存放最好結果
		double T=150;//初始溫度
		double Tmin=1e-6;//停止溫度
		double r=0.999;//溫度下降係數
		
		ArrayList<Integer> diceflavor=new ArrayList<Integer>();//骰子,每次隨機投擲,取predict_flavors前兩個變量作爲每次退火需要交換順序的虛擬機
		ArrayList<Integer> diceserver=new ArrayList<Integer>();//骰子,每次隨機投擲,決定每次生成什麼樣的服務器
		
		for(int i=0;i<vec_flavors.size();i++)
			diceflavor.add(i);
		for(int i=0;i<serverType.length;i++)
			diceserver.add(i);
		
		int innerjudgeend=0;//內循環用於判斷,如果經過這麼多次沒有接受新解,就退出
		int innnerjudgeendstand=50;//內循環如果連續judgeendstand次沒有接受新解,那就退出
		double judgequitwhilescore=0;
		while(T>Tmin){
			double L=100;//內循環次數
			double kb=1;//kb隨着內循環次數增加,作用於最後的判定是否跳解上
			
			for(;kb<=L;kb++){//每個溫度要迭代L次,看能不能有用
			
			Collections.shuffle(diceflavor);//對dice進行重排,也算是擲骰子,隨機生成初始解
			Collections.shuffle(diceserver);
			
			ArrayList<Flavor> new_vec_flavors=new ArrayList<>(vec_flavors);//用原來的vec_flavor新建一個新的new_vec_flavors
			Collections.swap(new_vec_flavors, diceflavor.get(0), diceflavor.get(1));//交換new_vec_flavors的dice[0]和dice[1]位置
			
			if(serverlist.size()>=2) {
				Collections.swap(serverlist, diceserver.get(0), diceserver.get(1));
				Collections.swap(servercpulist, diceserver.get(0), diceserver.get(1));
				Collections.swap(servermemlist, diceserver.get(0), diceserver.get(1));
			}
			
			ArrayList<Server> servers=new ArrayList<Server>();//先對新的服務器列表初始化一個服務器
			
			for(int i=0;i<new_vec_flavors.size();i++){//進行首次自適應
				int j=0;
				for(;j<servers.size();j++){
					if(servers.get(j).put_flavor(new Flavor(new_vec_flavors.get(i).name, new_vec_flavors.get(i).mem, new_vec_flavors.get(i).cpu)))
						break;
				}
				if(j==servers.size()){//已經超出邊界了,也就是說目前所有的服務器都放不下這個虛擬機了。注意,無論是什麼型號的服務器,它的資源一定比任意一個虛擬機多,因此一定至少能裝下一個虛擬機
					Collections.shuffle(diceserver);//對要新建的服務器又進行一次隨機排序
					double producep=Math.random();
					int newtempi=diceserver.get(0);
					Server tempserver=new Server(serverlist.get(newtempi),servermemlist.get(newtempi),servercpulist.get(newtempi));
					boolean b=tempserver.put_flavor(new Flavor(new_vec_flavors.get(i).name, new_vec_flavors.get(i).mem, new_vec_flavors.get(i).cpu));
					servers.add(tempserver);
				}
			}

			double currentscore=PutServer.judgeScore(servers);
			judgequitwhilescore=currentscore;
			
			if(currentscore>referencescore){//如果分數更高,則保存結果
				referencescore=currentscore;
				res_servers=(ArrayList<Server>)servers.clone();
				vec_flavors=(ArrayList<Flavor>)new_vec_flavors.clone();
				innerjudgeend=0;
			}
			else{//如果分數更高,則以一定概率保存結果,防止優化陷入局部最優解
				//要設定一個終止迭代的條件
				//終止迭代的條件有兩個:一個是,分數超過多少分就終止迭代,一個是連續多少個新解都沒被接收就終止迭代
				innerjudgeend++;
				if(T>=1){//只有溫度比較高的時候,才能進行隨機跳解
					if(Math.exp(-(referencescore-currentscore)/(kb*T))>=(Math.random()/1.0)){
						referencescore=currentscore;
						res_servers=(ArrayList<Server>)servers.clone();
						vec_flavors=(ArrayList<Flavor>)new_vec_flavors.clone();
						
					}
				}
			}
			if(innerjudgeend>=innnerjudgeendstand)
				break;
			}
			
			if(judgequitwhilescore>=0.98)//如果分數達到這麼多分了,就認爲已經達到預期了,爲了防止隨機跳到不那麼優的解,這個時候直接退出
				break;
			T=r*T;
		}
		return res_servers;
	}

在調參的過程中,我們發現這裏有幾點要注意:

1、對於每個不同的測試用例,他們對應的比較優的解,對應了不同的各個參數。因此,需要針對每個不同的用例,進行調參。可以調節的參數有很多,例如初始溫度、溫度下降係數、停止溫度、內循環次數、內循環退出次數等,甚至改變調解條件。因此,這就是一個漫長的調參過程。

2、最終的複賽用例,經過我們的推斷(可以通過在程序末尾設置拋出異常的條件,如果達到了某個放置的分數,就拋出異常終止程序運行,這樣就可以在提交代碼後的運行報告中推斷出自己的放置部分分數。當然前面預測部分同理),複賽多個用例放置部分的分數應該在95分左右。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章