回溯算法

引言
寻找问题的解的一种可靠的方法是首先列出所有候选解,然后依次检查每一个,在检查完所有或部分候选解后,即可找到所需要的解。理论上,当候选解数量有限并且通过检查所有或部分候选解能够得到所需解时,上述方法是可行的。不过,在实际应用中,很少使用这种方法,因为候选解的数量通常都非常大(比如指数级,甚至是大数阶乘),即便采用最快的计算机也只能解决规模很小的问题。对候选解进行系统检查的方法有多种,其中回溯和分枝定界法是比较常用的两种方法。按照这两种方法对候选解进行系统检查通常会使问题的求解时间大大减少(无论对于最坏情形还是对于一般情形)。事实上,这些方法可以使我们避免对很大的候选解集合进行检查,同时能够保证算法运行结束时可以找到所需要的解。因此,这些方法通常能够用来求解规模很大的问题。

算法思想
回溯(backtracking)是一种系统地搜索问题解答的方法。为了实现回溯,首先需要为问题定义一个解空间(solution space),这个空间必须至少包含问题的一个解(可能是最优的)。

下一步是组织解空间以便它能被容易地搜索。典型的组织方法是图(迷宫问题)或树(N皇后问题)。
一旦定义了解空间的组织方法,这个空间即可按深度优先的方法从开始节点进行搜索。

回溯方法的步骤如下:
1) 定义一个解空间,它包含问题的解。
2) 用适于搜索的方式组织该空间。
3) 用深度优先法搜索该空间,利用限界函数避免移动到不可能产生解的子空间。
回溯算法的一个有趣的特性是在搜索执行的同时产生解空间。在搜索期间的任何时刻,仅保留从开始节点到当前节点的路径。因此,回溯算法的空间需求为O(从开始节点起最长路径的长度)。这个特性非常重要,因为解空间的大小通常是最长路径长度的指数或阶乘。所以如果要存储全部解空间的话,再多的空间也不够用。

算法应用
回溯算法的求解过程实质上是一个先序遍历一棵"状态树"的过程,只是这棵树不是遍历前预先建立的,而是隐含在遍历过程中<<数据结构>>(严蔚敏).


(3)N皇后问题:
在一个N*N的棋盘上放置N个皇后,且使得每两个之间不能互相攻击,也就是使得每两个不在同一行,同一列和同一斜角线上。
对于N=1,问题的解很简单,而且我们很容易看出对于N=2和N=3来说,这个问题是无解的。所让我们考虑4皇后问题并用回溯法对它求解。因为每个皇后都必须分别占据—行,我们需要做的不过是为图1棋盘上的每个皇后分配一列。

回溯算法
我们从空棋盘开始,然后把皇后1放到它所在行的第一个可能位置上,也就是第一行第—列。对于皇后2,在经过第一列和第二列的失败尝试之后,我们把它放在第一个可能的位置,就是格子〔2,3),位于第二行第二列的格子。这被证明是一个死胡同,因为皇后:将没有位置可放。所以,该算法进行回溯,把皇后2放在下一个可能位置(2,4)上。然后皇后3就可以放在(3,2),这被证明是另一个死胡同。该算法然后就回溯到底,把皇后1移到(1,2)。接着皇后2到(2,4),皇后3到(3,1),而皇后4到(4,3),这就是该问题的一个解。图2给出了这个查找的状态空间树。
回溯算法
程序如下:

回溯算法#include<stdio.h>
回溯算法#include<math.h>
回溯算法#defineN4
回溯算法intcol[N+1];
回溯算法//输出结果
回溯算法voidOutput()
回溯算法{
回溯算法for(inti=1;i<=N;i++)
回溯算法{
回溯算法printf("(%d,%d)\n",i,col[i]);
回溯算法}
回溯算法printf("\n");
回溯算法}
回溯算法//求解函数
回溯算法voidQueen(inti,intn)
回溯算法{
回溯算法if(i>n)
回溯算法Output();
回溯算法else
回溯算法{
回溯算法for(intj=1;j<=n;++j)
回溯算法{
回溯算法intk=1;
回溯算法col[i]=j;
回溯算法while(k<i)
回溯算法{
回溯算法if((col[k]-col[i])*(fabs(col[k]-col[i])-fabs(k-i))!=0)
回溯算法{
回溯算法k++;
回溯算法if(k==i)
回溯算法Queen(i+1,n);
回溯算法}
回溯算法else
回溯算法{
回溯算法break;
回溯算法}
回溯算法}
回溯算法}
回溯算法}
回溯算法}
回溯算法intmain()
回溯算法{
回溯算法printf("theansweris:\n");
回溯算法for(inti=1;i<=N;i++)
回溯算法{
回溯算法col[1]=i;//设置第一行
回溯算法Queen(2,N);
回溯算法}
回溯算法}

结果如下:
回溯算法