回溯法 或dfs 或深度優先 題總結

回溯法總結
一道數字到字符串的題,將數字映射到手機的字符串上,是編程之美版本的簡略版,簡明的DFS,其實我更願意用backtrack framework來做,說成是回溯法,本質沒有區別
vector<string> numtoletter;
void f(vector<char>& strvec, int selectn, int n,string digits, vector<string> numtoletter, vector<string>& allcombinations)
{
	if(selectn==n)
	{
		string str;
		for(int i=0;i<n;i++)
			str+=strvec.at(i);
		allcombinations.push_back(str);
	}
	else
	{
		for(int i=0;i< numtoletter.at(digits.at(selectn)-'2').length();i++)// if 1?
		{
			strvec.push_back(numtoletter.at(digits.at(selectn)-'2').at(i));
			f(strvec,selectn+1,n,digits,numtoletter,allcombinations);
			strvec.pop_back();
			//f(strvec,selectn+1,n,digits,numtoletter,allcombinations);
		}
	}
}
vector<string> letterCombinations(string digits) {
	numtoletter.push_back("abc");
	numtoletter.push_back("def");
	numtoletter.push_back("ghi");
	numtoletter.push_back("jkl");
	numtoletter.push_back("mno");
	numtoletter.push_back("pqrs");
	numtoletter.push_back("tuv");
	numtoletter.push_back("wxyz");
	vector<char> strvec;
	vector<string> allcombinations;
	f(strvec,0,digits.size(),digits,numtoletter,allcombinations);
	return allcombinations;
}




期間犯了一個錯誤,就是for裏面多寫了一個f(),結果導致out of memory等奇怪錯誤。 主要是受了candidate有兩個情況,直接寫成兩個,外加用select bool數組將unmake 和下一輪的make重合在一起,於是下意識的多寫了一個f() 而沒有仔細思考。
其實一般框架都是一個for裏面先逐個make candidate,然後遞歸,然後unmake


外加一道子集和問題,返回給定集合S 的所有子集,典型的回溯。其實感覺這個就是有模板的,但是如果情況複雜些,我還是改起來有點費勁= =
void subsets_recur(bool *select, vector<int> S, vector<vector<int>>&intvec, int selectn, int n)
{
	if(selectn==n)
	{
		vector<int> vec;
		for(int i=0;i<S.size();i++)
		{
			if(select[i]==true)
				vec.push_back(S.at(i));
		}
		intvec.push_back(vec);
	}
	else
	{


		select[selectn]=false;//not select
		subsets_recur(select,S,intvec,selectn+1,n);
		select[selectn]=true;
		subsets_recur(select,S,intvec,selectn+1,n);
	}
}
vector<vector<int> > subsets(vector<int> &S) {
	bool *select=new bool[S.size()];
	vector<vector<int>> intvec;
	sort(S.begin(),S.end());
	subsets_recur(select,S,intvec,0,S.size());
	delete[] select;
	return intvec;
}


打印 C(n,k)的所有情況,裏面多一個k,於是對應多一個selectk表示當前已經選了多少變量了,好像遞歸出口如果寫成下面這個會有問題,
具體還沒分析出爲啥= =
if(selectk==k)
	{
		string str;
		for(int i=0;i<n;i++)
			if(select[i]==true)
				str+='1';
			else
				str+='0';
		//cout<<endl;
		numset.push_back(str);
	}
	else if(selectn==n)
		;
	else
	{
		select[selectn]=false;
		SubSetNum(numset,select,selectk,k,selectn+1,n);
		select[selectn]=true;
		SubSetNum(numset,select,selectk+1,k,selectn+1,n);
	}


C(n,k)代碼
void Combinations(bool* select, vector<vector<int>> &vecset,int selectk, int k, int selectn, int n)
{
	if(k>n) return ;
	if(selectn==n)
	{
		if(selectk==k)//proning
		{
			vector<int> vec;
			for(int i=0;i<n;i++)
			{
				if(select[i]==true)
					vec.push_back(i+1);
			}
			vecset.push_back(vec);
		}
	}
	else
	{
		select[selectn]=false;
		Combinations(select, vecset,selectk,k,selectn+1,n);
		select[selectn]=true;
		Combinations(select, vecset,selectk+1,k,selectn+1,n);
	}
}
vector<vector<int> > combine(int n, int k)
{
	bool* select=new bool[n];
	memset(select,0,sizeof(bool)*n);
	vector<vector<int>> vecset;
	Combinations(select,vecset,0,k,0,n);
	delete [] select;
	return vecset;
}





總結:回溯法其實就是你for循環要做,但是不知道多少層for循環的時候,或者是解指數(子集樹)或者階乘級(排列樹),然後裏面涉及的每個當前解的變量要麼數組傳遞,遞歸調用共享訪問,要麼vector引用傳遞保持遞歸調用一致,要麼全局變量。
然後記錄全局變量的也是如此,保持全局一致,如果作爲遞歸參數千萬不要值傳遞,導致每個遞歸棧保存的都是一個copy而不是訪問全局的一個。然後遞歸出口的時候一定要把情況羅列清楚,多個條件邏輯與或分析能力。


本質是for遍歷每個數,make, recursive, unmake 不要多加recursive導致和  01選擇的混淆

但是如果弄個有重複數字的全排列,我就搞不清了,還有重複數字的組合數= =


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