回溯算法(1):八皇后问题
#include
"
stdafx.h
"
/*
算法系列---回溯算法
引言
寻找问题的解的一种可靠的方法是首先列出所有候选解,然后依次检查每一个,在检查完所有或部分候选解后,即可找到所需要的解。理论上,当候选解数量有限并且通过检查所有或部分候选解能够得到所需解时,上述方法是可行的。不过,在实际应用中,很少使用这种方法,因为候选解的数量通常都非常大(比如指数级,甚至是大数阶乘),即便采用最快的计算机也只能解决规模很小的问题。对候选解进行系统检查的方法有多种,其中回溯和分枝定界法是比较常用的两种方法。按照这两种方法对候选解进行系统检查通常会使问题的求解时间大大减少(无论对于最坏情形还是对于一般情形)。事实上,这些方法可以使我们避免对很大的候选解集合进行检查,同时能够保证算法运行结束时可以找到所需要的解。因此,这些方法通常能够用来求解规模很大的问题。
算法思想
回溯(backtracking)是一种系统地搜索问题解答的方法。为了实现回溯,首先需要为问题定义一个解空间(solution space),这个空间必须至少包含问题的一个解(可能是最优的)。
下一步是组织解空间以便它能被容易地搜索。典型的组织方法是图(迷宫问题)或树(N皇后问题)。
一旦定义了解空间的组织方法,这个空间即可按深度优先的方法从开始节点进行搜索。
回溯方法的步骤如下:
1) 定义一个解空间,它包含问题的解。
2) 用适于搜索的方式组织该空间。
3) 用深度优先法搜索该空间,利用限界函数避免移动到不可能产生解的子空间。
回溯算法的一个有趣的特性是在搜索执行的同时产生解空间。在搜索期间的任何时刻,仅保留从开始节点到当前节点的路径。因此,回溯算法的空间需求为O(从开始节点起最长路径的长度)。这个特性非常重要,因为解空间的大小通常是最长路径长度的指数或阶乘。所以如果要存储全部解空间的话,再多的空间也不够用。
算法应用
回溯算法的求解过程实质上是一个先序遍历一棵"状态树"的过程,只是这棵树不是遍历前预先建立的,而是隐含在遍历过程中<<数据结构>>(严蔚敏).
回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。
回溯法是一个既带有系统性又带有跳跃性的搜索算法。它在包含问题的所有解的解空间树中,按照深度优先的策略,从根结点出发搜索解的空间树。算法搜索至解的空间树的任一结点时,总是先判断该结点是否肯定不包含问题的解。如果肯定不包含,则跳过对以该结点为根的子树的系统搜索,逐层向其祖先结点回溯。否则,进入该子树,继续按深度优先的策略进行搜索。回溯法在用来求问题的所有解时,要回溯到根,且根结点的所有子树都已被搜索遍才结束。而回溯法在用来求问题的任一解时,只要搜索到问题的一个解就可以结束。这种以深度优先的方式系统地搜索问题的解的算法称为回溯法,它适用于解一些组合数较大的问题。
算法框架:
1、问题的解空间:应用回溯法解问题时,首先应明确定义问题的解空间。问题的解空间应至少包含一个(最优)解。
2、回溯法的基本思想:确定了解空间的组织结构后,回溯法就从开始结点(根结点)出发,以深度优先的方式搜索整个解空间,这个开始结点就成为一个活结点,同时也成为当前的扩展结点。在当前的扩展结点处,搜索向纵深方向移至一个新结点,这个新结点就成为一个新的活结点,并成为当前扩展结点。如果在当前的扩展结点处不能再向纵深方向移动,则当前扩展结点就成为死结点。换句话说,这个结点不再是一个活结点。此时,应往回移动(回溯)至最近的一个活结点处,并使这个活结点成为当前的扩展结点。回溯法即以这种工作方式递归地在解空间中搜索,直至找到所要求的解或解空间中已没有活结点时为止。
运用回溯法解题通常包含以下三个步骤:
(1)针对所给问题,定义问题的解空间;
(2)确定易于搜索的解空间结构;
(3)以深度优先的方式搜索解空间,并且在搜索过程中用剪枝函数避免无效搜索。
3、递归回溯:由于回溯法是对解的空间的深度优先搜索,因此在一般情况下可用递归函数来实现回溯法如下:
void backtrace(int i)
{
for (int j=下界;j<上界;j++)
{
matrix[i] = j;
//可行{满足限界函数和约束条件}
if ( 可行())
{
//置值
if (i>n)
{
//中止搜索并输出结果;
}
backtrace(i+1);
}
}
}
说明:
i是递归深度;
n是深度控制,即解空间树的高度;
可行性判断有两方面的内容:
①不满约束条件则剪去相应子树;
②若限界函数越界,也剪去相应子树;
③两者均满足则进入下一层;
搜索:全面访问所有可能的情况,分为两种:不考虑给定问题的特有性质,按事先设好的顺序,依次运用规则,即盲目搜索的方法;另一种则考虑问题给定的特有性质,选用合适的规则,提高搜索的效率,即启发式的搜索。
*/
// 八皇后问题
// 解空间
static int matirx[ 8 ] = { 0 };
static int count = 0 ;
void display()
{
int row;
int col;
printf( " \r\n======%02d=======\n " ,count);
for (row = 0 ; row < 8 ; row ++ )
{
for (col = 0 ; col < matirx[row]; col ++ )
printf( " * " );
printf( " # " );
for (col = matirx[row] + 1 ; col < 8 ; col ++ )
printf( " * " );
printf( " \n " );
}
}
bool place( int row, int col)
{
int prev;
int data;
for (prev = 0 ; prev < row; prev ++ ){
data = matirx[prev];
// 同一列
if (col == data)
return false ;
// 左斜线
if ((prev - row) == (col - data) )
return false ;
// 右斜线
if ((row - prev) == (col - data))
return false ;
}
return true ;
}
void queen( int row)
{
int col;
for (col = 0 ;col < 8 ;col ++ )
{
if (place(row,col))
{
matirx[row] = col;
if (row >= 7 )
{
count ++ ,display();
// matirx[row]=0;
return ;
}
queen(row + 1 );
// matirx[row]=0;
}
}
}
int main( void )
{
queen( 0 );
system( " pause " );
return 0 ;
}
/*
算法系列---回溯算法
引言
寻找问题的解的一种可靠的方法是首先列出所有候选解,然后依次检查每一个,在检查完所有或部分候选解后,即可找到所需要的解。理论上,当候选解数量有限并且通过检查所有或部分候选解能够得到所需解时,上述方法是可行的。不过,在实际应用中,很少使用这种方法,因为候选解的数量通常都非常大(比如指数级,甚至是大数阶乘),即便采用最快的计算机也只能解决规模很小的问题。对候选解进行系统检查的方法有多种,其中回溯和分枝定界法是比较常用的两种方法。按照这两种方法对候选解进行系统检查通常会使问题的求解时间大大减少(无论对于最坏情形还是对于一般情形)。事实上,这些方法可以使我们避免对很大的候选解集合进行检查,同时能够保证算法运行结束时可以找到所需要的解。因此,这些方法通常能够用来求解规模很大的问题。
算法思想
回溯(backtracking)是一种系统地搜索问题解答的方法。为了实现回溯,首先需要为问题定义一个解空间(solution space),这个空间必须至少包含问题的一个解(可能是最优的)。
下一步是组织解空间以便它能被容易地搜索。典型的组织方法是图(迷宫问题)或树(N皇后问题)。
一旦定义了解空间的组织方法,这个空间即可按深度优先的方法从开始节点进行搜索。
回溯方法的步骤如下:
1) 定义一个解空间,它包含问题的解。
2) 用适于搜索的方式组织该空间。
3) 用深度优先法搜索该空间,利用限界函数避免移动到不可能产生解的子空间。
回溯算法的一个有趣的特性是在搜索执行的同时产生解空间。在搜索期间的任何时刻,仅保留从开始节点到当前节点的路径。因此,回溯算法的空间需求为O(从开始节点起最长路径的长度)。这个特性非常重要,因为解空间的大小通常是最长路径长度的指数或阶乘。所以如果要存储全部解空间的话,再多的空间也不够用。
算法应用
回溯算法的求解过程实质上是一个先序遍历一棵"状态树"的过程,只是这棵树不是遍历前预先建立的,而是隐含在遍历过程中<<数据结构>>(严蔚敏).
回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。
回溯法是一个既带有系统性又带有跳跃性的搜索算法。它在包含问题的所有解的解空间树中,按照深度优先的策略,从根结点出发搜索解的空间树。算法搜索至解的空间树的任一结点时,总是先判断该结点是否肯定不包含问题的解。如果肯定不包含,则跳过对以该结点为根的子树的系统搜索,逐层向其祖先结点回溯。否则,进入该子树,继续按深度优先的策略进行搜索。回溯法在用来求问题的所有解时,要回溯到根,且根结点的所有子树都已被搜索遍才结束。而回溯法在用来求问题的任一解时,只要搜索到问题的一个解就可以结束。这种以深度优先的方式系统地搜索问题的解的算法称为回溯法,它适用于解一些组合数较大的问题。
算法框架:
1、问题的解空间:应用回溯法解问题时,首先应明确定义问题的解空间。问题的解空间应至少包含一个(最优)解。
2、回溯法的基本思想:确定了解空间的组织结构后,回溯法就从开始结点(根结点)出发,以深度优先的方式搜索整个解空间,这个开始结点就成为一个活结点,同时也成为当前的扩展结点。在当前的扩展结点处,搜索向纵深方向移至一个新结点,这个新结点就成为一个新的活结点,并成为当前扩展结点。如果在当前的扩展结点处不能再向纵深方向移动,则当前扩展结点就成为死结点。换句话说,这个结点不再是一个活结点。此时,应往回移动(回溯)至最近的一个活结点处,并使这个活结点成为当前的扩展结点。回溯法即以这种工作方式递归地在解空间中搜索,直至找到所要求的解或解空间中已没有活结点时为止。
运用回溯法解题通常包含以下三个步骤:
(1)针对所给问题,定义问题的解空间;
(2)确定易于搜索的解空间结构;
(3)以深度优先的方式搜索解空间,并且在搜索过程中用剪枝函数避免无效搜索。
3、递归回溯:由于回溯法是对解的空间的深度优先搜索,因此在一般情况下可用递归函数来实现回溯法如下:
void backtrace(int i)
{
for (int j=下界;j<上界;j++)
{
matrix[i] = j;
//可行{满足限界函数和约束条件}
if ( 可行())
{
//置值
if (i>n)
{
//中止搜索并输出结果;
}
backtrace(i+1);
}
}
}
说明:
i是递归深度;
n是深度控制,即解空间树的高度;
可行性判断有两方面的内容:
①不满约束条件则剪去相应子树;
②若限界函数越界,也剪去相应子树;
③两者均满足则进入下一层;
搜索:全面访问所有可能的情况,分为两种:不考虑给定问题的特有性质,按事先设好的顺序,依次运用规则,即盲目搜索的方法;另一种则考虑问题给定的特有性质,选用合适的规则,提高搜索的效率,即启发式的搜索。
*/
// 八皇后问题
// 解空间
static int matirx[ 8 ] = { 0 };
static int count = 0 ;
void display()
{
int row;
int col;
printf( " \r\n======%02d=======\n " ,count);
for (row = 0 ; row < 8 ; row ++ )
{
for (col = 0 ; col < matirx[row]; col ++ )
printf( " * " );
printf( " # " );
for (col = matirx[row] + 1 ; col < 8 ; col ++ )
printf( " * " );
printf( " \n " );
}
}
bool place( int row, int col)
{
int prev;
int data;
for (prev = 0 ; prev < row; prev ++ ){
data = matirx[prev];
// 同一列
if (col == data)
return false ;
// 左斜线
if ((prev - row) == (col - data) )
return false ;
// 右斜线
if ((row - prev) == (col - data))
return false ;
}
return true ;
}
void queen( int row)
{
int col;
for (col = 0 ;col < 8 ;col ++ )
{
if (place(row,col))
{
matirx[row] = col;
if (row >= 7 )
{
count ++ ,display();
// matirx[row]=0;
return ;
}
queen(row + 1 );
// matirx[row]=0;
}
}
}
int main( void )
{
queen( 0 );
system( " pause " );
return 0 ;
}