算法篇-9-回溯法-罗密欧与朱丽叶&图的M着色&旅行售货员

本系列所有代码https://github.com/YIWANFENG/Algorithm-github

罗密欧与朱丽叶走迷宫

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

 

算法分析思路以及相关数学公式:

用标志位F[i][j]标志该个房间是否被走过。

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

否则根据探索过的房间的标志位以及探索完此房间后对其他的影响来选择下一步探索的方向是当前房间的八个方向的哪个。

 

 

程序源代码:

#include <iostream>
#include <stdlib.h>
 
//Luomio and Zhuliye 问题 P155
//罗密欧与朱丽叶走迷宫问题
 
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();
       return0;
}

 

 

 

图的M着色

算法思路分析与相关公式:

假设第I个块的着色用X[i]表示(取值为1-m),那么由题目可知,此问题解空间应为一子集树。并且可知,若可以搜索到该树的叶子节点,即找到一个可行解。即终止条件可以是搜索到叶子节点(只需一个可行解),也可以是搜索完整棵树(得到所有可行解)。由题目可知,判断一解是否可行即如果i节点着色z,则z不应和与i相连的块的颜色代号相同。

设(i,j)属于图G 的边集E,若a[i][j] == 1则i与j相连,否则不想连。

限界条件可为:a[i][j]==1&&x[i]!=x[j],j=1…n,(n为块数,图的顶点数)

 

程序源代码:

#include <iostream>
#include <memory.h>
using namespace std;
 
class GraphMColor
{
private:
       intn;       //图的顶点数量
       intm;      //颜色数量
       int*a;     //图的邻接矩阵
      
       int*x;     //当前解
       longsum;       //可行解数量
      
private:
       voidBacktrack(int t);
       boolOK(int k);
public:
       intsolve(int n_,int m_,int *a_);
      
};
 
int GraphMColor::solve(int n_,int m_,int*a_)
{
       //intn_;   //图的顶点数量
       //intm_;  //颜色数量
       //int*a_; //图的邻接矩阵
       n= n_;
       m= m_;
       a= a_;
      
       sum=0;
       x=newint[n+1];
       memset(x,0,sizeof(int)*(n+1));
       Backtrack(1);
       delete[]x;
       returnsum;
}
 
void GraphMColor::Backtrack(int t)
{
       if(t>n){
              ++sum;
              for(int i=1; i<=n; ++i)
                     cout<<x[i]<<'';
              cout<<endl;
       }else {
              for(int i=1; i<=m; ++i) {
                     x[t]= i;
                     if(OK(t))Backtrack(t+1);
                     //x[t]= 0; //防止深度递归造成对OK的影响
              }
       }
}
 
bool GraphMColor::OK(int k)
{
       for(inti=1; i<=k-1; ++i) {
              if(a[k*(n+1)+i]==1 && x[i]==x[k])
                     returnfalse;
       }
       returntrue;
}
 
 
 
int main()
{
       intn= 5;
       intm=4;
       inta[] = {
              0, 0, 0, 0, 0, 0,
              0, 0, 1, 1, 1, 0,
              0, 1, 0, 1, 1, 1,
              0, 1, 1, 0, 1, 0,
              0, 1, 1, 1, 0, 1,
              0, 0, 1, 0, 1, 0
       };
       GraphMColorgc;
       intsum = gc.solve(n,m,a);
      
       cout<<"可行解数量:"<<sum<<endl;
       cin.get();
       return0;
}

 

缺点分析:

在程序中,由于我们的限界条件是for(int i=1; i<=k-1; ++i) {       if(a[k*(n+1)+i]==1&& x[i]==x[k])

       returnfalse;    }     return true;

则在输入图的边集时,应设置第i边与自己不相连。

 

 

旅行售货员问题 

算法思路分析与相关公式:

根据题目,很容易发现这是一个解空间为排列树的问题。共有n的地方

设第i个该去的地方用x[i]表示,则当找到第n-1号该走的地方时,应检测是否存在x[n-1]到x[n]是否有边并且x[n]与x[1]是否有边,都有便说明有一条回路,则判断当前的旅行费用是否比已找到的解还好。

  

程序源代码:

#include <iostream>
 
using namespace std;
template<class T_>
class TSP
{
private:
       intn;              //图的顶点数量
       T_*a;            //图的邻接矩阵
       T_NoEdge;    //无边标记
      
       int*x ;    //当前解
       T_cc;            //当前耗费
       int*bestx;      //最优解、
       T_bestc; //最短路长
      
private:
       voidBacktrack(int i)
       {
              int itemp;
              if(i==n) {
                     //检查当前解是否形成回路
                     if(a[x[n-1]*(n+1)+x[n]]!=NoEdge&& a[x[n]*(n+1)+x[1]]!=NoEdge) {
                            T_ ctemp = cc + a[x[n-1]*(n+1)+x[n]] +a[x[n]*(n+1)+x[1]];
                            //回路长度是否更优
                            if(ctemp<bestc || bestc == NoEdge) {
                                   bestc= ctemp;
                                   for(intj=1; j<=n; ++j) bestx[j] = x[j];
                            }
                     }
              } else {
                     for(intj=i; j<=n; ++j) {
                            //约束条件
                            if(a[x[i-1]*(n+1)+x[j]]!=NoEdge&&
                                   (cc+ a[x[i-1]*(n+1)+x[j]] <bestc || bestc == NoEdge)) {
                                   itemp=x[i]; x[i] = x[j]; x[j] = itemp;
                                   cc+= a[x[i-1]*(n+1)+x[i]];
                                   Backtrack(i+1);
                                   cc-= a[x[i-1]*(n+1)+x[i]];   
                                   itemp=x[i]; x[i] = x[j]; x[j] = itemp;         
                            }
                     }
              }
       }
public:
       T_Solve(int n_,T_ *a_,T_ NoEdge_,int *v)
       {
              // n_;              //图的顶点数量
              // a_;              //图的邻接矩阵
              // NoEdge_;    //无边标记    
              // v (out)  最优解
              //返回最短路长
              n = n_;
              a = a_;
              NoEdge = NoEdge_;
              bestx = v;
             
              cc = 0;
              bestc=NoEdge;
             
              x = new int[n+1];
              for(int i=1;i<=n; ++i) x[i]=i;
              Backtrack(2);
              delete [] x;
              return bestc;          
       }
};
 
 
int main()
{
       intn=4;
       intNoEdge = 0x7fffffff;
       inta[] = {
              0,0,0,0,0,
              0,0,30,6,4,
              0,30,0,5,10,
              0,6,5,0,20,
              0,4,10,20,0
       };
       intv[5];
       TSP<int>tsp;
       intc = tsp.Solve(n,a,NoEdge,v);
       cout<<"最短路长"<<c<<endl;
       cout<<"路线:";
       for(inti=1; i<=n; ++i)
              cout<<' '<<v[i];
       cout<<"1"<<'\n';
       cin.get();
       return0;
}
 

 

缺点分析:

在这里只做了最简单的排列树搜索,并未添加限界条件,可以添加限界如此:

若当前耗费已经超过了以求出的最优解,则以当前结点为根节点的子树全不用搜索,可以直接回溯。

 

    原文作者:回溯法
    原文地址: https://blog.csdn.net/hffhjh111/article/details/53788297
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞