彻底搞懂回溯算法

news/2024/11/15 17:37:49/文章来源:https://www.cnblogs.com/madadam/p/18424493

1.回溯算法的核心思想

回溯算法的核心思想是:尝试+记录+回退。
尝试一种选项,在选择该选项的前提下继续寻解,如果最后寻解成功,则记录这个解,否则不用记录,然后再回退到选择该选项前的状态,改为尝试其它选项再继续寻解,判断其它选项是不是解。

2.回溯算法的关键点

回溯算法用于寻找全部解的集合,这些解满足共同的条件。我们先以"N皇后"为例分析下什么是回溯算法。
"N皇后"问题是在一个N*N的棋盘中,将N个棋子放到合适位置,满足任意两个棋子不在同一行、不在同一列、不在主对角线或次对角线上。下图是一个N为4的棋盘,其中Q表示棋子,#表示空格,满足条件。


主对角线是从左上角到右下角的线,次对角线是从右上角到左下角的线,如下图所示

我们可以按照行逐次选择位置放置Q,最终结果肯定是每行有且仅有一个Q。

  • 每行可以选择的列号为[0,1,...,N-1],这就是选项集合
  • 从[0,1,...,N-1]按照先后顺序选择一个位置,记为K,这个K是否满足"所在列无Q、所在主对角线无Q、所在次对角线无Q",这三个条件就是判断选项是否有效的限制条件(因为是按行取,每次只能尝试一个位置,所以每行不可能同时存在多个Q,不用判断"所在行无Q")。
  • 每次尝试一个选项后,棋盘布局和三个限制条件都需要更新。这里棋盘的布局就是动态变化的状态。当该状态满足棋盘行数达到N,则说明找到了一个

如下图所示,展示回溯过程:

  • 首先行0选择(0,0);
  • 行1的(1,0)和(1,1)不满足限制条件,尝试选项(1,2);
  • 行2所有选项都不满足限制条件,说明前面行0和行1那样选择不行,回退到行1取消选项(1,2),尝试
    选项(1,3),行2选择(2,1);
  • 行3所有选项都不满足限制条件,说明前面行0、行1和行2那样选择不行,回退到行2,行2剩余的(2,2)和(2,3)都不行,继续回退到行1,行1没有剩余选项,继续回退到行0,行0尝试选项(0,1),新一轮开始。
  • 行1选择(1,3);
  • 行2选择(2,0);
  • 行3选择(3,2),棋盘行数达到4,所以找到一个解,记录该解。回退最近尝试的选项,回退到行3选择(3,3),不满足限制,故继续回退到行2,通过这种尝试、记录、回退的方法可以找到所有解。


通过上面例子,相信你已经对回溯算法有了自己的理解,回溯算法关键信息包括如下:
1>可以尝试的选项集合
2>用于判断一种选项是否是有效的限制条件
3>尝试新的有效选项后,更新状态
4>继续寻找。
5>状态达到为解需要满足什么解条件。如果是解,需要记录这个解。
6>尝试完成后再回退到该尝试前的状态

3.伪代码

几乎所有的回溯算法代码都可以使用下面的代码框架:

 /**result:所有解的集合.*state:状态.*choices:选项集合.*constraint:限制条件.*/
void backtrack(vector<State> &result,State &state,vector<Choice>&choices,Constraint &constraint)
{if(isSolution(state)) //当前state是解{recordSolution(result,state);//记录这个解到resultreturn;                     //回退}for(auto choice:choices)       //按先后顺序遍历选项{if(isValidChoice(choice,constraint)) //选项有效{tryChoice(state,constraint);                  //尝试该选项backtrack(result,state,choices,constraint);   //继续回溯roolbackChoice(state,constraint);             //回退该选项}}
}

4.代码实现

上面"N皇后"问题代码实现如下:

class Solution 
{
private:int s_row;					 //当前处理的行号int m_size;					 //棋盘大小vector<int> m_colPostionS;   //表示可选择的列vector<bool> m_colConstrain; //表示每列是否有Qvector<bool> m_mDiagonalConstrain;//表示每个主对角线上是否有Qvector<bool> m_sDiagonalConstrain;//表示每个次对角线上是否有Q
public:int solveNQueues(int n) {s_row=0;m_size=n;m_colPostionS.resize(m_size);for(int i=0;i<m_size;++i)m_colPostionS[i]=i;m_mDiagonalConstrain.resize(2*n-1,false);m_sDiagonalConstrain.resize(2*n-1,false);m_colConstrain.resize(n,false);//vector<vector<string>> chessboard;vector<vector<vector<string>>> res;backtrackMy(chessboard,res,m_colPostionS,m_colConstrain,m_mDiagonalConstrain,m_sDiagonalConstrain);return res.size();}
//判断解bool isSolution(vector<vector<string>> &state){if (!state.empty() && state.size()==m_size)//解条件return true;return false;}
//记录解
void recordSolution(vector<vector<string>> &state,vector<vector<vector<string>>> &res)
{res.emplace_back(state);
}
//判断选项有效
bool isValidChoice(int row,int col,vector<bool> &colConstrain,vector<bool> &mDiagonalConstrain,vector<bool> &sDiagonalConstrain)
{if(colConstrain[col]==true || mDiagonalConstrain[row+col]== true || sDiagonalConstrain[row-col+m_size-1]==true)return false;return true;
}
//尝试选项
void tryChoice(vector<vector<string>> &state,int row,int col,vector<bool> &colConstrain,vector<bool> &mDiagonalConstrain,vector<bool> &sDiagonalConstrain)
{vector<string>tempRow(m_size,"#");tempRow[col]="Q";state.emplace_back(tempRow);colConstrain[col]=true;sDiagonalConstrain[row+col]=true;mDiagonalConstrain[row-col+m_size-1]=true;
}
//回退选项
void roolbackChoice(vector<vector<string>> &state,int row,int col,vector<bool> &colConstrain,vector<bool> &mDiagonalConstrain,vector<bool> &sDiagonalConstrain)
{state.pop_back();colConstrain[col]=false;sDiagonalConstrain[row+col]=false;mDiagonalConstrain[row-col+m_size-1]=false;}
//使用按行放置的策略
//棋盘上"Q"表示皇后,"#"表示空位置
//colPostionS是choices
//state表示棋盘
//res表示最终结果
void backtrack(vector<vector<string>> &state, vector<vector<vector<string>>> &res,vector<int> &colS,vector<bool> &colConstrain,vector<bool> &mDiagonalConstrain,vector<bool> &sDiagonalConstrain) 
{if (isSolution(state))//检查是否为解 {recordSolution(state, res);//记录解return;}for (int col : colS)    // 遍历所有选择{if (isValidChoice(s_row,col,colConstrain,mDiagonalConstrain,sDiagonalConstrain))//有效 {// 尝试:做出选择,更新状态tryChoice(state,s_row++,col,colConstrain,mDiagonalConstrain,sDiagonalConstrain);//注意对s_row的处理// 进行下一轮选择backtrack(state, res,colS,colConstrain,mDiagonalConstrain,sDiagonalConstrain);// 回退:撤销选择,恢复到之前的状态roolbackChoice(state,--s_row,col,colConstrain,mDiagonalConstrain,sDiagonalConstrain);//注意对s_row的处理}}
}

};
说明:
大小为N的棋盘,其主对角线和次对角线个数都为2N+1条。如下图所示N为3为例:

  • 每条主对角线上元素的行号减去列号是一样的,即row-col分别为[-2,-1,0,1,2]。即row-col+N-1对应[0,1,2,3,4]。
  • 每条次对角线上元素的行号加上列号是一样的,即row+col分别为[0,1,2,3,4]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/803068.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

9.23 ~ 9.29

集训9.23 集训第一天。 早晨因为太多人没拿早读资料被老登 D 了。 不是哥们你不早说 现在我上哪给你找资料去 😅 上午模拟赛。 发现 T1 的图挂了,于是看形式化题意;初始有一张 \(n\) 个点的完全图,接着删除 \(m\) 条边。 询问有多少长度为 \(13\) 的序列 \(p_1,...,p_{13}…

CSP 集训记录

用来整理模拟赛等9.23 csp-3【noip23 ZR二十连测 DAY10】 保龄. A.奇观 狗市题目描述。不是这题意太大歧义了吧,我讨厌的第二种出题人——题意描述相当不清。CTH:13 座城市又不代表是 13 座不同的城市。直接看形式化题目的话(如果能看懂要干什么)那这题确实不难。 解: 容易…

判断系统大小端字节序的方法

1、字节序 1.1、大端字节序(big-endian) 数据低位存储在高地址位,数据高位存储在低地址位。 假设定义一个变量并赋予初值: int a = 0x12345678; 对于这个整型数据,一共有四个字节,假设为其分配的地址空间为0x1001~0x1004,则从低位到高位,每个字节依次是:12、34、56、78。…

第二十四讲:MySQL是怎么保证高可用的?

为了让各位更好的了解文章,我归纳了下面几点最重要的: 1、MySQL 高可用系统的可用性,是依赖于主备延迟的。延迟的时间越小,主库故障的时候,服务恢复需要的时间就越短,可用性就越高。 2、主备延迟原因:备库用的机子不行(IOPS是和主库相同的,不要轻视备库)、备库压力太…

redis内容记录

redis的基本数据类型String:是最基本的数据类型,它可以存储任何二进制安全的数据。 不仅能存放文本数据,还能保存图片、音频、视频、压缩文件等二进制数据。它们通常用于缓存。 Hash:哈希类型,其中键值对中的值本身又是一个键值对结构,hash 特别适合用于存储对象。 List:…

人工智能教育技术学第四周

1.用亿图图示制作黄山奇石语文课文的思维导图2.CAJViewer9.2(CAJ全文浏览器)是中国知网的专用全文格式阅读器,CAJ浏览器支持中国期刊网的CAJ、PDF、KDH等多种格式文件阅读。并且它的打印效果与原版的效果一致。可实现页面设置、浏览页面、查找文字、切换显示语言、文本摘录、…

项目实战:Qt+OSG爆破动力学仿真三维引擎测试工具v1.1.0(加载.K模型,子弹轨迹模拟动画,支持windows、linux、国产麒麟系统)

需求1.使用osg三维引擎进行动力学模型仿真性能测试;  2.打开动力学仿真模型文件,.k后缀的模型文件,测试加载解析过程;  3.解决第三方company的opengl制作的三维引擎,绘制面较多与弹丸路径模拟较卡顿的问题;  4.测试时,使用的模型为公开模型,基础面数量达到160多万…

【入门岛第1关】linux 基础知识

目录闯关任务 完成SSH连接与端口映射并运行hello_world.py 闯关任务 完成SSH连接与端口映射并运行hello_world.py 1 在远程主机上建立hello_python.py程序并运行,查看程序运行的端口: import socket import re import gradio as gr# 获取主机名 def get_hostname():hostname …

DOTS计算Voronoi图形生成,根据点自动划分区域生成多边形

如图,生成Voronoi图形,代码如下。// web* src = https://gist.github.com/andrew-raphael-lukasik/cc9d61edbbb44ecb4956c6cb69363a8e using UnityEngine; using Unity.Mathematics; using Unity.Jobs; using Unity.Collections; using Unity.Profiling;[ExecuteInEditMode] …

Vue2+3基础

。第一个Vue程序 使用script进行Vue全局设置: 指定Vue实例挂载的位置 , Vue和js一样,都需要在script里写 第一步创建vue实例 1.为什么要new vue(),直接调用Vue不行吗?不行,因为如果直接调用Vue()会报如下错误: 2.关于vue构造函数:optionsoptions翻译为多个选项 Vue…

JAVA的字符串这篇讲清楚了

JAVA_String 从概念上讲,JAVA字符串就是Unicode序列。例如"Java\u2122"由5个UNICODE字符J,a,v,a和™组成。Java没有内置和字符串类型,而是试用java标准库中提供的一个预定义类,很自然地叫做了string。 子串substring String subStr = str.substring(beginIndex);这…