算法 64式 4、回溯算法整理__第1部分_1到13题

1算法思想回溯1.1含义以深度优先方式搜索问题解的算法称为回溯法。1.2思想按照深度优先搜索策略,从根节点出发搜索解空间树,如果某结点不包含问题解,则逐层向祖先结点回溯;否则进入子树。1.3特点深度优先搜索+递归前设置变量,递归后清除变量设置1.4适用适合求取问题所有解类型的题目。例如求出问题的多少种组合。1.5通用解法回溯...
摘要由CSDN通过智能技术生成

1算法思想

回溯

 

1.1含义

以深度优先方式搜索问题解的算法称为回溯法。

 

1.2思想

按照深度优先搜索策略,从根节点出发搜索解空间树,如果某结点不包含问题解,则逐层向祖先结点回溯;否则进入子树。

 

1.3特点

深度优先搜索+递归前设置变量,递归后清除变量设置

 

1.4适用

适合求取问题所有解类型的题目。例如求出问题的多少种组合。

 

1.5通用解法

回溯算法:

            如果当前是问题的解,打印解;

            设置变量;

            递归;

            清除变量设置。

 

1.6经典例题讲解

n皇后问题

在n×n格的国际象棋上摆放n个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。

代码如下:

   void place(int pos , vector<int>& result , vector< vector<int> >& results , int n)

   {

              if(pos == n)     //如果当前是问题的解,打印解

              {

                        results.push_back(result);

                        return;

              }

              //从中选出任意一个i值作为result[pos]摆放,作为第pos行的列

              for(int i = 0 ; i < n ; i++)

              {

                        bool isOk = true;

                        for(int j = 0 ; j < pos ; j++)

                        {

                                   //如果在同一列,说明不符合,

                                   if(result.at(j) == i)

                                   {

                                             isOk = false;

                                             break;

                                   }

                                   //如果在同一对角线

                                   if( abs( result.at(j) - i ) == abs(j - pos) )

                                   {

                                             isOk = false;

                                             break;

                                   }

                        }

                        if(isOk)

                        {

                                  

                                   result.push_back(i);                 //设置变量

                                   place(pos + 1 , result , results , n);    //递归

                                   result.pop_back();                 //清除变量设置

                        }

              }

   }

 

1.7回溯与递归的区别

回溯通常也用到递归,递归往往求取的是一个解,回溯通常求取的是多个解。回溯的过程中需要对标记量进行设置,递归完成后,需要对标记量清除设置。

 

 

2回溯系列

 

类别-编号

题目

遁去的1

1

Prime ring problem

在给定的1到n的数字中,将数字填入环中,使得环中任意2个相邻的数字和为素数。按字典序输出所有符合条件的解

计算机考研—机试指南

https://blog.csdn.net/qingyuanluofeng/article/details/47186279

关键:

1 对于其余情况,首先要判断当前的数字是否在环中,若不在环中,先标记为已用,并尝试设置第num+1个数字在ans数组中,然后继续尝试放入下一个数字,最后再将该数字

  重新标记为未使用

2 注意剪枝的基本过程:初始化剪枝标记,初始化节点,放入循环中

 

//用于判定两个数的和是否为素数。

int prime[13] = {2,3,5,7,11,13,17,19,23,29,31,37,41};

//用于存放所有数的和

int ans[N];

bool mark[N];//剪枝标记

 

//判断一个数是否为素数

bool judge(int x)

{

           //判断一个数是否为素数

           for(int i = 0 ; i < 13 ; i++)

           {

                     if(x==prime[i])

                     {

                                return true;

                     }

           }

           return false;

}

//判断最后一个数和第一个数是之和是否为质数,若是,输出答案

bool check(int n)

{

           if(judge(ans[n] + ans[1])==false)

           {

                     return false;

           }

           else

           {

                     for(int i = 1 ; i <= n ; i++)

                     {

                                if(i!=1)

                                {

                                          printf(" %d",ans[i]);

                                }

                                else

                                {

                                          printf("%d",ans[i]);

                                }

                     }

                     printf("\n");

                     return true;

           }

}

 

//num:ans数组中已经存入的数字,n表示总共输入的数字个数

void DFS(int num,int n)

{

           //如果已经存在大于一个数字,就开始检查

           if(num > 1)

           {

                     if(judge(ans[num] + ans[num-1])==false)

                     {

                                return;

                     }

           }

           //如果所有数字全部确定完毕,需要检查第一个数和第n个数的和是否为质数

           if(num==n)

           {

                     check(n);

                     //检查结束之后就返回

                     return;

           }

           //对第2个数到第n个数进行判定,递归判定

           for(int i = 2 ; i <= n ; i++)

           {

                     //如果当前数未被尝试

                     if(false==mark[i])

                     {

                                mark[i] = true;//标记该数已经被尝试

                                ans[num+1] = i;//将新的数字加入到数组中

                                DFS(num+1,n);//尝试新的数字

                                mark[i] = false;//如果尝试失败,将当前数字重新设置为未被访问

                     }

           }

}

2

货郎担问题

四个顶点的货郎担问题。求从顶点1出发,最后回到顶点1的最短路线。

输入:

4

0 0  1 7

8 0      5 1

7 2  0 1

2 5  3 0

输出:

1 3 2 4 1

6

 

 

 

算法设计与分析

https://blog.csdn.net/qingyuanluofeng/article/details/47189381

           v1                 v2                 v3                 v4

v1       无穷   无穷   1                   7

v2       8                   无穷   5                   1

v3       7                   2                   无穷   1

v4       2                   5                   3                   无穷

算法分析:

因为是采用回溯法来做,肯定是递归,然后还需要现场清理。

要设置一个二维数组来标识矩阵内容,然后回溯还需要设计

一个二维标记数组来剪枝,设定一个目标变量,初始为无穷大,

后续如果有比目标变量值小的就更新。剪枝的条件就是如果走到当前节点的耗费值>=目标变量,就直接不再往下面走,

向上走。

深度优先 = 递归

递归基:如果到达叶子节点的上一个节点,那么就进行是否更新的判断

递归步:如果没有到达叶子节点,就进行剪枝操作,判断能否进入下一个节点,如果能,更新最优值

 

关键:

1        //递归基:如果已经遍历到叶子节点的上一层节点,i标识递归深度

           if(i == g_n)

           {

                     //判断累加和是否超过最大值,如果有0,应该排除;满足这个条件,才打印

                     if((g_iArr[pArr[i-1]][pArr[i]] != 0) && (g_iArr[pArr[g_n]][1] != 0)  &&

                                (g_iCurResult + g_iArr[pArr[i-1]][pArr[i]] + g_iArr[pArr[g_n]][1] < g_iResult ))

                     {

                                g_iResult = g_iCurResult + g_iArr[pArr[i-1]][pArr[i]] + g_iArr[pArr[g_n]][1];

                                //用当前最优路径去更新最优路径,防止下一次没有

                                for(int k = 1 ; k <= g_n ; k++)

                                {

                                          g_iBestPath[k] = pArr[k];

2        //递归步:判断能否进入子树,需要尝试每一个节点

           else

           {

                     //尝试不同的组合

                     for(int j = i ; j <= g_n ; j++)

                     {

                                //判断能否进入子树:如果当前值+下一个连线值的和 < 最优值,就进入,0要pass

                                if( (g_iArr[pArr[i-1]][pArr[j]] != 0) && (g_iCurResult + g_iArr[ pArr[i-1] ][ pArr[j] ] < g_iResult) )

3                                       //交换i与j,则i为当前可以尝试的范围

                                          //为完成后面k个元素的排列,逐一对数组第n-k~n个元素互换。数组第一个元素为1,生成后面n-1个元素的排列

                                          //数组第一个元素与第二个元素互换,第一个元素为2,第2个元素为1,生成后面的n-1个元素的排列...

                                          swap(&pArr[i],&pArr[j]);

                                          //更新当前累加值,是i-1与i的

                                          g_iCurResult += g_iArr[ pArr[i-1] ][ pArr[i] ];

                                          //递归

                                          backTrace(i+1,pArr);

                                          //回溯,清空累加值;能够走到这里,说明上述结果不是最优解,需要向求解树上一层回退

                                          g_iCurResult -= g_iArr[pArr[i-1]][ pArr[i] ];

                                          swap(&pArr[i],&pArr[j]);

 

代码:

const int MAXSIZE = 100;

const int MAX = 1000000000;

int g_iArr[MAXSIZE][MAXSIZE];//邻接矩阵

int g_iResult;//存放最优解

int g_iPath[MAXSIZE];//存放最优路径上

int g_n;//元素个数

int g_iCurResult;//当前累加路径和

int g_iBestPath[MAXSIZE];//还需要设置一个数组,用来保存最优解

 

 

//可以做成字符串全排列的性质track(int i,int* pArr,int* pResult),其中pArr是用于存放最优解的路径

void backTrace(int i,int* pArr)

{

           //递归基:如果已经遍历到叶子节点的上一层节点

           if(i == g_n)

           {

                     //判断累加和是否超过最大值,如果有0,应该排除;满足这个条件,才打印

                     if((g_iArr[pArr[i-1]][pArr[i]] != 0) && (g_iArr[pArr[g_n]][1] != 0)  &&

                                (g_iCurResult + g_iArr[pArr[i-1]][pArr[i]] + g_iArr[pArr[g_n]][1] < g_iResult ))

                     {

                                g_iResult = g_iCurResult + g_iArr[pArr[i-1]][pArr[i]] + g_iArr[pArr[g_n]][1];

                                //用当前最优路径去更新最优路径,防止下一次没有

                                for(int k = 1 ; k <= g_n ; k++)

                                {

                                          g_iBestPath[k] = pArr[k];

                                }

                     }

           }

           //递归步:判断能否进入子树,需要尝试每一个节点

           else

           {

                     //尝试不同的组合

                     for(int j = i ; j <= g_n ; j++)

                     {

                                //判断能否进入子树:如果当前值+下一个连线值的和 < 最优值,就进入,0要pass

                                if( (g_iArr[pArr[i-1]][pArr[j]] != 0) && (g_iCurResult + g_iArr[ pArr[i-1] ][ pArr[j] ] < g_iResult) )

                                {

                                          //交换i与j,则i为当前可以尝试的范围

                                          //为完成后面k个元素的排列,逐一对数组第n-k~n个元素互换。数组第一个元素为1,生成后面n-1个元素的排列

                                          //数组第一个元素与第二个元素互换,第一个元素为2,第2个元素为1,生成后面的n-1个元素的排列...

                                          swap(&pArr[i],&pArr[j]);

                                          //更新当前累加值,是i-1与i的

                                          g_iCurResult += g_iArr[ pArr[i-1] ][ pArr[i] ];

                                          //递归

                                          backTrace(i+1,pArr);

                                          //回溯,清空累加值;能够走到这里,说明上述结果不是最优解,需要向求解树上一层回退

                                          g_iCurResult -= g_iArr[pArr[i-1]][ pArr[i] ];

                                          swap(&pArr[i],&pArr[j]);

                                }

                     }

           }

}

3

n皇后问题

在n×n格的国际象棋上摆放n个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。

算法设计与分析

https://blog.csdn.net/qingyuanluofeng/article/details/47189389

//判断当前列能否摆放成功。判断存不存在相同列或者在同一个斜线上

bool place(int x[] , int k)

{

           for(int i = 1 ; i < k ; i++)

           {

                     if(x[i] == x[k] || abs(x[i] - x[k]) == abs(i - k))

                     {

                                return false;

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值