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

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 failed
			return 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)这样一个自我指涉的式子,于是它就自动地运转起来,不需要你用循环人为地推动它。把人脑从繁琐的程序执行流程中解放了出来《回溯法与递归 C++中动态的二维数组》


递归不像循环,先不考虑其实现细节,先定义好 子问题,利用子问题的结果,设计求解原问题的算法,即得到递推关系式(把子问题看做已经完成的方案)。有的问题明显蕴含着递归表达式,比如阶乘在数学上的定义,子问题、递推关系式都是显然的。然而有的问题不这样显而易见,我们得自己定义 子问题,比如汉诺塔。基础是分治法,把原问题分解为子问题(意味着问题的规模更小,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)的接口。然后你就知道子问题以及子问题的接口定义的合不合适。

    原文作者:回溯法
    原文地址: https://blog.csdn.net/u010476094/article/details/42076881
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞