递归之我见

最近递归看多了,貌似也看出花样了,今天来分门别类的看看。

首页要明白,需要求得所有解的时候,不用多想,用递归!虽然大多数递归会比较耗时,但是这里用其他的思路一样耗时,谁让解空间那么大呢,呵呵。

一、求排列

无话可说,直接递归呈上,那么递归函数需要哪些参数呢?看下面。

permutation(int a[], int out, int used[], int len, int lev); 其中

        out[] -- 表示结果数组

        used[] -- 表示还有哪些元素没有被枚举到,这个参数可以说是排列枚举中独有的,回溯时不要忘记恢复原值!

        len, lev -- 就是枚举中很常见的常数了,一个是限长,一个是当前需要枚举的位置。

下面给出求字符串所有排列的代码。

void permutate(char str[]){
	int len=strlen(str);
	char out[len+1];
	int used[len];
	for(int i=0;i<len;i++) used[i]=0;
	out[len]='\0';
	doPermutate(str, out, used, len, 0);
}

void doPermutate(char str[], char out[], int used[], int len, int lev){
	if(lev==len){
		cout<<out<<endl; //out已经被填充满了	
		return; 
	}	
	for(int i=0;i<len;i++){  //枚举当前level上的候选集 
		if(used[i]==0){
			out[lev]=str[i];	
			used[i]=1;
			doPermutate(str,out,used,len,lev+1);
			used[i]=0;  //用过了就恢复原状,因为要找出所有的情况 
		}  
	} 
}
二、求所有子集合

较上面的排列不同的是,子集合的结果长度是变化的,即这里不能再用一个len来表示结果的长度了,那么怎么解决这个问题呢?

自然而然,我们可以想到下面的代码

class Solution {
public:
    vector<vector<int> > subsets(vector<int> &S) {
        vector<vector<int> > res;
        vector<int> v;
        doSubsets(S, res, v, 0);
        return res;
    }
    
    void doSubsets(vector<int> &S, vector<vector<int> > & res, vector<int>& v, int lev){
        res.push_back(v);               //结果集不用等到lev==S.size()再处理
        //if(lev==S.size()) return;
        for(int i=lev;i<S.size();i++){  //i=lev来控制枚举方向,当前位向右
            v.push_back(S[i]);
            doSubsets(S, res, v, i+1);  //lev=i+1表示当前位置的下一位!
            v.pop_back();
        }
    }
};

其中,思路是逐个把每个元素开头的所有集合都枚举完,例如 {1, 2, 3}的子集{1} {1,2}, {1,2, 3}, {2}, {2,3}, {3}。这里很重要的是

1,结果集不用等到lev==S.size()再处理,和排列枚举不同

2,对于当前lev,枚举的可选值,只能为其后面的值,不能为前面的,故i=lev。

3,下一位要枚举的位置,则为当前选择的位置的下一位,lev=i+1;


如果枚举是按照位置变化的,那么需要需要添加一个新的参数start, 表示下一次枚举的位置,而lev表示当期枚举的位数,最大不超过len。

class Solution {
public:
    vector<vector<int> > subsets(vector<int> &S) {
        vector<vector<int> > res;
        vector<int> v;
        for(int i=0;i<=S.size();i++)
        	doSubsets(S, res, v, i, 0,0);
        return res;
    }
    
    void doSubsets(vector<int> &S, vector<vector<int> > & res, vector<int>& v, int len, int lev, int start){
        if(lev==len){
			res.push_back(v);
			return;
		}
        for(int i=start;i<S.size();i++){
            v.push_back(S[i]);
            doSubsets(S, res, v, len, lev+1, i+1);  //这里lev=lev+1, 而start=i+1;
            v.pop_back();                           //回溯时依然需要将值还原
        }
    }
};

三、搜索最优解

不同于前面说的枚举所有解,这里需要找到所有枚举中“某一个”符合要求的解。例如八皇后问题,数独问题。那么应该对上面的代码如何做相应的修改呢?首先要明确,只有在错误的路径上才需要返回,否则不需要!给出数独的代码

class Solution {
public:
    void solveSudoku(vector<vector<char> >& board) {
    	doSolveSudoku(board);  
    }

    void getCandidates(vector<vector<char> >& board, vector<char>& v, int i, int j){
    	int flag[10];
    	for(int k=1;k<10;k++) flag[k]=k;
    
    	for(int k=0;k<board.size();k++){
    		if(board[i][k]!='.'){ flag[board[i][k]-'0']=0;}
    		if(board[k][j]!='.') flag[board[k][j]-'0']=0;
    	}
    	for(int k1=(i/3)*3;k1<(i/3)*3+3;k1++){
    		for(int k2=(j/3)*3;k2<(j/3)*3+3;k2++){
    			if(board[k1][k2]!='.') 
    			    flag[board[k1][k2]-'0']=0;
    		}	
    	}
    	for(int k=1;k<10;k++)
    		if(flag[k]!=0) v.push_back(k+'0');	
    }

	pair<int, int> findFirstEmpty(const vector< vector<char> >& board) {
        for (int i=0; i<9; ++i)
            for (int j=0; j<9; ++j)
                if (board[i][j] == '.') 
					return make_pair(i, j);
        return make_pair(-1, -1);
    }

    bool doSolveSudoku(vector<vector<char> >& board){
        pair<int, int> pos=findFirstEmpty(board);  //这个一定要放在最前面
    	if(pos.first==board.size() || pos.first==-1 && pos.second==-1)
    		 return true;
		vector<char> candidates;
		candidates.clear();
		getCandidates(board, candidates, pos.first, pos.second);
		for(int k=0;k<candidates.size();k++){
			board[pos.first][pos.second]=candidates[k];
			if(doSolveSudoku(board))  
				return true;
			board[pos.first][pos.second]='.'; //只有不正确才需要回溯!
		}
		return false;	
    }
};






        

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值