目录

一、概述

二、素数环问题

三、n个数中取出任意r个数进行排列

四、整数划分问题/自然数拆分(递归方法也可以实现,见递归部分)

五、装载问题(最大装载量)

六、0-1背包问题

七、n皇后问题

八、子集和问题


一、概述

  1. 以深度优先的方式系统地搜索问题的解的方法称为回溯法。
  2. 回溯算法是深度优先策略的典型应用,回溯算法就是沿着一条路向下走,如果此路不通了,则回溯到上一个分岔路,再选一条路走,一直这样递归下去,直到遍历完所有的路径。
  3. 回溯算法的解空间结构可以组成成子集树(0-1背包问题、全排列)、排列树。会画出解决某个问题的解空间树,比如会画出描述3个物品的背包问题的解空间树等

旅行商问题就是一颗排列数

4.使用回溯法解题,通常包括的三个步骤

(1)针对所给问题,定义问题的解空间;

(2)确定易于搜索的解空间结构;

(3)以深度优先的方式搜索解空间,并且在搜索过程中使用剪枝函数避免无效搜索。

5.在回溯法搜索解空间树时,通常采用剪枝函数(包括约束函数和限界函数)避免无效搜索以提高回溯法的搜索效率。针对0-1背包问题,可以定义哪些剪枝规则,并能对这些剪枝规则进行简单描述。

  • 用约束函数在扩展结点处剪去不满足约束条件的子树;
  • 用限界函数剪去不能得到最优解的子树。

6.回溯算法搜索子集树的伪代码(之一)

void backtrack (int t){  //形参t为树的深度,根为1

if (t>n)

update(x);   //扩展到叶子结点,得到了一组解决方案

else

for (int i=0; i<2; i++) //每个结点有2个子树

{

x[t]=i;// 即0/1,表示第  t个元素是否是可选元素

if (constraint(t) && bound(t)) //判断当前结点是否是扩展结点

backtrack(t+1); //对当前结点进行扩展

}   }

7.回溯算法搜索子集树的伪代码(之二)

void backtrack (int t){  //形参t为树的深度,根为1

for (int i=0; i<2; i++){ //每个结点有n个子树

if (t>n) {

update(x);   //扩展到叶子结点,得到了一组解决方案

break;

}

x[t]=i;     //即0/1,表示第  t个元素是否是可选元素

if (constraint(t) && bound(t)) //判断当前结点是否是扩展结点

backtrack(t+1); //对当前结点进行扩展

}

}

8.回溯算法搜索排列树的伪代码

void backtrack (int t){//形参t为树的深度,根为1

if (t>n)

update(x); //得到了一个全排列,对排列结果进行更新

else

for (int i=t; i<=n; i++){

//为了保证排列中每个元素不同,通过交换 来实现

swap(x[t], x[i]);

if (constraint(t) && bound(t))

backtrack(t+1);

swap(x[t], x[i]); //恢复状态

}

}

9.回溯算法解决:素数环问题、n个数中取出任意r个数进行排列、自然数拆分(递归方法也可以实现,见递归部分)、装载问题(最大装载量)、0-1背包问题、n皇后问题、子集和问题

知道图的m着色问题、旅行商问题、流水作业调度问题可以使用回溯法解决即可。

二、素数环问题

素数环:从1到20这20个数摆成一个环,要求相邻的两个数的和是一个素数。

分析:

从1开始,每个空位有20种可能,只要填进去的数合法:

1、与前面已经填过的数不相同;

2、与左边相邻的数的和是一个素数。

3、第20个数还要判断和第1个数的和是否素数

子集数—全排列

算法流程:

1、数据初始化(布尔数组,标记某个数是否已经填入到素数环中);

2、递归填数:判断第i个数填入是否合法;

A、如果合法(剪枝条件):

填数;判断是否到达目标(20个已填完):

是,打印结果;

不是,递归填下一个;

  1. 如果不合法:选择下一种可能;

代码:

  1. 子集数:
void search(int t)//寻找所有解{int i;for(i=1;i<=20;i++){if(pd(a[t-1],i)&&(!b[i])) //与前一个数是否构成素数及该数是否可用{a[t]=i;//i进入素数环b[i]=1;//标志1if(t==20)//到最后了,得到一个解{if(pd(a[20],a[1]))//判断最后一个和第一个print();}elsesearch(t+1);b[i]=0;}}}
  1. 排列数:
void search(int t)//寻找所有解{int i;for(i=t;i<=20;i++){swap(a[t],a[i]);if(pd(a[t-1],a[t])){if(t==20){if(pd(a[20],a[1]))print();}elsesearch(t+1);}swap(a[t],a[i]);}}

完整代码:

#include <iostream>#include<algorithm>#include<cstring>#include<cmath>using namespace std;bool b[21]={0};//标志i是否出现在素数环中int total=0,a[21]={0};//a记录素数环中的每一个数void search(int t);//回溯过程。形参表示素数环中的数的编号void print();//输出方案bool pd(int,int);//判断两数之和是不是素数void search(int t)//寻找所有解{int i;for(i=t;i<=20;i++){swap(a[t],a[i]);if(pd(a[t-1],a[t])){if(t==20){if(pd(a[20],a[1]))print();}elsesearch(t+1);}swap(a[t],a[i]);}}void print(){total++;cout<<"<"<<total<<">";for(int j=1;j<=20;j++)cout<<a[j]<<" ";cout<<endl;}bool pd(int x,int y){int k=2,i=x+y;while(k<=sqrt(i)&&i%k!=0)k++;if(k>sqrt(i))return 1;elsereturn 0;}int main(){for(int i=1;i<=20;i++)a[i]=i;search(1);cout<<total<<endl; //输出总方案数return 0;}

三、n个数中取出任意r个数进行排列

设有n个整数【1-n】的集合{1,2,…,n},从中取出任意r个数进行排列(r<n),试列出所有的排列

输入:n r

输出:方案数

代码:

#include <iostream>#include<algorithm>#include<cstring>#include<cmath>using namespace std;int num=0,a[101]={0},n,r;//从1-n,取r个数bool b[101]={0};//标志//void search(int ,int);//回溯过程void print();//输出方案int search(int k,int start) //两个参数。k代表组合中的第几个数,start从哪个位置开始选择数字{for(int i=start;i<=n;i++){a[k]=i;//保存结果if (k==r)print();elsesearch(k+1,i+1);}}void print(){num++;for(int j=1;j<=r;j++)cout<<a[j]<<" ";cout<<endl;}int main(){cin>>n>>r;search(1,1);cout<<num<<endl;//输出方案总数}

四、整数划分问题/自然数拆分(递归方法也可以实现,见递归部分)

任何一个大于1的自然数n,总可以拆分成若干个小于n的自然数之和。

#include <iostream>#include<algorithm>#include<cstring>#include<cmath>using namespace std;int total,a[101]={1},n;int search(int s,int t);//表待拆分的数; 序列中t 代表拆分序列中的数的编号int print(int);//输出方案int search(int s,int t){int i;for(i=a[t-1];i<=s;i++)if(i<n)当前数i要大于等于前1位数,且不过n{a[t]=i;//保存当前拆分的数is-=i;//s减去数i, s的值将继续拆分if(s==0)print(t);  //当s=0时,拆分结束输出结果elsesearch(s,t+1);//当s>0时,继续递归s+=i;//回溯:加上拆分的数,以便产生所有可能的拆分}}int print(int t){cout<<n<<"=";for(int i=1;i<=t-1;i++)cout<<a[i]<<"+";cout<<a[t]<<endl;total++;}int main(){cin>>n;search(n,1);//将要拆分的数n传递给scout<<total<<endl;//输出方案总数}

五、装载问题(最大装载量)

给定n个集装箱要装上一艘载重量为c的轮船,其中集装箱i的重量为wi。集装箱装载问题要求确定在不超过轮船载重量的前提下,将尽可能多的集装箱装上轮船(贪心算法中的装载问题讨论的是装载件数;本题讨论的是最大装载重量。)

输入:

每组测试数据:第1行有2个整数c和n。c是轮船的载重量(0<c<30000),n是集装箱的个数(n≤20)。第2行有n个整数w1, w2, …, wn,分别表示n个集装箱的重量。

输出:

对每个测试例,输出两行:第1行是装载到轮船的最大载重量,第2行是集装箱的编号。

分析:

假设解向量为X(x1, x2, …, xn),其中xi∈{0, 1}, xi =1表示集装箱i装上轮船, xi =0表示集装箱i不装上轮船。

用回溯法解装载问题时,其解空间是一棵子集树,与0 - 1背包问题的解空间树相同。

int cw,   当前的轮船的荷载

int bestcw ,  当前的最大荷载,荷载到达叶子结点才会更新

int r ;  剩余的未考察的集装箱重量和

约束函数剪枝:cw>c

限界函数剪枝:cw+r<bestw

不明白为什么关于这个问题的的解决方案大都用了类

方法一:课上方法,函数参数太多,有点乱

#include <iostream>#include<algorithm>#include<cstring>#include<cmath>using namespace std;class goods{int weight;//集装箱重量public:goods(int w=0):weight(w){}int get_w(){return weight;}void set(int w){weight=w;}};void load(goods *g, int *x, int t, int n,int cw, int &bestcw ,int *best,int r,int c)//轮船载重//goods *g,集装箱列表,*x,满足当前最大荷载的装载方案,t,子集树的层号,n,集装箱的总数// cw, 当前的轮船的荷载,bestcw ,当前的最大荷载, *best,待求解的最优装载方案;r,剩余的未考察的集装箱重量和{if(t==n)//已经遍历的到叶子结点{if(cw>bestcw)//当前的轮船的荷载>当前的最大荷载{for(int i=0;i<n;i++){best[i]=x[i];}bestcw=cw;}}else{r=r-g[t].get_w();//剩余未处理的物品的重量和,与是否选取当前物品无关if(cw+g[t].get_w()<=c)//约束条件进行剪枝{x[t]=1;cw=cw+g[t].get_w();//当前装入的物品的重量和load(g,x,t+1,n,cw,bestcw,best,r,c);cw=cw-g[t].get_w(); //回溯的需要}if(cw+r>bestcw)//限界规则{x[t]=0;load(g,x,t+1,n,cw,bestcw,best,r,c);}r=r+g[t].get_w(); //回溯的需要}}int main(){int n,c,bestcw=0;int *x,*best, r=0;cout<< "请输入轮船的装载重量和物品的件数:";cin>>c>>n;goods *g;g=new goods[n];x=new int [n];best=new int[n];cout<<"请输入每件物品的重量:";for(int i=0;i<n;i++){int w;cin>>w;g[i].set(w);r=r+w;}load(g,x,0,n,0,bestcw,best,r,c);cout<<bestcw<<endl;for(int i=0;i<n;i++)cout<<best[i]<<"  ";cout<<endl;return 0;}

方法二:如果用类,为什么不直接全部使用类,方法单独定义,这样就不用传那么多参数了,两个方法其实是一样的,但是这样看起来是不是简洁好理解一点了

#include<bits/stdc++.h>using namespace std;int bestx[101];//满足当前最大荷载的装载方案class Loading{int MaxLoading(int[],int,int);//初始化的函数public:void Backtrack(int i);//回溯int n;//集装箱数int* w,//集装箱重量数组c,//轮船的载重量cw,//当前载重量,当前的轮船的荷载bestw,//当前的最大荷载,当前最优载重量r;//剩余未考察的集装箱重量};void Loading::Backtrack(int i){//搜索第i层结点if(i>n)//到达叶节点{if(cw>bestw)bestw=cw;//更新最优解return;}r-=w[i];//修改剩余集装箱重量if(cw+w[i]<=c)//约束函数进行剪枝{cw+=w[i];//装入bestx[i]=1;Backtrack(i+1);cw-=w[i];//回溯}if(cw+r>bestw) //限界规则{Backtrack(i+1);bestx[i]=0;r+=w[i];//修改剩余集装箱重量}}int MaxLoading(int w[],int c,int n){//初始化Loading X;X.w = w;X.c = c;X.n = n;X.bestw = 0;X.cw = 0;//初始化X.r = 0;for(int i=1;i<=n;i++){X.r+=w[i];}//初始时的r为全体物品重量和X.Backtrack(1);//从第一层开始搜索return X.bestw;}int main(){int c,n,m;cin>>c>>n;int w[101];for(int i=1;i<=n;i++)cin>>w[i];m=MaxLoading(w, c, n);cout<<"最大装载量为:"<<endl;cout<<m<<endl;for(int i=1;i<=n;i++){cout<<"x["<<i<<"]:"<<bestx[i]<<endl;}return 0;}

六、0-1背包问题

给定一个物品集合s={1,2,3,…,n},物品i的重量是wi,其价值是vi,背包的容量为W,即最大载重量不超过W。在限定的总重量W内,我们如何选择物品,才能使得物品的总价值最大。

输入:

第一行是背包容量 c,物品个数n

接下来n行是物品i的重量是wi,其价值为vi

输出:

输出装入背包中物品的最大价值。

分析:0-1背包问题(子集数)

  • 令cw(i)表示目前搜索到第i层已经装入背包的物品总重量
  • 令cv(i)表示目前到第i层结点已经装入背包的物品价值
  • 对于左子树, xi =1 ,其约束函数为:construction(i)=cw(i-1)+wi。若constraint(i)>W,则停止搜索左子树,否则继续搜索。
  • 对于右子树,r(i)表示剩余物品的总价值,限界函数Bound(i)=cv(i)+r(i)
  • r(i)越小, Bound(i)越小,剪掉的分支就越多(越靠近树根剪枝,剪掉的分支越多。从而能加快搜索速度)。
  • 为了构造更小的r(i) ,将物品以单位重量价值比di=vi/wi递减的顺序进行排列(贪心策略)

代码:

#include<bits/stdc++.h>using namespace std;int c,n;//背包容量、物品数量int cw,cv;//背包当前重量,当前价值int bestv;//当前最优价值struct bag{int w; //物品的重量int v; //物品的价值double d; //物品的单位重量价值比}a[101];bool cmp(bag a,bag b)//按照价值比从大到小排序{if(a.d>=b.d)return true;elsereturn false;}int Bound(int i)//限界函数{int cleft=c-cw;//背包剩余的容量int b=cv;//上界//尽量装满背包while(i<n&&a[i].w<=cleft){cleft-=a[i].w;b+=a[i].v;i++;}/*剩余的部分空间也装满。0-1是可能装不满的。但此处主要计算最大值,所以,需要从装满的角度考虑该问题(尽管这不是一个可行解,但可以证明其价值是最优值的上界)*/if(i<n)b+=1.0*cleft*a[i].v/a[i].w;return b;}int ff(int i)//形参i是回溯的深度,从0开始.商品编号从0开始编号{if(i==n){bestv=cv;//当前最优价值=当前价值// return ;}//进入左子树搜索, 表示选择第i件物品if(cw+a[i].w<=c)//满足约束条件{cw+=a[i].w;//选择第i件物品cv+=a[i].v;ff(i+1);cw-=a[i].w;//回溯的需要cv-=a[i].v;//回溯需要}//进入右子树搜索,表示不选择第i件物品,相关的cw, cv不改变if(Bound(i+1)>bestv)//大于当前最优价值,满足条件不剪枝{ff(i+1);}}int main(){cin>>c>>n;for(int i=0;i<n;i++){cin>>a[i].w>>a[i].v;a[i].d=1.0*a[i].v/a[i].w;}sort(a,a+n,cmp);ff(0);cout<<bestv<<endl;}

七、n皇后问题

在n×n格的棋盘上放置彼此不受攻击的n个皇后。

按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。

n皇后问题等价于在n×n格的棋盘上放置n个皇后,任何两个皇后不放在同一行或同一列或同一斜线上。

编程要求:找出一个n×n格的棋盘上放置n个皇后并使其不能互相攻击的所有方案。

输入样例:5【皇后个数】

输出样例:

1 3 5 2 4

1 4 2 5 3

2 4 1 3 5

2 5 3 1 4

3 1 4 2 5

3 5 2 4 1

4 1 3 5 2

4 2 5 3 1

5 2 4 1 3

5 3 1 4 2

Total= 10

【输出样例含义:拿第一个来说,第一例的1,第二列的3,第二列的5.......】

分析:

由于棋盘的每列/行只有一个皇后,所以可以用一维向量X( x1, x2, …, xn),其中xi∈{1, 2, …, n},表示第i列皇后所在的行x[i]。解空间的每个结点都有n个儿子,因此解空间的大小为nn,这是一棵子集树

同一对角线判断:

#include<bits/stdc++.h>using namespace std;int n;//棋盘大小int x[101];//解向量int sum=0;//当前已经找到的可行方案数inline bool Place(int t)//判断是否在同一行,同一列,同一对角线{int i;for(i=1;i<t;i++){if ((abs(t-i) == abs(x[i]-x[t])) || (x[i] == x[t]))//判断是否在同一对角线、同一行return false;}return true;}void Back(int t)//t是回溯的深度,从1开始{int i;if(t>n)//到达叶子结点{sum++;//方案数+1for(i=1;i<=n;i++)cout<<x[i]<<" ";cout<<endl;}else{for(i=1;i<=n;i++){x[t]=i;if(Place(t))//判断是否符合条件Back(t+1);}}}int main(){cin>>n;Back(1);cout<<sum<<endl;}

八、子集和问题

子集和问题的一个实例为<S,c>。其中,S={w1, w2, …, wn}是一个正整数的集合,c是一个正整数。子集和问题判定是否存在S的一个子集S1,使得S1的和为c。

编程任务:对于给定的正整数集合S={w1, w2, …, wn}和正整数c,编程计算S的一个子集S1,使得S1的和为c。

输入:

第一行有2个正整数n和c,n表示S的大小,c是子集和的目标值。接下来的一行,有n个正整数(1≤n≤10000),表示集合S中的元素。

输出:

输出子集和问题的全部解,每个数据后面都有一个空格。当问题无解时,输出“No Solution!”

分析:用回溯法求解子集和问题,与0-1背包问题类似,解空间树是一棵子集树。

代码:

#include<bits/stdc++.h>using namespace std;class Sum{int n; //集合中数据的个数int *set;//集合int *x;//解向量,当x[i]=1表示第i 个元素属于子集,否则,x[i]=0;int c;//输入的条件。要计算的子集和int s;//正在构造的子集中元素的和int r;//不在子集中的未考察的其他数据元素之和;int total;//记录符合条件的子集的个数public:Sum(int n=0,int c=0);//构造函数void calculate(int t);//计算void display();int get_T()//方案总数{return total;}};Sum::Sum(int n,int c){this->n=n;this->c=c;x=new int [n+1];set=new int [n+1];cout<<"请输入集合中的元素"<<endl;r=0;total=0;for(int i=1;i<=n;i++){cin>>set[i];x[i]=0;r=r+set[i];}s=0;}void Sum::calculate(int t){if(t>n){if(c==s){display();total++;}}else{r=r-set[t];if(s+set[t]<=c)//约束规则{x[t]=1;s=s+set[t];calculate(t+1);s=s-set[t];x[t]=0;}if(s+r>=c)//限界函数calculate(t+1);r=r+set[t];}}void Sum::display(){int i;for(int i=1;i<=n;i++)if(x[i]==1)cout<<set[i]<<" ";cout<<endl;}int main(){int n,c;cout<<"输入n 和c 的值:";cin>>n>>c;Sum s(n,c);s.calculate(1);if(s.get_T()==0)cout<<"NO Solution!!"<<endl;elsecout<<"方案数="<<s.get_T()<<endl;return 0;}

这几章不好懂,大家一起加油呀!!! 

算法整理八——回溯算法相关推荐

  1. 程序员都会的五大算法之四(回溯算法),恶补恶补恶补!!!

    前言 点击查看算法介绍 五大算法 分治算法 动态规划 贪心算法 回溯算法 分支限界算法 WX搜素"Java长征记"对这些算法也有详细介绍. 回溯算法 一.算法概述 回溯算法是一种择 ...

  2. 【数据结构与算法】【算法思想】回溯算法

    贪心算法 回溯算法 分治算法 动态规划 回溯算法思想应用广泛,除了用来指导深度优先搜索这种经典算法设计之外,还可以用在如正则表达式匹配,编译原理中的语法分析等. 除此之外,很多经典的数学问题都可以用回 ...

  3. 回溯 皇后 算法笔记_回溯算法:N皇后问题

    给「代码随想录」一个星标吧! ❝ 通知:我将公众号文章和学习相关的资料整理到了Github :https://github.com/youngyangyang04/leetcode-master,方便 ...

  4. 8皇后以及N皇后算法探究,回溯算法的JAVA实现,递归方案

    八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例.该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行.同 ...

  5. 8皇后以及N皇后算法探究,回溯算法的JAVA实现,递归方案(一)

    八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例.该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行.同 ...

  6. 五大算法四:回溯算法

    1.概念 回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就"回溯"返回,尝试别的路径. 回溯法是一种选优搜索法,按选优条件 ...

  7. 8皇后以及N皇后算法探究,回溯算法的JAVA实现,非递归,数据结构“栈”实现

    是使用递归方法实现回溯算法的,在第一次使用二维矩阵的情况下,又做了一次改一维的优化 但是算法效率仍然差强人意,因为使用递归函数的缘故 下面提供另一种回溯算法的实现,使用数据结构"栈" ...

  8. 一个点是否在矩形内的算法_478,回溯算法解单词搜索

    想了解更多数据结构以及算法题,可以关注微信公众号"数据结构和算法",每天一题为你精彩解答. 问题描述 给定一个二维网格和一个单词,找出该单词是否存在于网格中. 单词必须按照字母顺序 ...

  9. 算法训练Day24 | 回溯算法理论基础;LeetCode77.组合(经典的回溯问题)

    目录 回溯算法理论基础 1. 什么是回溯法 2. 回溯法的效率 3. 回溯法解决的问题 4. 如何理解回溯法 5. 回溯法模板-- 回溯三部曲 6. 总结 LeetCode77.组合 1. 思路 2. ...

最新文章

  1. Spark安装与学习
  2. Andriod中的两种自定义颜色选择器
  3. 利用python随机生成姓名的实例教程
  4. 快速删除大文件和大量小文件。
  5. 帆软报表在已经搭载服务器上开发_史上最全企业数据产品选型对比(含数仓、报表、BI、中台、数据治理)...
  6. 怎么让无线网络共享打印机设置
  7. Android集成一个新产品时,lunch的product name和device name注意事项
  8. java如何遍历字典_Java中如何遍历Map对象的4种方法
  9. Java语句详解(图解java语句概念、快速掌握java基础知识点)——Java基础系列
  10. c语言课本答案解析宋士银,c语言教材
  11. 如何在电脑端同时登录多个企业微信或微信
  12. 学习笔记(1):机器学习数学基础之凸优化视频教学-(1.2)机器学习中的优化问题及实例...
  13. 数据分析的发展及数据分析师的技能浅谈
  14. [面试题]100层楼丢玻璃球,一旦超过某层就会破,你只有两个球。
  15. css3切角文本框_CSS3如何实现4个切角
  16. 20道经典Redis面试题
  17. 关于补码1.0000的真值为什么是-1的解答
  18. (简单介绍)PageRank算法
  19. vim全插件 linux,[转载]安装vim插件(linux下)
  20. VB合并excel表格(工作表和工作簿)

热门文章

  1. c语言画贝塞尔函数,Mathematica画各类贝塞尔函数
  2. 世界最美的40个小镇,中国上榜的竟是它们
  3. C语言中的结束符‘\0‘
  4. Win10无故自动重启解决方法
  5. W ndows7蓝屏0x00000024,Win7开机蓝屏错误代码0x00000024怎么解决?
  6. C++ delete 和 deallocate 的区别
  7. 霍纳规则(C/C++,Scheme)
  8. SpringMVC-应用(数据绑定-自定义类型转换器,数据的格式化,数据校验)
  9. MoveWindow函数
  10. hadoop集群搭建总结