算法-- 回溯算法 及 示例

本文详细介绍回溯法这一通用解题方法,包括其基本思想、适用范围及典型问题应用。通过素数环问题实例,展示了如何构造状态空间树,并利用深度优先搜索策略高效解决问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

回溯法介绍

回溯法(英语:backtracking)也称试探法,回溯法有“通用的解题方法”之称。它可以系统的搜索一个问题的所有解或者任意解。

回溯法是一个既带有系统性又带有跳跃性的的搜索算法。它在包含问题的所有解的解空间树中,按照深度优先的策略,从根结点

出发搜索解空间树。算法搜索至解空间树的任一结点时,总是先判断该结点是否肯定不包含问题的解。如果肯定不包含,则跳过

对以该结点为根的子树的系统搜索,逐层向其祖先结点回溯。否则,进入该子树,继续按深度优先的策略进行搜索。回溯法在用来

求问题的所有解时,要回溯到根,且根结点的所有子树都已被搜索遍才结束。而回溯法在用来求问题的任一解时,只要搜索到问题

的一个解就可以结束。这种以深度优先的方式系统地搜索问题的解的算法称为回溯法,它适用于解一些组合数较大的问题.

回溯法通常用最简单的递归方法来实现,在反复重复上述的步骤后可能出现两种情况:

1 .找到一个可能存在的正确的答案。

2. 在尝试了所有可能的分步方法后宣告该问题没有答案。

在最坏的情况下,回溯法会导致一次复杂度为指数时间的计算。

适用范围

在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。

 适用范围:

1,问题的解用向量表示

  X = (x1, x2, ..., xn)

2,需要搜索一个或一组解

3,满足约束条件的最优解


回溯法的基本思想

对于用回溯法求解的问题,首先要将问题进行适当的转化,得出状态空间树。这棵树的每条完整路径都代表了一种解的可能。通过深度优先搜索

这棵树,枚举每种可能的解的情况;从而得出结果。但是,回溯法中通过构造约束函数,可以大大提升程序效率,因为在深度优先搜索的过程中,

不断的将每个解(并不一定是完整的,事实上这也就是构造约束函数的意义所在)与约束函数进行对照从而删除一些不可能的解,这样就不必继续

把解的剩余部分列出从而节省部分时间。

回溯法中,首先需要明确下面三个概念

 1,约束函数:约束函数是根据题意定出的。通过描述合法解的一般特征用于去除不合法的解,从而避免继续搜索出这个不合法解的剩余部分。因此,

       约束函数是对于任何状态空间树上的节点都有效、等价的。

2,状态空间树:刚刚已经提到,状态空间树是一个对所有解的图形描述。树上的每个子节点的解都只有一个部分与父节点不同。

3,扩展节点、活结点、死结点:所谓扩展节点,就是当前正在求出它的子节点的节点,在DFS中,只允许有一个扩展节点。活结点就是通过与约束函数

      的对照,节点本身和其父节点均满足约束函数要求的节点;死结点反之。由此很容易知道死结点是不必求出其子节点的(没有意义)。

利用回溯法解题的具体步骤

首先,要通过读题完成下面三个步骤

(1)描述解的形式,定义一个解空间,它包含问题的所有解,这一步主要明确问题的解空间树。

(2)构造状态空间树。

(3)构造约束函数(用于杀死节点)。

然后就要通过DFS思想完成回溯,具体流程如下:

(1)设置初始化的方案(给变量赋初值,读入已知数据等)。

(2)变换方式去试探,若全部试完则转(7)。

(3)判断此法是否成功(通过约束函数),不成功则转(2)。

(4)试探成功则前进一步再试探。

(5)正确方案还未找到则转(2)。

(6)已找到一种方案则记录并打印。

(7)退回一步(回溯),若未退到头则转(2)。

(8)已退到头则结束或打印无解。


总结起来就是:

针对所给问题,确定问题的解空间 --> 确定结点的扩展搜索规则--> 以DFS方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。

回溯法基本框架

设问题的解是一个n维向量(a1,a2,………,an),约束条件是bi(i=1,2,3,…..,n)之间满足某种条件,记为f(bi)。

递归实现

int a[n];
初始化数组a[]等操作  
 ...
void dfs(int cur)
{
	int i;
     if(cur>n)
       //统计、输出结果等;
      else
     {
        for(i = 下界; i <= 上界; ++i)  // 枚举i所有可能的路径
        {
           if(fun(i))                 // 满足限界函数和约束条件
              {
                 a[cur] = i;
                  ...                 // 其他操作,设置标志等
                 dfs(cur+1);
                 //回溯前的清理工作(如a[cur]置空值,标志置0等);
              }
          }
      }
}

典型问题应用

素数环问题,hdoj1016:http://acm.hdu.edu.cn/showproblem.php?pid=1016

题目大意:

将从1到n这n个整数围成一个圆环,若其中任意2个相邻的数字相加,结果均为素数,那么这个环就成为素数环。

要求输出:从整数1开始。

分析问题,可以构造解空间树,比较顺利的想到 DFS 、回溯。

代码如下:

#include<stdio.h>
#include <string.h>

#define M 40

int isPrime[M];////素数表,下标为素数的置为1,否则0
int vis[M>>1];// vis 标识 1-n,是否被选
int res[M>>1];// 存储解向量

int cnt;// 测试样例个数

void prime()//求出1-40的所有素数
{
	int i, j;
	for(i=1; i<M; ++i)
	{
		int ok = 1;
		for(j=2; j*j<=i; ++j)
		{
			if(i%j == 0)
			{
				ok = 0;
				break;
			}
		}
		if(ok)
			isPrime[i]=1;

	}
}

void dfs(int cur, int n)
{
	int i;
	if(cur == n && isPrime[res[n-1] + res[0]])//别忘了测试边界,最后一个和第一个数 构成的环
	{
		for(i=0; i<n-1; ++i)
			printf("%d ", res[i]);
		printf("%d\n", res[i]);
	}
	else
	{
		for(i=2; i<=n; i++)// 尝试每个i, 1始终在排头,因此从2开始计算
		{
			if(!vis[i] && isPrime[res[cur-1] + i])// i未用过且和前一个数和为素数
			{
				res[cur] = i;
				vis[i] = 1; // 设置标志
				dfs(cur+1, n);
				vis[i] = 0; // 回溯, 清除标识
			}
		}
	}
}


int main()
{
	int n;
	//freopen("in.txt", "r", stdin);
	prime();
	cnt = 0;
	while(scanf("%d", &n) != EOF)
	{
		++cnt;
		printf("Case %d:\n", cnt);
		memset(vis, 0, sizeof(vis));
		res[0] = 1;
		dfs(1, n);
		printf("\n");
	}
	return 0;
}



代码中 prime()函数 求出1-40的所有素数,因为只要测试 1-19 因此 可以事前 把1-38的素数存储到一个 素数表里。这样计算时间更快。


下面是最近更新的回溯法的相关例子,http://blog.youkuaiyun.com/daniel_ustc/article/details/17041933

参考:

 http://zh.wikipedia.org/zh-cn/%E5%9B%9E%E6%BA%AF%E6%B3%95

http://www.newsjz.com/wxqgr/xxas/as2/sfjc/201103/337.html

<think>嗯,用户想了解数独求解算法中递归和回溯的实现方式。首先,我需要回忆一下递归和回溯的基本概念。递归就是函数自己调用自己,而回溯则是一种通过试错来寻找解决方案的方法,当发现当前路径无法得到解时,就回退一步,尝试其他可能。 接下来,数独的规则是每行、每列和每个3x3的小宫格内必须填入1-9且不重复。所以,算法需要填充空格,同时满足这三个条件。那递归和回溯如何应用在这里呢?应该是逐个填充空格,尝试填入可能的数字,如果后续发现冲突,就回溯到上一个位置重新选择数字。 根据之前的引用资料,比如引用[4]提到回溯是一种优化后的暴力求解,通过剪枝加速。所以,在数独中,递归函数可能从第一个空格开始,尝试填入1-9中的一个数字,检查是否合法,如果合法就递归处理下一个空格。如果下一个空格无法填入任何数字,说明当前路径错误,回溯到上一个位置,尝试下一个可能的数字。 具体步骤可能是这样的:遍历数独的每个单元格,找到空格,尝试填入可能的数字,验证行、列、宫格是否冲突。如果没有冲突,递归处理下一个空格。如果所有数字都尝试过都不行,就回溯,恢复当前空格为未填状态,回到上一个位置继续尝试。 需要处理的关键点包括如何高效地找到空格,如何验证填入数字的合法性,以及递归终止条件。终止条件应该是所有空格都填满,这时就可以输出解了。 另外,引用[2]提到递归回溯对数独很有效,可能因为数独的结构适合这种逐步试探的方法。而引用[3]的C语言实现也是用递归,说明这种方法的通用性。 可能需要注意的优化点,比如优先填充可能性少的空格,减少递归次数,但用户的问题是基本实现,所以可能不需要深入优化部分,先讲清楚基础实现。 所以,整个流程应该是:找到空格→尝试数字→验证→递归→回溯代码结构大致是递归函数,参数是当前数独状态,每次调用处理下一个空格,直到填满或无法继续时回溯。 然后,代码示例部分需要展示这个逻辑,比如用Python的话,可能有一个solve_sudoku函数,里面循环1-9,检查valid,然后递归调用,如果返回True则继续,否则重置单元格。 还需要考虑如何表示数独,可能用二维数组。验证函数需要检查行、列和宫格。宫格的检查可以通过确定起始行和列来计算。 总结起来,回答的结构应该是先讲算法思想,再分步骤解释,然后给出代码示例,最后相关问题。需要确保使用正确的中文术语,LaTeX公式正确,如行内用$...$,独立公式用$$...$$,但数独算法可能涉及较多代码和逻辑,数学公式可能较少,但如果有提到数学概念,比如组合可能,需要正确使用。</think>### 数独求解算法中的递归与回溯实现方式 #### 一、算法思想 数独求解的递归回溯算法通过**逐步填充空格**并验证合法性,遇到矛盾时回退(回溯)到上一步重新选择数字,直到所有空格被正确填充[^4]。核心步骤包括: 1. **寻找空格**:遍历数独网格,找到第一个未填充的单元格。 2. **尝试填入数字**:在空格中尝试填入$1$至$9$,满足**行、列、3×3宫格**无重复。 3. **递归与回溯**:若当前数字合法,递归处理下一个空格;若后续步骤无解,则回溯到当前空格并尝试其他数字。 #### 二、实现步骤 1. **合法性检查** 验证数字是否满足数独规则: - **行检查**:当前行是否已存在该数字。 - **列检查**:当前列是否已存在该数字。 - **宫格检查**:所在3×3宫格是否已存在该数字。 ```python def is_valid(board, row, col, num): # 检查行 if num in board[row]: return False # 检查列 for i in range(9): if board[i][col] == num: return False # 检查3×3宫格 start_row, start_col = 3 * (row // 3), 3 * (col // 3) for i in range(3): for j in range(3): if board[start_row + i][start_col + j] == num: return False return True ``` 2. **递归回溯函数** 递归填充空格,若遇到矛盾则回溯: ```python def solve_sudoku(board): for i in range(9): for j in range(9): if board[i][j] == 0: # 找到空格 for num in range(1, 10): if is_valid(board, i, j, num): board[i][j] = num if solve_sudoku(board): # 递归处理下一个空格 return True board[i][j] = 0 # 回溯 return False # 所有数字均不合法,触发回溯 return True # 所有空格已填满 ``` 3. **终止条件** 当遍历完所有单元格且无空格时,返回成功(数独已解)。 #### 三、复杂度与优化 - **时间复杂度**:最坏情况下为$O(9^{n})$($n$为空格数量),但实际通过剪枝(合法性检查)大幅减少尝试次数[^2]。 - **优化策略**:优先填充**候选数最少**的空格(启发式搜索),减少递归深度[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值