回溯法与递归 C++中动态的二维数组

来源:互联网 发布:淘宝被盗后果 编辑:程序博客网 时间:2024/05/17 07:00

leetcode的题目,求出数独游戏的一个解。

之前记得求解 全排列和八皇后问题也是用的回溯法。

分治法可以用递归是很显然的,它们俩是天生一对;回溯法也可以。因为 试探求解 子问题

下面代码是我用递归回溯求解数独的一个解,6个测试用例,184ms,在C++分布段上处在中间,还行吧,以后有时间再研究。

 

关键是学到了一种方便地表示动态二维数组的新方法。一维的动态数组 就用new[]、vector、deque。二维的呢?

  1、旧方法,比如:

int* baseAddr=new int[3*2]; // 3 行 2 列typedef int int2 [2];int2 * p=(int2 *)baseAddr;for(int i=0;i<3;i++){for(int j=0;j<2;j++){p[i][j]=i*2+j;}}delete [] baseAddr;

  2、新方法,比如这道求解数独的题目:
#include <vector>using namespace std;void solveSudoku(vector<vector<char> > &board);bool tillNow_boardIsValid_Try_thisNumber( vector<vector<char> > & board, int i, int j, char c);inline bool checkValid( vector<vector<char> > & board, int i, int j, char c );int main(){char* sd[]={"..9748...","7........",".2.1.9...","..7...24.",".64.1.59.",".98...3..","...8.3.2.","........6","...2759.."};vector<vector<char>> bd;for(int i=0;i<9;i++)bd.push_back(vector<char>(sd[i],sd[i]+9));solveSudoku(bd);return 0;}void solveSudoku(vector<vector<char> > &board) {for(int i=0;i<9;i++){for(int j=0;j<9;j++){if(board[i][j]!='.') {continue;}for (char c='1';c<='9';c++){if(tillNow_boardIsValid_Try_thisNumber(board,i,j,c)) {return;}}return;}}}bool tillNow_boardIsValid_Try_thisNumber( vector<vector<char> > & board, int i, int j, char c) {// the board is left unmodified if it returns false.// the board has become the answer when it returns true.if(checkValid(board,i,j,c)==false) return false;board[i][j]=c; // try int ni=i;int nj=j+1;if(nj==9) {ni++;nj=0;}if(ni==9) {return true;}for(;ni<9;ni++){for(;nj<9;nj++){if(board[ni][nj]!='.') {continue;}for (char cc='1';cc<='9';cc++){if(tillNow_boardIsValid_Try_thisNumber(board,ni,nj,cc)) {return true;}}board[i][j]='.'; // The attempt ( board[i][j]=c ) has failedreturn false;}nj=0;}return true;}inline bool checkValid( vector<vector<char> > & board, int i, int j, char c ) {for(int k=0;k<9;k++){if(board[i][k]=='.') {continue;}else if(board[i][k]==c) {return false;}}for(int k=0;k<9;k++){if(board[k][j]=='.') {continue;}else if(board[k][j]==c) {return false;}}int ROW=i/3*3;int COLUMN=j/3*3;for (int row=0;row<3;row++){for (int column=0;column<3;column++){if(board[ROW+row][COLUMN+column]=='.') {continue;}else if(board[ROW+row][COLUMN+column]==c) {return false;}}}return true;}

第一个关键 写递归程序,就是写出递推关系式 的过程,比如求斐波那契数Fn:

F1=1,F2=1,F3=2,F4=3,F5=5,……Fn=?
不想去求Fn关于n的数学函数或者迭代过程:Fn= Φ(n) or Fn={ ... repeat a  procedure ...} 

而是得到 Fn=F(n-1)+F(n-2)这样一个自我指涉的式子,于是它就自动地运转起来,不需要你用循环人为地推动它。把人脑从繁琐的程序执行流程中解放了出来得意


递归不像循环,先不考虑其实现细节,先定义好 子问题,利用子问题的结果,设计求解原问题的算法,即得到递推关系式(把子问题看做已经完成的方案)。有的问题明显蕴含着递归表达式,比如阶乘在数学上的定义,子问题、递推关系式都是显然的。然而有的问题不这样显而易见,我们得自己定义 子问题,比如汉诺塔。基础是分治法,把原问题分解为子问题(意味着问题的规模更小,n 更小),求解子问题,合并结果。回溯法的子问题就是试探求解。如何求解子问题呢?哈,“不用求”。因为递归就是要找 F( n ) 与 F (n-1) 的关系等式,不是求出F(n)=Φ(n)。Φ ()代表某一确定的,已知的过程,不像F()是未知的过程。


第二个关键 明确定义好 F(n) or F(n-1) 的功能接口,一丁点模糊的地方都不行。F(n-1) 执行前是什么样,执行中改变了什么(执行后变成什么样),必须完全确定下来。不过往往不是一步到位的,要看“利用子问题的结果设计求解原问题的算法”时的需求。定义F(n-1) 的功能接口利用F(n-1),设计求解F(n)的算法两者是相关的(因为F(n)  F(n-1) 要有相同的接口)。我一般按所问定义F(n-1) 的接口,利用它定义的F(n-1)所具有的功能,设计求解F(n)的算法,即去实现F(n)的接口。然后你就知道子问题以及子问题的接口定义的合不合适。

0 0
原创粉丝点击