回溯法习题

文章探讨了如何设计算法解决两种问题:一是计算n种不同形状和颜色宝石的排列方案,使用深度优先搜索生成解空间树;二是寻找罗密欧从起点到朱丽叶的最少转弯路径,利用回溯法在迷宫中探索。
摘要由CSDN通过智能技术生成

问题一

题干:

现有n种不同形状的宝石,每种n颗,共n*n颗。同一形状的n颗宝石分别具有n种不同的颜色c1,c2,…,cn中的一种颜色。欲将这n*n颗宝石排列成n行n列的一个方阵,使方阵中每一行和每一列的宝石都有n种不同的形状和n种不同颜色。是设计一个算法,计算出对于给定的n,有多少种不同的宝石排列方案。

解题思路:

首先搜索其所有的解空间,并生成解空间树,将满足条件的情况记录下来。由于要求每一行宝石的形状和颜色均不相同,因此我们可以从行开始,生成行的有效全排列,然后对刚生的位置进行列有效判定,如果成功则继续向下搜索,不成功则重新生成排列,继续判定。

具体代码:

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<set>
usingnamespace std;
int          used[10][10];
int          shapes[10][10];
int          colors[10][10];
set<int>  col_c[10];
set<int>  col_s[10];
int          n;
int          cnt = 0;
int dfs(int x, int y )
{
       int f = 0;
       for ( int i = y; i <= n; i++ )
       {
              swap( shapes[x][i], shapes[x][y]);
              if ( col_s[y].count( shapes[x][y]) == 0 )
              {
                     col_s[y].insert(shapes[x][y] );
                     for ( int j = y; j <= n;j++ )
                     {
                            swap( colors[x][j],colors[x][y] );
                            if ( col_c[y].count(colors[x][y] ) == 0 )
                            {
                                   col_c[y].insert(colors[x][y] );
                                   if (!used[shapes[x][y]][colors[x][y]] )
                                   {
                                          used[shapes[x][y]][colors[x][y]]    = 1;
                                          f                                  = 1;
                                          if ( x== n && y == n )
                                          {
                                                 ++cnt;
                                          }else  {
                                                 if( y == n )
                                                 {
                                                        dfs(x + 1, 1 );
                                                 }else  {
                                                        dfs(x, y + 1 );
                                                 }
                                          }
                                          used[shapes[x][y]][colors[x][y]]= 0;
                                   }
                                   col_c[y].erase(colors[x][y] );
                            }
                            swap( colors[x][j],colors[x][y] );
                     }
                     col_s[y].erase(shapes[x][y] );
              }
              swap( shapes[x][i], shapes[x][y]);
       }
       return(f);
}
 
 
int main()
{
       cin >> n;
       for ( int x = 1; x <= 9; x++ )
              for ( int i = 1; i <= 9; i++ )
              {
                     shapes[x][i] = colors[x][i]= i;
              }
       dfs( 1, 1 );
       cout << "输出:" << cnt<< endl;
       return(0);
}

问题二

题干:

罗密欧与朱丽叶身处一个m×n的迷宫中。每一个方格表示迷宫中的一个房间。这 m × n 个房间中有一些房间是封闭的,不允许任何人进入。在迷宫中任何位置均可沿 8 个方向进入未封闭的房间。罗密欧位于迷宫的(p,q)方格中,他必须找出一条通向朱丽叶所在的(r,s)方格的路。在抵达朱丽叶之前,他必须走遍所有未封闭的房间各一次,而且要使到达朱丽叶的转弯次数为最少。每改变一次前进方向算作转弯一次。请设计一个算法帮助罗密欧找出这样一条道路。

解题思路:

从入口出发,顺某一方向向前探索,若能走通,则继续往前走,并置走过的房间的F为True;否则沿原路退回F置为False,换一个方向再继续探索,直至所有房间经过完,并且到达朱丽叶所在房间。为保证在任何位置上都能沿原路退回,需要用栈保存经过路径。假设“当前位置”指的是“在搜索过程中的某一时刻所在图中某个方块位置”,则求迷宫中一条路径的算法的基本思想是:若当前位置“可通”,则纳入“当前路径”,并继续朝“下一位置”探索,否则根据探索过的房间的标志位以及探索完此房间后对其他的影响来选择下一步探索的方向是当前房间的八个方向的哪个。

具体代码:

#include <iostream>
#include <stdlib.h>
using namespace std;
 
typedef struct {
       intx, y;
}Point;           //表示一迷宫坐标
 
 
void FindWay(int *path,int n,int m,Pointstart,Point end) {
       while(start.x!=end.x|| start.y!=end.y) {
              cout<<"("<<start.x<<','<<start.y<<")"<<'';
              switch(path[start.x*(m+1)+start.y]) {
                     case0:     start.x--; break;
                     case1:     start.x--; start.y++; break;
                     case2:     start.y++; break;
                     case3: start.x++; start.y++; break;
                     case4: start.x++; break;
                     case5:     start.x--; start.y--; break;
                     case6: start.y--; break;
                     case7:     start.y--; break;
                     default:cout<<"??\n"; return ;
              }
       }
       cout<<"("<<end.x<<','<<end.y<<")";
       return;
}
 
void showAll(int *path,int n,int m)
{
       for(inti=1;i<=n;++i) {
              for(int j=1;j<=m;j++) {
                     switch(path[i*(m+1)+j]){
                            case 0:     cout<<"上\t"; break;
                            case 1:     cout<<"右上\t"; break;
                            case 2:     cout<<"右\t"; break;
                            case 3: cout<<"右下\t"; break;
                            case 4: cout<<"下\t"; break;
                            case 5:     cout<<"左下\t"; break;
                            case 6: cout<<"左\t"; break;
                            case 7:     cout<<"左上\t"; break;
                            default :cout<<"无\t";break;
                     }
              }
              cout<<endl;
       }
      
      
}
 
 
class TravelMaze {
 
private:
       Pointstart,end;
       //Pointcurrent_point;
      
       intbest_num_turn;    //最少转向次数
       intnum_traved;                    //记录经过多少房间
       intcurr_num_turn;        //当前转向次数
       int*TX ;
       int*TY ;
       intn,m;
       intb;                                  //不可进房间数
       bool*maze;//迷宫
      
       int*curr_path;                     //当前解该点下一步走向 0-7 顺时针
       int*best_path;                     //最优解的该点下一步走向 0-7 顺时针
      
private:
       boolIsOK(Point current_point)
       {
              //是否到达并且最少转向最优
              if(num_traved==n*m-b-1 &&
                     current_point.x==end.x&& current_point.y==end.y)
                     if(curr_num_turn<best_num_turn)
                                   returntrue;
              return false;
       }
      
      
       voidBackTrack(Point current_point,int dir)
       {
              if(IsOK(current_point)) {
                     for(inti=1; i<=n; ++i)
                            for(int j=1; j<=m; ++j) {
                                   best_path[i*(m+1)+j]=curr_path[i*(m+1)+j];
                                   best_num_turn=curr_num_turn;
                            }
                     //showAll(best_path,n,m);
                     //FindWay(best_path,n,m,start,end);
                     //return;
              }
                           
             
              Point tmp;
              for(int i=0; i<=7; ++i)   {
                     curr_path[current_point.x*(m+1)+current_point.y]= i;
                     tmp.x= current_point.x +TX[i];
                     tmp.y= current_point.y +TY[i];
                     //是否在迷宫内
                     if(tmp.x<=0||tmp.y<=0||tmp.x>n||tmp.y>m)
                            continue;
                     //是否已经经过
                     if(!maze[tmp.x*(m+1)+tmp.y]){
                            maze[tmp.x*(m+1)+tmp.y] = true;
                            //if(current_point.x==1&¤t_point.y==3&&num_traved==3)
                            //     cout<<"a:"<<i<<endl;
                            if(i!=dir) ++curr_num_turn; //是否转向
                            ++num_traved;
                            BackTrack(tmp,i);
                            --num_traved;
                            if(i!=dir) --curr_num_turn;
                            curr_path[current_point.x*(m+1)+current_point.y]= -1;
                            maze[tmp.x*(m+1)+tmp.y] = false;
                     }
              }
      }    
      
public:
       intSolve(int n_,int m_,Point bb[],int b_,Point start_,Point end_,int *path)
       {
              n = n_;                         //n行
              m = m_;                       //m 列
              b = b_;                         //不可进房间数
              start = start_;         //开式位置
              end = end_;                  //结束位置
              best_num_turn = n*m+1;          //最少转向次数
           num_traved = 1;                          //记录经过多少房间
              curr_num_turn = 0;
              maze = new bool [(n+1)*(m+1)];
              for(int i=1;i<=n;i++)
                     for(intj=1;j<=m;++j)
                            maze[i*(m+1)+j] = false;
              curr_path = new int [(n+1)*(m+1)];
              best_path = path;
             
              TX = new int[8];
              TY = new int[8];
              int tx[] = {-1,-1,0,1,1,1,0,-1};
              int ty[] = {0,1,1,1,0,-1,-1,-1};
              for(int i=0;i<=7;++i) {
                     TX[i]=tx[i];
                     TY[i]=ty[i];
              }
              for(int i=0;i<b;++i)maze[bb[i].x*(m+1)+bb[i].y] = true;
              maze[start.x*(m+1)+start.y] = true;
              BackTrack(start_,-1);
             
              delete[] TX;
              delete[] TY;
              delete[] maze;
              delete[] curr_path;
              return best_num_turn;
       }
};
 
 
 
int main()
{
      
       intn = 3,m=3;
       Pointa[4];
       a[0].x= 1;a[0].y = 2;
       a[1].x= 3;a[1].y = 7;
       a[2].x= 5;a[2].y = 5;
       a[3].x= 8;a[3].y = 2;
       intpath[(n+1)*(m+1)];
       Pointstart,end;
       start.x= start.y = 1;
       end.x= end.y = 3;
      
       TravelMazetm;
       intleast=tm.Solve(n,m,a,0,start,end,path) ;
       cout<<"最少转向"<<least<<endl<<"Path\n";
      
       //FindWay(path,n,m,start,end);
       showAll(path,n,m);
       cin.get();
       return 0;
}

问题三

题干:

石油传输网络通常可表示为一个非循环带权的有向图G.G中有一个称为源的顶点s,石油从顶点输送到G中其他顶点,图G中每条边的权表示该边连接的2个顶点间的距离,网络中的油压随距离的增大而减小,为保证整个输油网络的正常工作,需要维持网络的最低油压Pmin,为此需要在网络的某处或全部顶点处设置增压器,在设置增压器的顶点处油压可以升至Pmax,油压从Pmax减到Pmin可使石油传输的距离至少为d,试设计一个算法,计算网络中增压器的最优设置方案,使得用最少增压器保证石油运输的畅通。

解题思路:

使用回溯法,每个节点单独作为树的一层,左叉为1表示设置增压器,右叉为0表示不设置增压器。每当遍历到叶子节点时判断能否保证石油运输的畅通。

具体代码:

#include<iostream>
#include<vector>
#include<queue>
using namespace std;
int n;
int mx;
struct node
{
	int x; int y;
};
vector<node> que[100];
bool set[100];
void dfs(int x, int k);
void traceback(int x)//运用二叉树结构,深度优先遍历
{
	if (x == n)//如果遍历到叶子结点
	{
		dfs(1,mx);
	}
	else
	{
		set[x] = 1;//给当前点设置增压器
		traceback(x + 1);
		if (x != 1)//第一个结点相当于固定有设置增压器的
		{
			set[x] = 0;//不设置增压器
			traceback(x + 1);
		}
	}
}
int num = 0;
int ss = 0;
int ans = n;
bool answer[100];
void dfs(int x,int k)//遍历所有路径,看是否通畅
{
	num++;
	if (num == n)//遍历了所有结点即表示通畅
	{
		if (ss < ans)//如果当前增压器数量比之前的少
		{
			ans = ss;
			for (int i = 1; i <= n; i++)//记录当前答案
			{
				answer[i] = set[i];
			}
		}
	}
	int m = que[x].size();
	for (int i = 0; i < m; i++)//遍历当前结点的所有子节点
	{
		if (set[x] == 1)//如果当前结点有增压器
		{
			ss++;//增压器计数
			if(mx-que[x][i].y>=0)//如果可以到达下一个结点
			dfs(que[x][i].x, mx - que[x][i].y);
		}
		else//如果当前结点没有增压器
		{
			if(k - que[x][i].y>=0)//如果可以到达下一个结点
			dfs(que[x][i].x, k - que[x][i].y);
		}
	}
}
int main()
{
	
	cin >> n>>k;
	for (int i = 1; i <= n; i++)
	{
		int m;
		cin >> m;
		for (int j = 1; j <= m; j++)
		{
			int a, b;
			cin >> a >> b;
			if (a > i)
			{
				que[i].push_back({ a,b });
			}
		}
	}
	cout << ans - 1 << endl;
	for (int i = 1; i <= n; i++)
	{
		cout << answer[i] << endl;
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
题目C要求我们在一个网格图中求一条从起点到终点的路径,其中有障碍物阻挡了一些格子。这可以用回溯法进行求解。 我们可以用一个二维数组来表示网格图,其中0表示可以通过的格子,1表示障碍物阻挡的格子,2表示路径上的格子。我们可以从起点开始,不断尝试向右、向下、向左、向上四个方向前进,如果前进的下一个格子是可以通过的,则继续前进;如果下一个格子是障碍物或者已经走过,则回溯到上一个格子重新选择方向前进。当我们到达终点时,我们就找到了一条从起点到终点的路径。 这个过程可以用递归函数来实现。具体实现过程如下: 1. 定义一个递归函数`dfs(x, y)`,表示从坐标(x, y)开始寻找路径。 2. 检查当前坐标是否越界。如果越界,则返回false。 3. 检查当前坐标是否为终点。如果是,则返回true。 4. 检查当前坐标是否为障碍物或者已经走过。如果是,则返回false。 5. 将当前坐标标记为已经走过。 6. 尝试向右、向下、向左、向上四个方向前进。如果有一个方向前进成功,则返回true。 7. 如果四个方向都不能前进,则将当前坐标标记为未走过,并返回false。 8. 在主函数中,从起点开始调用递归函数`dfs(0, 0)`,如果返回true,则表示找到了一条路径,可以输出路径;否则表示不存在路径。 需要注意的是,在标记一个坐标为已经走过时,我们需要使用一个变量来记录是否已经走过,因为在回溯的过程中,我们需要将已经走过的坐标重新标记为未走过。 以上就是题目C的建模过程,可以根据这个思路进行编程实现。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值