2、回溯
定义
回溯算法实际上是一个类似枚举的搜索尝试过程,主要是在搜索尝试中寻找问题的解,当发现不能满足条件时,就“回溯”。许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法” 的美称。
步骤
(1)针对所给问题,定义问题的解空间,它至少包含问题的一个(最优)解;
(2)确定易于搜索的解空间结构,使得能用回溯法方便地搜索整个解空间;
(3)以深度优先的方式搜索解空间,并且在搜索过程中用剪枝函数避免无效搜索。
基本思想:从一条路往前走,能进则进,不能进则退回来,回一条路再走。
实现
回溯的实现方法一般有两种:递归和递推(迭代)
(1)递归
思路简单,设计容易,但效率较低:
//针对N叉树的递归回溯方法
void backtrack (int t)
{
if (t>n) output(x); //叶子节点,输出结果,x是可行解
else
for i = 1 to k//当前节点的所有子节点
{
x[t]=value(i); //每个子节点的值赋值给x
//满足约束条件和限界条件
if (constraint(t)&&bound(t))
backtrack(t+1); //递归下一层
}
} (2)递推
算法设计较复杂,但效率较高:
//针对N叉树的迭代回溯方法
void iterativeBacktrack ()
{
int t=1;
while (t>0) {
if(ExistSubNode(t)) //当前节点的存在子节点
{
for i = 1 to k //遍历当前节点的所有子节点
{
x[t]=value(i);//每个子节点的值赋值给x
if (constraint(t)&&bound(t))//满足约束条件和限界条件
{
//solution表示在节点t处得到了一个解
if (solution(t)) output(x);//得到问题的一个可行解,输出
else t++;//没有得到解,继续向下搜索
}
}
}
else //不存在子节点,返回上一层
{
t--;
}
}
} 识别回溯
一般回溯的问题有三种:
- 有没有解
- 求所有解(个数或具体信息)
- 求最优解
而对于每种问题有不同的模板
第一种:有没有解
bool solve(Node n){
if n is a leaf node{
if the leaf is a goal node , return true;
else return false;
}
else{
for each child c of n{
if solve(c) succeeds,return true;
}
return false;
}
}第二种:求全部解
void solve(Node n){
if n is a leaf node{
if the leaf is a goal node,count++,return;
else return;
}
else{
for each child c of n{
solve(c);
}
}
}第三种:求最优解
void solve(Node n){
if n is a leaf node{
if the leaf is a goal node,update best result,return;
else return;
}
else{
for each child c of n{
solve(c);
}
}
}实例一:八皇后问题
八皇后问题是一个古老而著名的问题,是回溯算法的典型例题。该问题是十九世纪著名的数学家高斯1850年提出:在8X8格的国际象棋上摆放八个皇后(棋子),使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上。
第一种:有没有解
#include <iostream>
using namespace std;
class ChessBoard{
public:
ChessBoard();
ChessBoard(int);
void findSolution();
private:
const bool available;
const int squares,norm;//棋盘边长、设定一个右斜线相加的常数
bool *column,*leftDiagonal,*rightDiagonal;//定义列、左斜线与右斜线
int *positionInRow,howMany;//定义行及方法的数量
char m[10][10];//记录棋盘格
void putQueen(int);//放置皇后
void printBoard();//打印输出
void initBoard();//初始化棋盘
void Delete();//释放内存
};
ChessBoard::ChessBoard():available(true),squares(8),norm(squares-1){
initBoard();
}
ChessBoard::ChessBoard(int n):available(true),squares(n),norm(squares-1){
initBoard()
}
void ChessBoard::initBoard(){
register int i;//将i寄存,使计算更快
column = new int[squares];
positionInRow = new int[squares];
leftDiagonal = new bool[squares*2-1];//左斜线的数目
rightDiagonal = new bool[squares*2-1];//右斜线的数目
for(int i = 0;i < squares;i ++)
positionInRow[i] = -1;//存储列数
for(int i = 0;i < squares;i ++)
column[i] = available;
for (i = 0; i < squares * 2 - 1; i++)
leftDiagonal[i] = rightDiagonal[i] = available;
howMany = 0;
}
void ChessBoard::printBoard() {
howMany++;//
cout << howMany << " way is:" << endl;
//为棋盘格赋值为1
for (int i = 0;i != squares;i++) {
for (int j = 0;j != squares;j++)
m[i][j] = '1';
}
//将皇后的位置在棋盘格上用'*'标志出来
for (int row = 0;row != squares;row++)
m[row][positionInRow[row]] = '*';
//打印棋盘格
for (int i = 0;i != squares;i++) {
for (int j = 0;j != squares;j++)
cout << m[i][j];
cout << endl;
}
cout << endl;
}
void ChessBoard::putQueen(int row) {
for (int col = 0; col < squares; col++) {
if (column[col] == available &&
leftDiagonal[row + col] == available &&
rightDiagonal[row - col + norm] == available)
{
positionInRow[row] = col;
column[col] = !available;
leftDiagonal[row + col] = !available;
rightDiagonal[row - col + norm] = !available;
if (row < squares - 1)
putQueen(row + 1);
else printBoard();
column[col] = available;
leftDiagonal[row + col] = available;
rightDiagonal[row - col + norm] = available;
}
}
}
void ChessBoard::Delete() {
delete[]column;
delete[]positionInRow;
delete[]leftDiagonal;
delete[]rightDiagonal;
}
void ChessBoard::findSolutions() {
putQueen(0);
cout << howMany << " solutions found.\n";
Delete();
}
int main() {
ChessBoard board(7);
board.findSolutions();
while (true)
{
}
return 0;
}
实例二:背包问题
给定n种物品和一背包。物品i的重量是wi,其价值为pi,背包的容量为C。问应如何选择装入背包的物品,使得装入背包中物品的总价值最大?
#include <stdio.h>
#define N 3 //物品的数量
#define C 16;//背包的容量
int w[N]={10,8,5};//每个物品的重量
int v[N]={5,4,1};//每个物品的价值
int x[N]={0,0,0};//代表是否放入背包
int curW=0;//当前背包中物品总重量
int curV=0;//当前背包中物品总价值
int BestV=0;//当前的最大价值
int BestX[N];//最优路径选择
void backTrack(int t){
//叶子节点,一条路径一条路径的遍历
if(t>N-1){
//找到一个更优的解
if(curV>BestV){
BestV=CurV;
for(int i=0;i<N;++i) BestX[i]=x[i];
}
}
else{
//主要在此处实现回溯,递归到下一个节点时,价值和重量加;回溯之后,价值和重量减。
if((CurV+w[t])<=C){
CurW+=w[t];
CurV+=v[t];
backTrack(t+1);
CurW-=w[t];
CurV-=v[t];
}
}
}
//主函数
int main(int argc,char* argv[]){
backTrack(0);
cout << "最优解:" << BestV << endl;
for(int i=0;i<N;i++){
cout << "路径:" << BestX[i]);
}
return 0;
} 
京公网安备 11010502036488号