典型的回溯算法问题
八皇后问题(经典算法-回溯法)
⼋皇后问题(经典算法-回溯法)问题描述:⼋皇后问题(eight queens problem)是⼗九世纪著名的数学家⾼斯于1850年提出的。
问题是:在8×8的棋盘上摆放⼋个皇后,使其不能互相攻击。
即任意两个皇后都不能处于同⼀⾏、同⼀列或同⼀斜线上。
可以把⼋皇后问题扩展到n皇后问题,即在n×n的棋盘上摆放n个皇后,使任意两个皇后都不能互相攻击。
思路:使⽤回溯法依次假设皇后的位置,当第⼀个皇后确定后,寻找下⼀⾏的皇后位置,当满⾜左上、右上和正上⽅向⽆皇后,即矩阵中对应位置都为0,则可以确定皇后位置,依次判断下⼀⾏的皇后位置。
当到达第8⾏时,说明⼋个皇后安置完毕。
代码如下:#include<iostream>using namespace std;#define N 8int a[N][N];int count=0;//判断是否可放bool search(int r,int c){int i,j;//左上+正上for(i=r,j=c; i>=0 && j>=0; i--,j--){if(a[i][j] || a[i][c]){return false;}}//右上for(i=r,j=c; i>=0 && j<N; i--,j++){if(a[i][j]){return false;}}return true;}//输出void print(){for(int i=0;i<N;i++){for(int j=0;j<N;j++){cout<<a[i][j]<<" ";}cout<<endl;}}//回溯法查找适合的放法void queen(int r){if(r == 8){count++;cout<<"第"<<count<<"种放法\n";print();cout<<endl;return;}int i;for(i=0; i<N; i++){if(search(r,i)){a[r][i] = 1;queen(r+1);a[r][i] = 0;}}}//⼊⼝int main(){queen(0);cout<<"⼀共有"<<count<<"放法\n"; return 0;}。
回溯算法原理和几个常用的算法实例
回溯算法原理和几个常用的算法实例回溯算法是一种基于深度优先的算法,用于解决在一组可能的解中找到满足特定条件的解的问题。
其核心思想是按照特定的顺序逐步构造解空间,并通过剪枝策略来避免不必要的。
回溯算法的实现通常通过递归函数来进行,每次递归都尝试一种可能的选择,并在达到目标条件或无法继续时进行回溯。
下面介绍几个常用的回溯算法实例:1.八皇后问题:八皇后问题是一个经典的回溯问题,要求在一个8×8的棋盘上放置8个皇后,使得每个皇后都不能相互攻击。
即每行、每列和对角线上都不能有两个皇后。
通过在每一列中逐行选择合适的位置,并进行剪枝,可以找到所有满足条件的解。
2.0-1背包问题:0-1背包问题是一个经典的组合优化问题,要求在一组物品中选择一些物品放入背包,使得其总重量不超过背包容量,同时价值最大化。
该问题可以通过回溯算法进行求解,每次选择放入或不放入当前物品,并根据剩余物品和背包容量进行递归。
3.数独问题:数独问题是一个经典的逻辑推理问题,要求在一个9×9的网格中填入数字1-9,使得每行、每列和每个3×3的子网格中都没有重复数字。
该问题可以通过回溯算法进行求解,每次选择一个空格,并依次尝试1-9的数字,然后递归地进行。
4.字符串的全排列:给定一个字符串,要求输出其所有可能的排列。
例如,对于字符串"abc",其所有可能的排列为"abc"、"acb"、"bac"、"bca"、"cab"和"cba"。
可以通过回溯算法进行求解,每次选择一个字符,并递归地求解剩余字符的全排列。
回溯算法的时间复杂度通常比较高,因为其需要遍历所有可能的解空间。
但是通过合理的剪枝策略,可以减少的次数,提高算法效率。
在实际应用中,可以根据具体问题的特点来设计合适的剪枝策略,从而降低算法的时间复杂度。
leetcode回溯算法经典题
文章标题:探寻经典题:深度解析leetcode回溯算法在计算机算法中,回溯算法是一种重要且常见的算法,通过对可能的解的全面搜索,寻找满足条件的解。
而在leetcode中,回溯算法经典题更是考验着我们对算法的理解和应用能力。
本文将深入探讨leetcode回溯算法经典题,并从多个角度全面解析其内涵和应用。
一、leetcode回溯算法简介回溯算法通常用于解决组合、排列、求子集、棋盘、全排列等问题。
它通过尝试所有可能的解来解决问题,直到找到满足条件的解或者尝试完所有可能的解。
在leetcode中,回溯算法题目通常以树形结构展现,根据不同的条件进行深度搜索。
二、leetcode回溯算法题目解析1. 组合总和组合总和是典型的回溯算法题目,在给定一组候选数字和一个目标值的情况下,找出所有的候选数字组合使得它们的和等于目标值。
这个题目可以通过回溯算法递归的方式解题,不断尝试不同的组合直到得到满足条件的解。
2. 全排列全排列是另一个经典的回溯算法题目,在给定一个没有重复数字的序列,返回其所有可能的全排列。
这个题目同样可以通过回溯算法深度搜索的方式解题,不断尝试不同的排列直到得到所有可能的解。
3. 子集子集问题也是回溯算法的典型题目之一,在给定一个可能包含重复元素的数组,返回该数组所有可能的子集。
通过回溯算法,我们可以递归地生成数组的不同子集,直到得到所有可能的解。
三、leetcode回溯算法题目要点总结通过对以上三个典型题目的解析,我们可以总结出leetcode回溯算法题目的几个关键要点:1. 深度搜索:通过不断尝试所有可能的解,直到找出满足条件的解为止。
2. 递归调用:回溯算法通常通过递归调用来实现,每次递归实现一步搜索。
3. 剪枝优化:为了减少搜索空间,我们可以通过一些条件判断和剪枝策略来对搜索过程进行优化。
四、个人理解和观点共享对于leetcode回溯算法题目,我个人的理解是,它需要我们在尝试所有可能的解的过程中,不断优化搜索策略并减少搜索空间,以更快地找到满足条件的解。
八皇后问题
八皇后问题八皇后问题是一个古老而著名的问题,是回溯算法的典型例题。
该问题是十九世纪著名的数学家高斯1850年提出:在8X8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。
高斯认为有76种方案。
1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。
对于八皇后问题的实现,如果结合动态的图形演示,则可以使算法的描述更形象、更生动,使教学能产生良好的效果。
下面是用Turbo C实现的八皇后问题的图形程序,能够演示全部的92组解。
八皇后问题动态图形的实现,主要应解决以下两个问题。
(1)回溯算法的实现(a)为解决这个问题,我们把棋盘的横坐标定为i,纵坐标定为j,i和j的取值范围是从1到8。
当某个皇后占了位置(i,j)时,在这个位置的垂直方向、水平方向和斜线方向都不能再放其它皇后了。
用语句实现,可定义如下三个整型数组:a[8],b[15],c[24]。
其中:a[j-1]=1 第j列上无皇后a[j-1]=0 第j列上有皇后b[i+j-2]=1 (i,j)的对角线(左上至右下)无皇后b[i+j-2]=0 (i,j)的对角线(左上至右下)有皇后c[i-j+7]=1 (i,j)的对角线(右上至左下)无皇后c[i-j+7]=0 (i,j)的对角线(右上至左下)有皇后(b)为第i个皇后选择位置的算法如下:for(j=1;j<=8;j++) /*第i个皇后在第j行*/if ((i,j)位置为空))/*即相应的三个数组的对应元素值为1*/{占用位置(i,j)/*置相应的三个数组对应的元素值为0*/if i<8为i+1个皇后选择合适的位置;else 输出一个解}(2)图形存取在Turbo C语言中,图形的存取可用如下标准函数实现:size=imagesize(x1,y1,x2,y2) ;返回存储区域所需字节数。
八皇后问题
八皇后问题编辑八皇后问题,是一个古老而著名的问题,是回溯算法的典型例题。
该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8X8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。
高斯认为有76种方案。
1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。
计算机发明后,有多种方法可以解决此问题。
八皇后问题最早是由国际西洋棋棋手马克斯·贝瑟尔于1848年提出。
之后陆续有数学家对其进行研究,其中包括高斯和康托,并且将其推广为更一般的n皇后摆放问题。
八皇后问题的第一个解是在1850年由弗朗兹·诺克给出的。
诺克也是首先将问题推广到更一般的n皇后摆放问题的人之一。
1874年,S.冈德尔提出了一个通过行列式来求解的方法,这个方法后来又被J.W.L.格莱舍加以改进。
艾兹格·迪杰斯特拉在1972年用这个问题为例来说明他所谓结构性编程的能力。
八皇后问题在1990年代初期的著名电子游戏第七访客和NDS平台的著名电子游戏雷顿教授与不可思议的小镇中都有出现。
2名词解释算法介绍八皇后问题是一个以国际象棋为背景的问题:如何能够在 8×8 的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?为了达到此目的,任两个皇后都不能处于同一条横行、纵行或斜线上。
八皇后问题可以推广为更一般的n 皇后摆放问题:这时棋盘的大小变为n ×n ,而皇后个数也变成n 。
当且仅当 n = 1 或 n ≥ 4时问题有解。
C 语言1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 intn=8;intx[9];intnum = 0;//解的个数//判断第k 个皇后能否放在第x[k]列boolPlace(intk){inti = 1;while ( i < k){if ( x[i]==x[k] || (abs (x[i]-x[k]) ==abs (i-k)) )returnfalse ;i++;}returntrue ;}void nQueens(intn){x[0] = x[1] =0;intk=1;while (k > 0){x[k]+=1;//转到下一行while (x[k]<=n && Place(k)==false ){//如果无解,最后一个皇后就会安排到格子外面去 x[k]+=1;}if (x[k]<=n){//第k 个皇后仍被放置在格子内,有解if (k==n){num++;cout << num <<":\t";for (inti=1; i<=n; i++){28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 cout << x[i] <<"\t";}cout << endl;}else {k++;x[k]=0;//转到下一行}}else //第k 个皇后已经被放置到格子外了,没解,回溯k--;//回溯}}int_tmain(intargc, _TCHAR* argv[]){nQueens(n);getchar ();return 0;}Java 算法1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 publicclass Queen {// 同栏是否有皇后,1表示有privateint [] column;// 右上至左下是否有皇后privateint [] rup;// 左上至右下是否有皇后privateint [] lup;// 解答privateint [] queen;// 解答编号privateint num;public Queen() {column =newint [8+1];rup =newint [2*8+1];lup =newint [2*8+1];for (int i =1; i <=8; i++)column[i] =1;2223242526272829303132333435363738394041424344454647484950515253545556575859606162636465 for(int i =1; i <=2*8; i++)rup[i] = lup[i] =1;queen =newint[8+1];}publicvoid backtrack(int i) {if(i >8) {showAnswer();}else{for(int j =1; j <=8; j++) {if(column[j] ==1&&rup[i+j] ==1&&lup[i-j+8] ==1) {queen[i] = j;// 设定为占用column[j] = rup[i+j] = lup[i-j+8] =0; backtrack(i+1);column[j] = rup[i+j] = lup[i-j+8] =1; }}}}protectedvoid showAnswer() {num++;System.out.println("\n解答 "+ num);for(int y =1; y <=8; y++) {for(int x =1; x <=8; x++) {if(queen[y] == x) {System.out.print(" Q");}else{System.out.print(" .");}}System.out.println();}}publicstaticvoid main(String[] args) {Queen queen =new Queen();queen.backtrack(1);66 67 }}Erlang 算法-module(queen).-export([printf/0,attack_range/2]).-define(MaxQueen, 4).%寻找字符串所有可能的排列%perms([]) ->%[[]];%perms(L) ->% [[H | T] || H <- L, T <-perms(L -- [H])].perms([]) ->[[]];perms(L)->[[H | T] || H <- L, T <- perms(L -- [H]),attack_range(H,T) == []].printf() ->L =lists:seq(1, ?MaxQueen),io:format("~p~n",[?MaxQueen]),perms(L).%检测出第一行的数字攻击到之后各行哪些数字%left 向下行的左侧检测%right 向下行的右侧检测attack_range(Queen,List) ->attack_range(Queen,left, List) ++ attack_range(Queen,right, List).attack_range(_, _, [])->[];attack_range(Queen, left, [H | _]) whenQueen - 1 =:= H ->[H];attack_range(Queen,right, [H | _]) when Queen + 1 =:= H->[H];attack_range(Queen, left, [_ | T])->attack_range(Queen - 1, left,T);attack_range(Queen, right, [_ | T])->attack_range(Queen + 1, right, T).C 语言算法C 代码头文件1 2 3 4 5 6 7 8 9 10 11 //eigqueprob.h#include#define N 8 /* N 表示皇后的个数 *//* 用来定义答案的结构体*/typedefstruct {intline;/* 答案的行号 */introw;/* 答案的列号 */}ANSWER_TYPE;/* 用来定义某个位置是否被占用 */12 13 14 15 16 17 18 19 20 typedefenum {notoccued = 0,/* 没被占用 */occued = 1/* 被占用 */}IFOCCUED; /* 该列是否已经有其他皇后占用 */IFOCCUED rowoccu[N];/* 左上-右下对角位置已经有其他皇后占用 */IFOCCUED LeftTop_RightDown[2*N-1];/* 右上-左下对角位置已经有其他皇后占用*/IFOCCUED RightTop_LefttDown[2*N-1];/* 最后的答案记录 */ANSWER_TYPE answer[N];主程序1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include "eigqueprob.h"/* 寻找下一行占用的位置 */void nextline(intLineIndex){static asnnum = 0;/* 统计答案的个数 */intRowIndex = 0;/* 列索引 */intPrintIndex = 0;/* 按列开始遍历 */for (RowIndex=0;RowIndex{/* 如果列和两个对角线上都没有被占用的话,则占用该位置 */if ((notoccued == rowoccu[RowIndex])\&&(notoccued == LeftTop_RightDown[LineIndex-RowIndex+N-1])\&&(notoccued == RightTop_LefttDown[LineIndex+RowIndex])){/* 标记已占用 */rowoccu[RowIndex] = occued;LeftTop_RightDown[LineIndex-RowIndex+N-1] = occued;RightTop_LefttDown[LineIndex+RowIndex] = occued;/* 标记被占用的行、列号 */answer[LineIndex].line = LineIndex;answer[LineIndex].row = RowIndex;/* 如果不是最后一行,继续找下一行可以占用的位置 */if ((N-1) > LineIndex ){nextline(LineIndex+1);}/* 如果已经到了最后一行,输出结果 */else{asnnum++;printf ("\nThe %dth answer is :",asnnum);for (PrintIndex=0;PrintIndex{343536373839404142434445464748495051525354 printf("(%d,%d) ",answer[PrintIndex].line+1,answer[PrintIndex].row+1}/* 每10个答案一组,与其他组隔两行 */if((asnnum % 10) == 0)printf("\n\n");}/* 清空占用标志,寻找下一组解 */rowoccu[RowIndex] = notoccued;LeftTop_RightDown[LineIndex-RowIndex+N-1] = notoccued;RightTop_LefttDown[LineIndex+RowIndex] = notoccued;}}}main(){inti = 0;/* 调用求解函数*/nextline(i);/* 保持屏幕结果*/getchar();}C语言实现图形实现对于八皇后问题的实现,如果结合动态的图形演示,则可以使算法的描述更形象、更生动,使教学能产生良好的效果。
五大常见算法策略之——回溯策略
五⼤常见算法策略之——回溯策略回溯策略欢迎⼤家访问我的个⼈搭建的博客回溯是五⼤常⽤算法策略之⼀,它的核⼼思想其实就是将解空间看作是⼀棵树的结构,从树根到其中⼀个叶⼦节点的路径就是⼀个可能的解,根据约束条件,即可得到满⾜要求的解。
求解问题时,发现到某个节点⽽不满⾜求解的条件时,就“回溯”返回,尝试别的路径。
回溯法是⼀种选优搜索法,按选优条件向前搜索,以达到⽬标。
下⾯通过⼏个例⼦来讨论这个算法策略。
货郎问题有⼀个推销员,要到n个城市推销商品,他要找出⼀个包含所有n个城市的具有最短路程的环路。
(最后回到原来的城市),也就是说给⼀个⽆向带权图G<V,E>,⽤⼀个邻接矩阵来存储两城市之间的距离(即权值),要求⼀个最短的路径。
我们设置⼀组数据如下:4个城市,之间距离如下图所⽰,默认从0号城市出发由此我们可以画出⼀棵解空间树:(只画了⼀部分,右边同理)按照这个解空间树,对其进⾏深度优先搜索,通过⽐较即可得到最优结果(即最短路径)package BackTrack;//解法默认从第0个城市出发,减⼩了问题难度,主要⽬的在于理解回溯策略思想public class Saleman {//货郎问题的回溯解法static int[][] map = {{ 0,10,5,9},{10,0,6,9},{ 5,6,0,3},{ 9,9,3,0}}; //邻接矩阵public static final int N = 4; //城市数量static int Min = 10000; //记录最短的长度static int[] city = {1,0,0,0}; //默认第0个城市已经⾛过static int[] road = new int[N]; //路线,road[i] = j表⽰第i个城市是第j个经过的/**** @param city 保存城市是否被经过,0表⽰未被⾛过,1表⽰已经⾛过* @param j 上⼀层⾛的是第⼏个城市* @param len 此时在当前城市⾛过的距离总和* @param level 当前所在的层数,即第⼏个城市*/public static void travel(int[] city, int j, int len, int level) {if(level == N - 1) { //到达最后⼀个城市/*do something*/if(len+map[j][0] < Min) {Min = len + map[j][0];for (int i = 0; i < city.length; i++) {road[i] = city[i];}}return;}for(int i = 0; i < N; i++) {if(city[i] == 0 && map[j][i] != 0) { //第i个城市未被访问过,且上⼀层访问的城市并不是此城市city[i] = level+2; //将此城市置为已访问travel(city, i, len+map[j][i], level+1);city[i] = 0; //尝试完上⼀层的路径后,将城市⼜置为未访问,以免影响后⾯的尝试情况,避免了clone数组的情况,节省内存开销}}}public static void main(String[] args) {travel(city,0,0,0);System.out.println(Min);for (int i = 0; i < N; i++) {System.out.print(road[i]+" ");}System.out.println("1");}}⼋皇后问题要在n*n的国际象棋棋盘中放n个皇后,使任意两个皇后都不能互相吃掉。
回溯算法在生活中案例
回溯算法在生活中案例
回溯算法是一种通过探索所有可能的解来解决问题的算法,当发现当前解不满足条件时,它会回溯到上一步,重新尝试其他可能的解。
以下是一些回溯算法在生活中的实际应用案例:
1. 组合优化问题:在日常生活中,很多问题可以通过组合优化问题来求解。
例如,旅行商问题(Traveling Salesman Problem),该问题是一个著名的组合优化问题,通过回溯算法可以找到最短路径或最优解。
2. 游戏AI:在游戏中,AI常常需要做出决策,而回溯算法可以帮助AI在游戏中进行决策。
例如,在棋类游戏中,AI可以使用回溯算法来分析游戏局面,预测游戏的胜负结果。
3. 数据库查询优化:在数据库查询中,回溯算法可以用于优化查询。
例如,在关系型数据库中,查询优化器可以使用回溯算法来选择最优的查询计划。
4. 编译器设计:在编译器的设计中,回溯算法可以用于语法分析。
编译器通过语法分析将源代码转化为机器代码,而回溯算法可以帮助编译器检查源代码是否符合语法规则。
5. 图像处理:在图像处理中,回溯算法可以用于图像修复、去噪等任务。
通过回溯算法可以找到最优的修复方案或去噪参数。
6. 决策支持系统:在决策支持系统中,回溯算法可以帮助决策者进行决策。
例如,在医疗诊断中,医生可以使用回溯算法来分析病人的病情,并给出最佳的治疗方案。
总之,回溯算法在许多领域都有广泛的应用,可以帮助人们解决复杂的问题。
最佳调度问题(回溯算法)
if( time_machine[i] > time_max_K )
time_max_K = time_machine[i];
}
if(time_max_K<time_min)
{
time_min = time_max_K;
for(inti=1;i<=N;i++)
Res[i] = x[i];
}
}
else
{
for(inti=1;i<=K;i++)
{
x[k] = i;
if( placetest(k) )
{
time_machine[i] += t[k];
Backtrack(k+1,t,x);
time_machine[i] -= t[k];
}
}
}
}
三、结果与分析:
附录(源代码)
算法源代码(C++描述)
/*
}
if(time_max_K > time_min)
return false;
else
return true;
}
void Backtrack(int k,int t[],int x[])
{
if(k > N )
{
int time_max_K = time_machine[1];
for(int i=2;i<=K;i++)
结点;直至找到一个解或全部解。
二、核心代码:
boolplacetest(intk)
{
inttime_max_K = time_machine[1];
for(inti=2;i<=K;i++)
八皇后问题—经典回溯算法
⼋皇后问题—经典回溯算法⼋皇后问题⼋皇后问题,是⼀个古⽼⽽著名的问题,是回溯算法的典型案例。
该问题是国际西洋棋棋⼿马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放⼋个皇后,使其不能互相攻击,即任意两个皇后都不能处于同⼀⾏、同⼀列或同⼀斜线上,问有多少种摆法。
⾼斯认为有76种⽅案。
1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有⼈⽤图论的⽅法解出92种结果。
回溯算法思想回溯算法的基本思想是:从⼀条路往前⾛,能进则进,不能进则退回来,换⼀条路再试。
⼋皇后问题就是回溯算法的典型,第⼀步按照顺序放⼀个皇后,然后第⼆步符合要求放第2个皇后,如果没有位置符合要求,那么就要改变第⼀个皇后的位置,重新放第2个皇后的位置,直到找到符合条件的位置就可以了。
回溯在迷宫搜索中使⽤很常见,就是这条路⾛不通,然后返回前⼀个路⼝,继续下⼀条路。
回溯算法说⽩了就是穷举法。
不过回溯算法使⽤剪枝函数,剪去⼀些不可能到达最终状态(即答案状态)的节点,从⽽减少状态空间树节点的⽣成。
回溯法是⼀个既带有系统性⼜带有跳跃性的的搜索算法。
它在包含问题的所有解的解空间树中,按照深度优先的策略,从根结点出发搜索解空间树。
算法搜索⾄解空间树的任⼀结点时,总是先判断该结点是否肯定不包含问题的解。
如果肯定不包含,则跳过对以该结点为根的⼦树的系统搜索,逐层向其祖先结点回溯。
否则,进⼊该⼦树,继续按深度优先的策略进⾏搜索。
回溯法在⽤来求问题的所有解时,要回溯到根,且根结点的所有⼦树都已被搜索遍才结束。
⽽回溯法在⽤来求问题的任⼀解时,只要搜索到问题的⼀个解就可以结束。
这种以深度优先的⽅式系统地搜索问题的解的算法称为回溯法,它适⽤于解⼀些组合数较⼤的问题。
⼋皇后实现⼆以下实现是极客时间王争的解法,⾮常巧妙,思路也⾮常清晰,如果理解了⼋皇后问题的本质后建议采⽤该⽅法,代码实现如下:#include <iostream>int queenPlace[8] = { 8 }; //全局变量,下标表⽰⾏,值表⽰queen存储在那⼀列int count = 0; //计数器void printQueen() { //打印⼀个⼆维数组for (int i = 0; i < 8; ++i) {for (int j = 0; j < 8; ++j) {if (queenPlace[i] == j) {printf("Q ");} else {printf("* ");}}printf("\n");}printf("----count:%d-----\n", ++count);}bool isOk(int row, int col) { //判断row⾏col列放置是否合适int leftUp = col - 1; //左上对⾓线int rightUp = col + 1; //右上对⾓线for (int i = row - 1; i >= 0; --i) {if (queenPlace[i] == col) return false; //同列上的格⼦有皇后if (leftUp >= 0) {if (queenPlace[i] == leftUp) return false; //左上对⾓线有皇后}if (rightUp < 8) {if (queenPlace[i] == rightUp) return false; //右上对⾓线有皇后}--leftUp; ++rightUp;}return true;}void eightQueen(int row) {if (row == 8) { //8个皇后都放置好,打印,⽆法递归返回printQueen();return;}for (int col = 0; col < 8; ++col) { //每⼀⾏都有8种⽅法if (isOk(row, col)) { //满⾜要求queenPlace[row] = col; //第row⾏的皇后放在col列eightQueen(row+1); //考察下⼀⾏}}}int main() {eightQueen(0);return0;class Solution {public:vector<vector<string>> res;vector<int> n_queen;vector<vector<string>> solveNQueens(int n) {n_queen.resize(n);backtrack(0);return res;}void backtrack(int row) {if (row == n_queen.size()) {storeResult();return;}for (int i = 0; i < n_queen.size(); ++i) {if (!isOk(row, i)) continue;n_queen[row] = i;backtrack(row + 1);}}bool isOk(int row, int col) {int left_up = col - 1;int right_up = col + 1;for (int i = row - 1; i >= 0; --i) {if (n_queen[i] == col // 当前列|| n_queen[i] == left_up-- // 左上对⾓,⽆需判断 left_up < 0, 该情况不会成⽴的 || n_queen[i] == right_up++) { // 右上对⾓,⽆需判断 right_up > n_queen.size() return false;}}return true;}void storeResult() {vector<string> result;for (auto i : n_queen) {string s(n_queen.size(), '.');s[i] = 'Q';result.push_back(s);}res.push_back(result);}};解法2:class Solution {public:vector<bool> col;vector<bool> dia1;vector<bool> dia2;vector<vector<string>> result;vector<string> generateQueen(vector<int>& q){vector<string> res;for (int i = 0; i < q.size(); ++i){string s(q.size(), '.');s[q[i]] = 'Q';res.push_back(s);}return res;}void traceBack(int n, int row, vector<int>& q){if (row == n) {result.push_back(generateQueen(q));return;}for (int i = 0; i < n; ++i){if (!col[i] && !dia1[row + i] && !dia2[row - i + n - 1]){q.push_back(i);col[i] = true;dia1[row + i] = true;dia2[row - i + n - 1] = true;traceBack(n, row + 1, q);col[i] = false;dia1[row + i] = false;dia2[row - i + n - 1] = false;q.pop_back();}}vector<vector<string>> solveNQueens(int n) { col = vector<bool>(n, false);dia1 = vector<bool>(2 * n - 1, false);dia2 = vector<bool>(2 * n - 1, false);vector<int> q;traceBack(n, 0, q);return result;}};。
经典回溯算法:集合划分问题
经典回溯算法:集合划分问题读完本⽂,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题⽬:-----------之前说过回溯算法是笔试中最好⽤的算法,只要你没什么思路,就⽤回溯算法暴⼒求解,即便不能通过所有测试⽤例,多少能过⼀点。
回溯算法的技巧也不难,前⽂说过,回溯算法就是穷举⼀棵决策树的过程,只要在递归之前「做选择」,在递归之后「撤销选择」就⾏了。
但是,就算暴⼒穷举,不同的思路也有优劣之分。
本⽂就来看⼀道⾮常经典的回溯算法问题,⼒扣第 698 题「划分为k个相等的⼦集」。
这道题可以帮你更深刻理解回溯算法的思维,得⼼应⼿地写出回溯函数。
题⽬⾮常简单:给你输⼊⼀个数组nums和⼀个正整数k,请你判断nums是否能够被平分为元素和相同的k个⼦集。
函数签名如下:boolean canPartitionKSubsets(int[] nums, int k);我们之前写过⼀次⼦集划分问题,不过那道题只需要我们把集合划分成两个相等的集合,可以转化成背包问题⽤动态规划技巧解决。
但是如果划分成多个相等的集合,解法⼀般只能通过暴⼒穷举,时间复杂度爆表,是练习回溯算法和递归思维的好机会。
⼀、思路分析⾸先,我们回顾⼀下以前学过的排列组合知识:1、P(n, k)(也有很多书写成A(n, k))表⽰从n个不同元素中拿出k个元素的排列(Permutation/Arrangement);C(n, k)表⽰从n个不同元素中拿出k个元素的组合(Combination)总数。
2、「排列」和「组合」的主要区别在于是否考虑顺序的差异。
3、排列、组合总数的计算公式:好,现在我问⼀个问题,这个排列公式P(n, k)是如何推导出来的?为了搞清楚这个问题,我需要讲⼀点组合数学的知识。
排列组合问题的各种变体都可以抽象成「球盒模型」,P(n, k)就可以抽象成下⾯这个场景:即,将n个标记了不同序号的球(标号为了体现顺序的差异),放⼊k个标记了不同序号的盒⼦中(其中n >= k,每个盒⼦最终都装有恰好⼀个球),共有P(n, k)种不同的⽅法。
回溯算法经典问题
回溯算法经典问题请设计⼀个函数,⽤来判断在⼀个矩阵中是否存在⼀条包含某字符串所有字符的路径。
路径可以从矩阵中的任意⼀个格⼦开始,每⼀步可以在矩阵中向左,向右,向上,向下移动⼀个格⼦。
如果⼀条路径经过了矩阵中的某⼀个格⼦,则该路径不能再进⼊该格⼦。
分析:回溯算法这是⼀个可以⽤回朔法解决的典型题。
⾸先,在矩阵中任选⼀个格⼦作为路径的起点。
如果路径上的第i个字符不是ch,那么这个格⼦不可能处在路径上的第i个位置。
如果路径上的第i个字符正好是ch,那么往相邻的格⼦寻找路径上的第i+1个字符。
除在矩阵边界上的格⼦之外,其他格⼦都有4个相邻的格⼦。
重复这个过程直到路径上的所有字符都在矩阵中找到相应的位置。
由于回朔法的递归特性,路径可以被开成⼀个栈。
当在矩阵中定位了路径中前n个字符的位置之后,在与第n个字符对应的格⼦的周围都没有找到第n+1个字符,这个时候只要在路径上回到第n-1个字符,重新定位第n个字符。
由于路径不能重复进⼊矩阵的格⼦,还需要定义和字符矩阵⼤⼩⼀样的布尔值矩阵,⽤来标识路径是否已经进⼊每个格⼦。
当矩阵中坐标为(row,col)的格⼦和路径字符串中相应的字符⼀样时,从4个相邻的格⼦(row,col-1),(row-1,col),(row,col+1)以及(row+1,col)中去定位路径字符串中下⼀个字符如果4个相邻的格⼦都没有匹配字符串中下⼀个的字符,表明当前路径字符串中字符在矩阵中的定位不正确,我们需要回到前⼀个,然后重新定位。
⼀直重复这个过程,直到路径字符串上所有字符都在矩阵中找到合适的位置class Solution {public:bool hasPath(char* matrix, int rows, int cols, char* str){if(str==NULL||rows<=0||cols<=0)return false;bool *isOk=new bool[rows*cols]();for(int i=0;i<rows;i++){for(int j=0;j<cols;j++)if(isHsaPath(matrix,rows,cols,str,isOk,i,j))return true;}return false;}bool isHsaPath(char *matrix,int rows,int cols,char *str,bool *isOk,int curx,int cury){if(*str=='\0')return true;if(cury==cols){curx++;cury=0;}if(cury==-1){curx--;cury=cols-1;}if(curx<0||curx>=rows)return false;if(isOk[curx*cols+cury]||*str!=matrix[curx*cols+cury])return false;isOk[curx*cols+cury]=true;bool sign=isHsaPath(matrix,rows,cols,str+1,isOk,curx-1,cury)||isHsaPath(matrix,rows,cols,str+1,isOk,curx+1,cury)||isHsaPath(matrix,rows,cols,str+1,isOk,curx,cury-1)||isHsaPath(matrix,rows,cols,str+1,isOk,curx,cury+1);isOk[curx*cols+cury]=false;return sign;}};。
回溯算法的一些例题
回溯算法搜索与回溯是计算机解题中常用的算法,很多问题无法根据某种确定的计算法则来求解,可以利用搜索与回溯的技术求解。
回溯是搜索算法中的一种控制策略。
它的基本思想是:为了求得问题的解,先选择某一种可能情况向前探索,在探索过程中,一旦发现原来的选择是错误的,就退回一步重新选择,继续向前探索,如此反复进行,直至得到解或证明无解。
如迷宫问题:进入迷宫后,先随意选择一个前进方向,一步步向前试探前进,如果碰到死胡同,说明前进方向已无路可走,这时,首先看其它方向是否还有路可走,如果有路可走,则沿该方向再向前试探;如果已无路可走,则返回一步,再看其它方向是否还有路可走;如果有路可走,则沿该方向再向前试探。
按此原则不断搜索回溯再搜索,直到找到新的出路或从原路返回入口处无解为止。
递归回溯法算法框架[一]procedure Try(k:integer);beginfor i:=1 to 算符种数 Doif 满足条件 thenbegin保存结果if 到目的地 then 输出解else Try(k+1);恢复:保存结果之前的状态{回溯一步}end;end;递归回溯法算法框架[二]procedure Try(k:integer);beginif 到目的地 then 输出解elsefor i:=1 to 算符种数 Doif 满足条件 thenbegin保存结果Try(k+1);end;end;例 1:素数环:把从1到20这20个数摆成一个环,要求相邻的两个数的和是一个素数。
【算法分析】非常明显,这是一道回溯的题目。
从1 开始,每个空位有20(19)种可能,只要填进去的数合法:与前面的数不相同;与左边相邻的数的和是一个素数。
第 20个数还要判断和第1个数的和是否素数。
〖算法流程〗1、数据初始化; 2、递归填数:判断第J种可能是否合法;A、如果合法:填数;判断是否到达目标(20个已填完):是,打印结果;不是,递归填下一个;B、如果不合法:选择下一种可能;【参考程序】program z74;框架[一]var a:array[0..20]of byte;b:array[0..20]of boolean;total:integer;function pd(x,y:byte):boolean;var k,i:byte;begink:=2; i:=x+y; pd:=false;while (k<=trunc(sqrt(i)))and(i mod k<>0) do inc(k);if k>trunc(sqrt(i)) then pd:=true;end;procedure print;var j:byte;begininc(total);write('<',total,'>:');for j:=1 to 20 do write(a[j],' ');writeln;end;procedure try(t:byte);var i:byte;beginfor i:=1 to 20 doif pd(a[t-1],i)and b[i] thenbegina[t]:=i; b[i]:=false;if t=20 then begin if pd(a[20],a[1]) then print;end b[i]:=true;end;end;BEGINfillchar(b,sizeof(b),#1);total:=0;try(1);write('total:',total);END.通过观察,我们可以发现实现回溯算法的特性:在解决过程中首先必须要先为问题定义一个解的空间.这个空间必须包含问题的一个解。
回溯算法的应用
回溯算法的应用回溯算法是一种通过逐步尝试所有可能的解决方案来解决问题的算法。
它是一个递归的算法,通过尝试一条路径,如果不符合要求,则退回到上一步,重新选择路径,直到找到问题的解决方案。
回溯算法适用于那些可以通过排列、组合、选择的方式进行解决的问题。
它的应用非常广泛,下面是一些常见的应用场景:1.八皇后问题:八皇后问题是一个著名的回溯算法问题。
在一个8x8的棋盘上放置八个皇后,使得它们不能相互攻击(同一行、同一列、同一对角线)。
通过回溯算法,可以穷举所有可能的解决方案,并找到满足条件的解。
2.数独问题:数独是一种非常经典的数学游戏,通过在9x9的格子中填入数字,使得每一行、每一列、每一个3x3的子网格都包含了数字1-9,且不重复。
回溯算法可以用来解决数独问题,通过穷举所有可能的填数方式,找到满足条件的解。
3.图的遍历:回溯算法可以用来遍历图中的所有节点。
通过递归地尝试从一个节点出发,访问邻接节点,并对每个邻接节点再进行递归。
可以使用回溯算法解决深度优先(DFS)问题,如寻找图中的路径、寻找连通分量等。
4.0-1背包问题:0-1背包问题是一个经典的动态规划问题,可以使用回溯算法进行求解。
在问题中,有一个背包和一组物品,每个物品有一定的重量和价值。
目标是在背包容量有限的情况下,选择一些物品放入背包,使得背包中的总价值最大。
5.解数独问题:解数独问题是为已给出的部分填写的数独面板找到一个解决方法。
回溯算法在解数独问题上的应用是一个很好的例子。
通过逐个尝试每个格子的数字并检查是否满足数独的规则,如果不满足则回退到上一个状态重新选择,直到找到一个解决方案或穷举所有可能。
回溯算法原理和几个常用的算法实例
回溯算法原理和几个常用的算法实例回溯算法是一种通过不断尝试和回退的方式来进行问题求解的算法。
它的基本思想是在过程中,当发现当前的选择并不符合要求时,就进行回退,尝试其他的选择,直到找到符合要求的解或者遍历完所有可能的选择。
回溯算法通常用于问题求解中的和排列组合问题,比如求解八皇后问题、0-1背包问题、数独等。
下面将介绍几个常用的回溯算法实例。
1.八皇后问题:八皇后问题是指在一个8×8的国际象棋棋盘上,放置八个皇后,使得任意两个皇后都不在同一行、同一列或同一斜线上。
可以通过递归的方式依次尝试每一行的位置,并判断当前位置是否满足条件。
如果满足条件,则进入下一行尝试;否则回溯到上一行,并尝试其他的位置,直到找到解或遍历完所有的可能。
2.0-1背包问题:0-1背包问题是指在给定一组物品和一个容量为C的背包,每个物品都有自己的重量和价值,求解在不超过背包容量时,如何选择物品使得背包中物品的总价值最大。
可以通过递归的方式依次考察每个物品,并判断是否选择当前物品放入背包。
如果放入当前物品,则背包容量减小,继续递归考察下一个物品;如果不放入当前物品,则直接递归考察下一个物品。
直到遍历完所有物品或背包容量为0时,返回当前总价值。
3.数独问题:数独是一种通过填充数字的方式使得每一行、每一列和每一个九宫格内的数字都满足一定条件的谜题。
可以通过递归的方式依次尝试填充每一个空格,并判断当前填充是否符合条件。
如果符合条件,则继续递归填充下一个空格;如果不符合条件,则回溯到上一个空格,并尝试其他的数字,直到找到解或遍历完所有的可能。
回溯算法的时间复杂度一般较高,通常为指数级别。
因此,在实际应用中,可以结合剪枝等优化策略来提高算法的效率。
此外,回溯算法也可以通过非递归的方式进行实现,使用栈来存储当前的状态,从而避免递归带来的额外开销。
总之,回溯算法是一种非常有效的问题求解方法,通过不断尝试和回退,可以在复杂的空间中找到符合要求的解。
回溯算法经典例题
回溯算法经典例题回溯算法是一种解决问题的方法,其核心思想是通过深度优先搜索来寻找解决方案。
回溯算法通常用于解决需要重复操作的问题,例如迷宫问题、图论问题等。
以下是回溯算法的经典例题:1. 八皇后问题八皇后问题是一个经典的回溯算法例题,它要求在 8×8 的国际象棋棋盘上放置八个皇后,使得它们无法相互攻击。
回溯算法的核心思想是,不断尝试每个皇后的位置,直到找到合法的位置为止。
具体实现如下:```cpp#include <iostream>using namespace std;int queen_board[8][8] = {{0, 0, 0, 0, 0, 0, 0, 0},{0, 0, 0, 0, 0, 0, 0, 0},{0, 0, 0, 0, 0, 0, 0, 0},{0, 0, 0, 0, 0, 0, 0, 0},{0, 0, 0, 0, 0, 0, 0, 0},{0, 0, 0, 0, 0, 0, 0, 0},{0, 0, 0, 0, 0, 0, 0, 0},{0, 0, 0, 0, 0, 0, 0, 0}}};int main(){int n = 8;int queen = 0;for (int i = 0; i < 8; i++){for (int j = 0; j < 8; j++){if (queen_board[i][j] == 0){queen_board[i][j] = queen++;cout << "Queen placed on " << i << ", " << j << endl;}}}return 0;}```在这个实现中,我们首先初始化一个 8×8 的矩阵来表示皇后可以放置的位置,如果当前位置已经放置了一个皇后,则将该位置标记为已经放置,并重新搜索下一个位置。
回溯法习题汇总
回溯法习题汇总1.1 马拦过河卒源程序名knight.???(pas, c, cpp)可执行文件名knight.exe输入文件名knight.in输出文件名knight.out【问题描述】棋盘上A点有一个过河卒,需要走到目标B点。
卒行走的规则:可以向下、或者向右。
同时在棋盘上C点有一个对方的马,该马所在的点和所有跳跃一步可达的点称为对方马的控制点。
因此称之为“马拦过河卒”。
棋盘用坐标表示,A点(0, 0)、B点(n, m)(n, m为不超过15的整数),同样马的位置坐标是需要给出的。
现在要求你计算出卒从A点能够到达B点的路径的条数,假设马的位置是固定不动的,并不是卒走一步马走一步。
【输入】一行四个数据,分别表示B点坐标和马的坐标。
【输出】一个数据,表示所有的路径条数。
【样例】knight.in knight.out6 6 3 3 6【算法分析】从起点开始往下走(只有两个方向可以走),如果某个方向可以走再继续下一步,直到终点,此时计数。
最后输出所有的路径数。
这种方法可以找出所有可能走法,如果要输出这些走法的话这种方法最合适了,但是本题只要求输出总的路径的条数,当棋盘比较大时,本程序执行会超时,此时最好能找出相应的递推公式更合适,详见后面的递推章节。
1.2 出栈序列统计源程序名stack1.???(pas, c, cpp)可执行文件名stack1.exe输入文件名stack1.in输出文件名stack1.out【问题描述】栈是常用的一种数据结构,有n令元素在栈顶端一侧等待进栈,栈顶端另一侧是出栈序列。
你已经知道栈的操作有两·种:push和pop,前者是将一个元素进栈,后者是将栈顶元素弹出。
现在要使用这两种操作,由一个操作序列可以得到一系列的输出序列。
请你编程求出对于给定的n,计算并输出由操作数序列1,2,…,n,经过一系列操作可能得到的输出序列总数。
【输入】一个整数n(1<=n<=15)【输出】一个整数,即可能输出序列的总数目。
回溯法 ppt课件
回溯法举例:
[旅行商问题] 在这个问题中 ,给出一个n 顶点网络(有向 或无向) ,要求找出一个包含所有n 个顶点的具有最小耗 费的环路 。任何一个包含网络中所有n 个顶点的环路被称 作一个旅行(t o u r )。在旅行商问题中 ,要设法找到一 条最小耗费的旅行。 [分析]图给出了一个四顶点网络 。在这个网络中 ,一些旅
Bound(t) : 返回的值为true时 , 在当前扩展节点处 x[1: t]的取值为时 目标函数越界 , 还需由Backtrack(t+1) 对其相应的子树做进一步搜索 。否则 , 当前扩展节点处 x[1: t]的取值是目标函数越界 ,可剪去相应的子树
for循环作用: 搜索遍当前扩展的所有未搜索过的 子树。
si+1均不满足约束条件,则去掉xi , 回溯到(x 1 , x 2 , … xi-1), 添加尚 未考虑过的xi , 如此反复进行,直到(x1 , x2 , … xk) k n满足所有的 约束条件或证明无解.
E= { (x1 , x2 , … xn), xi si , si为有限集 }称为问题的解空间.
5. 1 回溯法基本思想
穷举法技术建议我们先生成所有的候选解 , 然后找出那个 具有需要特性的元素
1 、 回溯法主要思想是每次只构造解的一个分量 ,然后按照 鲜明的方法来评估这个部分构造解 。如果一个部分构造解可以进一 步构造而不会违反问题的约束 , 我们就接受对下一个分量所作的第 一个合法选择 。如果无法对下一个分量进行合法的选择 , 就不对剩 下的任何分量再做任何选择了 。在这种情况下 ,该算法进行回溯 , 把部分构造解的最后一个分量替换为它的下一个选择。
算法模式 Procedure BACKTRACK (n); {k := l;
回溯算法
三、回溯的一般步骤
回溯法正是针对这类问题,利用这类问题的
上述性质而提出来的比枚举法效率更高的算 法。
二、回溯的一般描述
procedure rbacktrack(k); begin if k > n then return else for each x(k),如果x(k)∈t(x(1)…x(k-1))且 b(x(1)…x(k))=true do begin if x(1)…x(k)是一个解 then write(x(1)…x(k) else rbacktrack(k+1); end; end;
演示
一、回溯的概念
像走迷宫这样,遇到死路就回头的搜索思路
就叫做“回溯”。
从问题的某种可能情况出发,搜索所有能到
达的可能情况,然后以其中一种可能的情况 为新的出发点,继续向下探索,当所有可能 情况都探索过且都无法到达目标的时候,再 回退到上一个出发点,继续探索另一个可能 情况,这种不断回头寻找目标的方法称为 “回溯法”。
二、回溯的一般描述
可用回溯法求解的问题P,通常要能表达为:
对于已知的由n元组(x1,x2,…,xn)组成 的一个状态空间E={(x1,x2,…,xn) ∣xi∈Si ,i=1,2,…,n},给定关于n元组 中的一个分量的一个约束集D,要求E中满足 D的全部约束条件的所有n元组。其中Si是分 量xi的定义域,且 |Si| 有限,i=1,2,…, n。我们称E中满足D的全部约束条件的任一 n元组为问题P的一个解。
骑士遍历
骑士遍历问题的解空间是从左下角到右上角
node、扩展节点)。 从E-节点可移动到一个新节点。 如果能从当前的E-节点移动到一个新节点,那么这个新 节点将变成一个活节点和新的E-节点,旧的E-节点仍是 一个活节点。 如果不能移到一个新节点,当前的E-节点就“死”了 (即不再是一个活节点),那么便只能返回到最近被考 察的活节点(回溯),这个活节点变成了新的E-节点。 当我们已经找到了答案或者回溯尽了所有的活节点时, 搜索过程结束。
回溯算法原理和几个常用的算法实例
回溯算法思想:回溯(backtracking)是一种系统地搜索问题解答的方法。
为了实现回溯,首先需要为问题定义一个解空间(solution space),这个空间必须至少包含问题的一个解(可能是最优的)。
下一步是组织解空间以便它能被容易地搜索。
典型的组织方法是图(迷宫问题)或树(N皇后问题)。
一旦定义了解空间的组织方法,这个空间即可按深度优先的方法从开始节点进行搜索。
回溯方法的步骤如下:1) 定义一个解空间,它包含问题的解。
2) 用适于搜索的方式组织该空间。
3) 用深度优先法搜索该空间,利用限界函数避免移动到不可能产生解的子空间。
回溯算法的一个有趣的特性是在搜索执行的同时产生解空间。
在搜索期间的任何时刻,仅保留从开始节点到当前节点的路径。
因此,回溯算法的空间需求为O (从开始节点起最长路径的长度)。
这个特性非常重要,因为解空间的大小通常是最长路径长度的指数或阶乘。
所以如果要存储全部解空间的话,再多的空间也不够用。
算法应用:回溯算法的求解过程实质上是一个先序遍历一棵"状态树"的过程,只是这棵树不是遍历前预先建立的,而是隐含在遍历过程中。
(1) 幂集问题(组合问题)(参见《数据结构》(严蔚敏))求含N个元素的集合的幂集。
如对于集合A={1,2,3},则A的幂集为p(A)={{1,2,3},{1,2},{1,3},{1},{2,3},{2},{3},Φ}幂集的每个元素是一个集合,它或是空集,或含集合A中的一个元素,或含A 中的两个元素,或者等于集合A。
反之,集合A中的每一个元素,它只有两种状态:属于幂集的元素集,或不属于幂集元素集。
则求幂集P(A)的元素的过程可看成是依次对集合A中元素进行“取”或“舍”的过程,并且可以用一棵状态树来表示。
求幂集元素的过程即为先序遍历这棵状态树的过程。
程序:#include <stdio.h>#include <malloc.h>#define ERROR 0#define OK 1typedef int ElemType;typedef struct LNode{ElemType data;struct LNode *next;} LNode,*LinkList;//初始化LinkList ListInit(){LNode *base=(LinkList)malloc(sizeof(LNode)); base->data=0;base->next=NULL;return base;}//插入一个元素int ListInsert(LinkList L,int i,ElemType e){LNode *p,*s;int j=0;p=(LNode *)L;while(p&&j<i-1){p=p->next;++j;}if(!p||j>i-1)return ERROR;s=(LNode *)malloc(sizeof(LNode));s->data=e;s->next=p->next;p->next=s;return OK;}//删除一个结点int ListDelete(LinkList &L,int i,ElemType &e) {LinkList p=L,q;int j=0;while(p->next&&j<i-1){p=p->next;++j;}if(!(p->next)||j>i-1)return ERROR;q=p->next;p->next=q->next;e=q->data;free(q);}//长度int ListLength(LinkList L){LinkList p=L;int j=0;if(!L)return ERROR;while(p->next){p=p->next;++j;}return j;}//查找一个元素int GetElem(LinkList L,int i,ElemType &e) {LNode *p=L;int j=0;while(p->next&&j<i){p=p->next;++j;}if(!p||j>i)return ERROR;e=p->data;return OK;}//输出链表元素void Display(LinkList L){LNode *p=L;if(!(p->next)){printf("NULL,");return;}elsep=p->next;while(p){printf("%d,",p->data);p=p->next;}}//求幂集void PowerSet(int i,LinkList A,LinkList &B) {int k=0;ElemType e=0;if(i>ListLength(A)){Display(B);printf("\n");}else{GetElem(A,i,e);k=ListLength(B);ListInsert(B,k+1,e);PowerSet(i+1,A,B);ListDelete(B,k+1,e);PowerSet(i+1,A,B);}}int main(){LinkList list=ListInit(); //初始化LinkList list2=ListInit();//初始化ListInsert(list,1,1);//插入元素ListInsert(list,2,2);ListInsert(list,3,3);Display(list);//输出元素printf("\npower set is:\n");PowerSet(1,list,list2);//求幂集}(2)迷宫问题(参见《数据结构》(严蔚敏))计算机解迷宫时,通常用的是"试探和回溯"的方法,即从入口出发,顺某一方向向前探索,若能走通,则继续往前走;否则沿原路退回,换一个方向再继续探索,直至所有可能的通路都探索到为止,如果所有可能的通路都试探过,还是不能走到终点,那就说明该迷宫不存在从起点到终点的通道。
第5章 回溯法(1-例子)
{ if ((count>half)||(t*(t-1)/2-count>half)) return; if (t>n) sum++;
-++-+ -
else for (int i=0;i<2;i++) { p[1][t]=i;
-+
count+=i;
for (int j=2;j<=t;j++) { p[j][t-j+1]=p[j-1][t-j+1]^p[j-1][t-j+2]; count+=p[j][t-j+1];
批处理作业调度问题要求对于给定的n个作业,制定最佳作 业调度方案,使其完成时间和达到最小。
tji 作业1 作业2 作业3
机器1 2 3 2
机器2 1 1 3
这3个作业的6种可能的调度方案是1,2,3;1,3,2;2,1,3; 2,3,1;3,1,2;3,2,1;它们所相应的完成时间和分别是19, 18,20,21,19,19。易见,最佳调度方案是1,3,2,其完成
}
n后问题
在n×n格的棋盘上放置彼此不受攻击的n个皇后。按照国际象
棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线 上的棋子。n后问题等价于在n×n格的棋盘上放置n个皇后,任 何2个皇后不放在同一行或同一列或同一斜线上。
1
Q1
2
Q2
3
Q3
4
Q4
5
Q5
6 Q6
7
Q7
8
Q8
1 2345678
13
x1=1
2
x2=2 3
4
1
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
(d)返回到上一个元素k:=k-1 ,并修改D[0]、D[1]。
(5)直到求出所有解。
二、骑士游历问题
在n×n的国际象棋上的某一位置上放置一个马,然后采用象棋中“马走日字”的规则,要求这个马能不重复地走完 n×n
一、购票问题
有2n个人排队购一件价为0.5元的商品,其中一半人拿一张1元人民币,另一半人拿一张0.5元的人民币,要使售货员在售货中,不发生找钱困难,问这2n个人应该如何排队?找出所有排队的方案。(售货员一开始就没有准备零钱)
分析:
(1)根据题意可以看出,要使售货员在售货中,不发生找钱困难,则在排队中,应该是任何情况下,持0.5元的排在前面的人数多于持1元的人数。
(2)该问题可以用二进制数表示:用0表示持0.5元的人,用1表示持1元的人,那么2n个人排队问题化为2n个0、1的排列问题。这里我们用数组B[1..
分析:
(1)本题是图论中的一个搜索问题,我们可以将该问题简化:将每个省看着一个点,而将省之间的联系看作一条边,可以得到如图2所示图形。
(2)从图2可以知道各省之间的相邻关系,我们可以数据阵列表示——矩阵,即用一个二维数组来表示:
(b) 若k<2n,则k+1→k,试探下一个元素,若k=2n则输出B[1]、B[2] ……,B[2n]。
(c)如果B[K]的值不符合要求,则B[K]再加1,试探新的值,若B[K]=2,表示第k个元素的所有值都搜索过,均不符合条件,只能返回到上一个元素B[K-1],即回溯。
三、四色问题
设有如图1的地图,每个区域代表一个省,区域中的数字表示省的编号,现在要求给每个省涂上红、蓝、黄、白四种颜色之一,同时使相邻的省份以不同的颜色区分。
图1 图2
(4)问题关键在于在填色过程中,如果即将填的颜色与相邻省的颜色相同,而且四种颜色都试探过,均不能满足要求,则需要回溯到上一个点(即前一个省),修改上一个省的颜色,重新试探下一个省的颜色。
四、填数问题
在n*n 的棋盘上(1<=n<=10)填入1,2,3,……,n*n, 共有n*n 个数,使得任意两个相邻数的和为素数。例如:当n=2时,有:
典型的回溯算法问题.txt等余震的心情,就像初恋的少女等情人,既怕他不来,又怕他乱来。 听说女人如衣服,兄弟如手足,回想起来,我竟然七手八脚地裸奔了二十多年!今天心情不好,我只有四句话想说,包括这句和前面的两句,我的话说完了!
典型ቤተ መጻሕፍቲ ባይዱ回溯算法问题
16 15 8 5
13 4 9 14
6 7 10 3
分析:
(1)从问题可以看出这是一个搜索问题,程序中必须完成判断相邻两个数的和是否是素数的任务。
(2)如果一边填数,一边求和及判断素数,程序运行速度将大大减慢,所以我们可以采用数组结构,将2~n*n之间所有素数存放在一个数组中。由于问题中指出n<=10,所以,最大的相邻两个数的和是:99+100<200。我们只需要将2~200之间的素数存放在数组中就可以了。
由图2可以得到如下矩阵
0 1 0 0 0 0 1
1 0 1 1 1 1 1
0 1 0 1 0 0 0
0 1 1 0 1 0 0
(2)用一个二维数组A,存放棋盘格子上填数的状态,其中A[1,1]=1。
(3)用一个数组下标指针,表明递归调用层次和回溯的层次。
个格子,试用计算机解决这个问题。
分析:
本题是典型的回溯算法问题,设骑士在某一位置,设(X,Y ),按规则走,下一步可以是如图 ( n=5 ) 所示的8个位置之一。
我们将重点考虑前进的方向:如果某一步可继续走下去,就试探着走下去且考虑下一步的走法,若走不通则返回,考虑另选一个位置。
(3)按行、列填入数据,并求相邻两个数的和,将其与素数数组比较,若是素数,则当前格可以填数,继续填数。若当前格没有数据可以填,则要回溯到上一格,重新填写上一格数据,再进行判断。
数据结构:
(1)用一个布尔数组T 表示1~n*n之间的数是否用过,false表示没有用过,true表示已经用过。
0 1 0 1 0 1 0
0 1 0 0 1 0 1
1 1 0 0 0 1 0
(3)从编号为1的省开始按四种颜色顺序填色,当第一个省颜色与相邻省颜色不同时,就可以确定第一个省的颜色,然后,再依次对第二、第三,……,进行处理,直到所有省份颜色都涂上为止。
2n ] 存放持币情况。
(3)设k是B数组下标指针,B[K]=0或B[K]=1,另用数组d[0]、d[1]记录0与1的个数,且必须满足:n >d[0]>=d[1]。
(4)算法:回溯搜索。
(a)先将B[1]、B[2]、……B[2n]置-1,从第一个元素开始搜索,每个元素先取0,再取1,即B[K]+1→B[K],试探新的值,若符合规则,增加一个新元素;
R[x,y ]= 1 表示省 x 与省 y 相邻
0 表示省 x 与省 y 不相邻
1 2
4 3
当n=4时,一种可以填写的方案如下表,在这里我们约定:左上角的格子里必须填数字1。程序要求:输入n ;输出
:有多个解,则输出第一行、第一列之和为最小的排列方案;若无解,则输出“NO!”
1 2 11 12