回溯法详解

 

 

一. 回溯法 – 深度优先搜素                       

 

1. 简单概述

 

       回溯法思路的简单描述是:把问题的解空间转化成了图或者树的结构表示,然后使用深度优先搜索策略进行遍历,遍历的过程中记录和寻找所有可行解或者最优解。

基本思想类同于:

 

  • 图的深度优先搜索
  • 二叉树的后序遍历

 

      【

         分支限界法:广度优先搜索

         思想类同于:图的广度优先遍历

                                二叉树的层序遍历

      】

2. 详细描述

        详细的描述则为:

        回溯法按深度优先策略搜索问题的解空间树。首先从根节点出发搜索解空间树,当算法搜索至解空间树的某一节点时,先利用剪枝函数判断该节点是否可行(即能得到问题的解)。如果不可行,则跳过对该节点为根的子树的搜索,逐层向其祖先节点回溯;否则,进入该子树,继续按深度优先策略搜索。

        回溯法的基本行为是搜索,搜索过程使用剪枝函数来为了避免无效的搜索。剪枝函数包括两类:1. 使用约束函数,剪去不满足约束条件的路径;2.使用限界函数,剪去不能得到最优解的路径。

        问题的关键在于如何定义问题的解空间,转化成树(即解空间树)。解空间树分为两种:子集树和排列树。两种在算法结构和思路上大体相同。

3. 回溯法应用

       当问题是要求满足某种性质(约束条件)的所有解或最优解时,往往使用回溯法。

       它有“通用解题法”之美誉。

二. 回溯法实现 - 递归和递推(迭代)                               

        回溯法的实现方法有两种:递归和递推(也称迭代)。一般来说,一个问题两种方法都可以实现,只是在算法效率和设计复杂度上有区别。
      【类比于图深度遍历的递归实现和非递归(递推)实现】

1. 递归

        思路简单,设计容易,但效率低,其设计范式如下:

 

[cpp] view plain copy

  1. //针对N叉树的递归回溯方法  
  2. void backtrack (int t)  
  3. {  
  4.     if (t>n) output(x); //叶子节点,输出结果,x是可行解  
  5.     else  
  6.        for i = 1 to k//当前节点的所有子节点  
  7.         {  
  8.             x[t]=value(i); //每个子节点的值赋值给x  
  9.             //满足约束条件和限界条件  
  10.           if (constraint(t)&&bound(t))   
  11.                 backtrack(t+1);  //递归下一层  
  12.         }  
  13. }  

 

2. 递推

      算法设计相对复杂,但效率高。

 

 

[cpp] view plain copy

  1. //针对N叉树的迭代回溯方法  
  2. void iterativeBacktrack ()  
  3. {  
  4.     int t=1;  
  5.     while (t>0) {  
  6.         if(ExistSubNode(t)) //当前节点的存在子节点  
  7.         {  
  8.             for i = 1 to k  //遍历当前节点的所有子节点  
  9.             {  
  10.                 x[t]=value(i);//每个子节点的值赋值给x  
  11.                 if (constraint(t)&&bound(t))//满足约束条件和限界条件   
  12.                 {  
  13.                     //solution表示在节点t处得到了一个解  
  14.                     if (solution(t)) output(x);//得到问题的一个可行解,输出  
  15.                     else t++;//没有得到解,继续向下搜索  
  16.                 }  
  17.             }  
  18.         }  
  19.         else //不存在子节点,返回上一层  
  20.         {  
  21.             t--;  
  22.         }  
  23.     }  
  24. }  

 

三. 子集树和排列树                                                        

1. 子集树

       所给的问题是从n个元素的集合S中找出满足某种性质的子集时,相应的解空间成为子集树。
如0-1背包问题,从所给重量、价值不同的物品中挑选几个物品放入背包,使得在满足背包不超重的情况下,背包内物品价值最大。它的解空间就是一个典型的子集树。

 

       回溯法搜索子集树的算法范式如下:

 

[cpp] view plain copy

  1. void backtrack (int t)  
  2. {  
  3.   if (t>n) output(x);  
  4.     else  
  5.       for (int i=0;i<=1;i++) {  
  6.         x[t]=i;  
  7.         if (constraint(t)&&bound(t)) backtrack(t+1);  
  8.       }  
  9. }  
  10.  

 

2. 排列树

      所给的问题是确定n个元素满足某种性质的排列时,相应的解空间就是排列树。
如旅行售货员问题,一个售货员把几个城市旅行一遍,要求走的路程最小。它的解就是几个城市的排列,解空间就是排列树。
      回溯法搜索排列树的算法范式如下:

 

 

[cpp] view plain copy

  1. void backtrack (int t)  
  2. {  
  3.   if (t>n) output(x);  
  4.     else  
  5.       for (int i=t;i<=n;i++) {  
  6.         swap(x[t], x[i]);  
  7.         if (constraint(t)&&bound(t)) backtrack(t+1);  
  8.         swap(x[t], x[i]);  
  9.       }  
  10. }   
  11.   

 

四. 经典问题                                    

(1)装载问题
(2)0-1背包问题
(3)旅行售货员问题
(4)八皇后问题
(5)迷宫问题
(6)图的m着色问题

1. 0-1背包问题

        问题:给定n种物品和一背包。物品i的重量是wi,其价值为pi,背包的容量为C。问应如何选择装入背包的物品,使得装入背包中物品的总价值最大?
        分析:问题是n个物品中选择部分物品,可知,问题的解空间是子集树。比如物品数目n=3时,其解空间树如下图,边为1代表选择该物品,边为0代表不选择该物品。使用x[i]表示物品i是否放入背包,x[i]=0表示不放,x[i]=1表示放入。回溯搜索过程,如果来到了叶子节点,表示一条搜索路径结束,如果该路径上存在更优的解,则保存下来。如果不是叶子节点,是中点的节点(如B),就遍历其子节点(D和E),如果子节点满足剪枝条件,就继续回溯搜索子节点。

 

回溯法详解_第1张图片

 

代码:

[cpp] view plain copy

  1. #include   
  2.    
  3. #define N 3         //物品的数量  
  4. #define C 16        //背包的容量  
  5.    
  6. int w[N]={10,8,5};  //每个物品的重量  
  7. int v[N]={5,4,1};   //每个物品的价值  
  8. int x[N]={0,0,0};   //x[i]=1代表物品i放入背包,0代表不放入  
  9.    
  10. int CurWeight = 0;  //当前放入背包的物品总重量  
  11. int CurValue = 0;   //当前放入背包的物品总价值  
  12.    
  13. int BestValue = 0;  //最优值;当前的最大价值,初始化为0  
  14. int BestX[N];       //最优解;BestX[i]=1代表物品i放入背包,0代表不放入  
  15.    
  16. //t = 0 to N-1  
  17. void backtrack(int t)  
  18. {  
  19.     //叶子节点,输出结果  
  20.     if(t>N-1)   
  21.     {  
  22.         //如果找到了一个更优的解  
  23.         if(CurValue>BestValue)  
  24.         {  
  25.             //保存更优的值和解  
  26.             BestValue = CurValue;  
  27.             for(int i=0;i
  28.         }  
  29.     }  
  30.     else  
  31.     {  
  32.         //遍历当前节点的子节点:0 不放入背包,1放入背包  
  33.         for(int i=0;i<=1;++i)  
  34.         {  
  35.             x[t]=i;  
  36.    
  37.             if(i==0) //不放入背包  
  38.             {  
  39.                 backtrack(t+1);  
  40.             }  
  41.             else //放入背包  
  42.             {  
  43.                  //约束条件:放的下  
  44.                 if((CurWeight+w[t])<=C)  
  45.                 {  
  46.                     CurWeight += w[t];  
  47.                     CurValue += v[t];  
  48.                     backtrack(t+1);  
  49.                     CurWeight -= w[t];  
  50.                     CurValue -= v[t];  
  51.                 }  
  52.             }  
  53.         }  
  54.         //PS:上述代码为了更符合递归回溯的范式,并不够简洁  
  55.     }  
  56. }  
  57.    
  58. int main(int argc, char* argv[])  
  59. {  
  60.     backtrack(0);  
  61.    
  62.     printf("最优值:%d\n",BestValue);  
  63.    
  64.     for(int i=0;i
  65.     {  
  66.        printf("最优解:%-3d",BestX[i]);  
  67.     }  
  68.     return 0;  
  69. }  

 

 

2. 旅行售货员问题

      回溯法----旅行售货员问题

 

 

3. 详细描述N皇后问题

       问题:在n×n格的棋盘上放置彼此不受攻击的n个皇后。按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。

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

      分析:从n×n个格子中选择n个格子摆放皇后。可见解空间树为子集树。

      使用Board[N][N]来表示棋盘,Board[i][j]=0 表示(I,j)位置为空,Board[i][j]=1 表示(I,j)位置摆放有一个皇后。

      全局变量way表示总共的摆放方法数目。

      使用Queen(t)来摆放第t个皇后。Queen(t) 函数符合子集树时的递归回溯范式。当t>N时,说明所有皇后都已经摆   放完成,这是一个可行的摆放方法,输出结果;否则,遍历棋盘,找皇后t所有可行的摆放位置,Feasible(i,j) 判断皇后t能否摆放在位置(i,j)处,如果可以摆放则继续递归摆放皇后t+1,如果不能摆放,则判断下一个位置。

       Feasible(row,col)函数首先判断位置(row,col)是否合法,继而判断(row,col)处是否已有皇后,有则冲突,返回0,无则继续判断行、列、斜方向是否冲突。斜方向分为左上角、左下角、右上角、右下角四个方向,每次从(row,col)向四个方向延伸一个格子,判断是否冲突。如果所有方向都没有冲突,则返回1,表示此位置可以摆放一个皇后。

回溯法详解_第2张图片

        代码:

 

[cpp] view plain copy

  1. /************************************************************************  
  2.  * 名  称:NQueen.cpp 
  3.  * 功  能:回溯算法实例:N皇后问题  
  4.  * 作  者:JarvisChu  
  5.  * 时  间:2013-11-13  
  6.  ************************************************************************/   
  7.    
  8. #include   
  9.    
  10. #define N 8  
  11.    
  12. int Board[N][N];//棋盘 0表示空白 1表示有皇后  
  13. int way;//摆放的方法数  
  14.    
  15.    
  16. //判断能否在(x,y)的位置摆放一个皇后;0不可以,1可以  
  17. int Feasible(int row,int col)  
  18. {  
  19.     //位置不合法  
  20.     if(row>N || row<0 || col >N || col<0)  
  21.         return 0;  
  22.    
  23.     //该位置已经有皇后了,不能  
  24.     if(Board[row][col] != 0)  
  25.     {   //在行列冲突判断中也包含了该判断,单独提出来为了提高效率  
  26.         return 0;  
  27.     }  
  28.    
  29.     //////////////////////////////////////////////////  
  30.     //下面判断是否和已有的冲突  
  31.    
  32.     //行和列是否冲突  
  33.     for(int i=0;i
  34.     {  
  35.         if(Board[row][i] != 0 || Board[i][col]!=0)  
  36.             return 0;  
  37.     }  
  38.    
  39.     //斜线方向冲突  
  40.    
  41.     for(int i=1;i
  42.     {  
  43. /* i表示从当前点(row,col)向四个斜方向扩展的长度 
  44.   
  45. 左上角 \  / 右上角   i=2 
  46.         \/           i=1 
  47.         /\           i=1 
  48. 左下角 /  \ 右下角   i=2 
  49. */  
  50.         //左上角  
  51.         if((row-i)>=0 && (col-i)>=0)    //位置合法  
  52.         {  
  53.             if(Board[row-i][col-i] != 0)//此处已有皇后,冲突  
  54.                 return 0;  
  55.         }  
  56.    
  57.         //左下角  
  58.         if((row+i)=0)  
  59.         {  
  60.             if(Board[row+i][col-i] != 0)  
  61.                 return 0;  
  62.         }  
  63.    
  64.         //右上角  
  65.         if((row-i)>=0 && (col+i)
  66.         {  
  67.             if(Board[row-i][col+i] != 0)  
  68.                 return 0;  
  69.         }  
  70.    
  71.         //右下角  
  72.         if((row+i)
  73.         {  
  74.             if(Board[row+i][col+i] != 0)  
  75.                 return 0;  
  76.         }  
  77.     }  
  78.    
  79.     return 1; //不会发生冲突,返回1  
  80. }  
  81.    
  82.    
  83. //摆放第t个皇后 ;从1开始  
  84. void Queen(int t)  
  85. {  
  86.     //摆放完成,输出结果  
  87.     if(t>N)  
  88.     {  
  89.         way++;  
  90.         /*如果N较大,输出结果会很慢;N较小时,可以用下面代码输出结果 
  91.         for(int i=0;i
  92.             for(int j=0;j
  93.                 printf("%-3d",Board[i][j]); 
  94.             printf("\n"); 
  95.         } 
  96.         printf("\n------------------------\n\n"); 
  97.         */  
  98.     }  
  99.     else  
  100.     {  
  101.         for(int i=0;i
  102.         {  
  103.             for(int j=0;j
  104.             {  
  105.                 //(i,j)位置可以摆放皇后,不冲突  
  106.                 if(Feasible(i,j))  
  107.                 {  
  108.                     Board[i][j] = 1;  //摆放皇后t  
  109.                     Queen(t+1);       //递归摆放皇后t+1  
  110.                     Board[i][j] = 0;  //恢复  
  111.                 }  
  112.             }  
  113.         }  
  114.     }  
  115. }  
  116.    
  117. //返回num的阶乘,num!  
  118. int factorial(int num)  
  119. {  
  120.     if(num==0 || num==1)  
  121.         return 1;  
  122.     return num*factorial(num-1);  
  123. }  
  124.    
  125.    
  126. int main(int argc, char* argv[])  
  127. {  
  128.     //初始化  
  129.     for(int i=0;i
  130.     {  
  131.         for(int j=0;j
  132.         {  
  133.             Board[i][j]=0;  
  134.         }  
  135.     }  
  136.    
  137.     way = 0;  
  138.    
  139.     Queen(1);  //从第1个皇后开始摆放  
  140.    
  141.     //如果每个皇后都不同  
  142.     printf("考虑每个皇后都不同,摆放方法:%d\n",way);//N=8时, way=3709440 种  
  143.    
  144.     //如果每个皇后都一样,那么需要除以 N!出去重复的答案(因为相同,则每个皇后可任意调换位置)  
  145.     printf("考虑每个皇后都不同,摆放方法:%d\n",way/factorial(N));//N=8时, way=3709440/8! = 92种  
  146.    
  147.     return 0;  
  148. }  


PS:该问题还有更优的解法。充分利用问题隐藏的约束条件:每个皇后必然在不同的行(列),每个行(列)必然也只有一个皇后。这样我们就可以把N个皇后放到N个行中,使用Pos[i]表示皇后i在i行中的位置(也就是列号)(i = 0 to N-1)。这样代码会大大的简洁,因为节点的子节点数目会减少,判断冲突也更简单。

 

4. 迷宫问题

        问题:给定一个迷宫,找到从入口到出口的所有可行路径,并给出其中最短的路径

        分析:用二维数组来表示迷宫,则走迷宫问题用回溯法解决的的思想类似于图的深度遍历。从入口开始,选择下一个可以走的位置,如果位置可走,则继续往前,如果位置不可走,则返回上一个位置,重新选择另一个位置作为下一步位置。

        N表示迷宫的大小,使用Maze[N][N]表示迷宫,值为0表示通道(可走),值为1表示不可走(墙或者已走过);

        Point结构体用来记录路径中每一步的坐标(x,y)

       (ENTER_X,ENTER_Y) 是迷宫入口的坐标

       (EXIT_X, EXIT _Y)    是迷宫出口的坐标

       Path容器用来存放一条从入口到出口的通路路径

       BestPath用来存放所有路径中最短的那条路径

 

       Maze()函数用来递归走迷宫,具体步骤为:

       1. 首先将当前点加入路径,并设置为已走
       2. 判断当前点是否为出口,是则输出路径,保存结果;跳转到4
       3. 依次判断当前点的上、下、左、右四个点是否可走,如果可走则递归走该点
       4. 当前点推出路径,设置为可走

       代码:

 

[cpp] view plain copy

  1. /************************************************************************  
  2.  * 名  称:Maze.cpp 
  3.  * 功  能:回溯算法实例:迷宫问题 
  4.  * 作  者:JarvisChu  
  5.  * 时  间:2013-11-13  
  6.  ************************************************************************/   
  7. #include   
  8. #include   
  9.    
  10. using namespace std;  
  11.    
  12. typedef struct  
  13. {  
  14.     int x;  
  15.     int y;  
  16. }Point;  
  17.    
  18. #define N 10         //迷宫的大小  
  19. #define ENTER_X 0    //入口的位置(0,0)  
  20. #define ENTER_Y 0  
  21. #define EXIT_X N-1   //出口的位置(N-1,N-1)  
  22. #define EXIT_Y N-1   
  23.    
  24.    
  25. int Maze[N][N];      //定义一个迷宫,0表示通道,1表示不可走(墙或已走)  
  26. int paths;       //路径条数  
  27. vector Path;    //保存一条可通的路径  
  28. vector BestPath;    //保存最短的路径  
  29.    
  30. bool First = true;   //标志,找到第一条路径  
  31.    
  32. //初始化迷宫  
  33. void InitMaze()  
  34. {  
  35.  //简单起见,本题定义一个固定大小10*10的迷宫  
  36.  //定义一个迷宫,0表示通道,1表示墙(或不可走)  
  37.     int mz[10][10]={  
  38.     {0,0,1,1,1,1,1,1,1,1}, //0  
  39.     {1,0,0,1,1,0,0,1,0,1}, //1  
  40.     {1,0,0,1,0,0,0,1,0,1}, //2  
  41.     {1,0,0,0,0,1,1,0,0,1}, //3  
  42.     {1,0,1,1,1,0,0,0,0,1}, //4  
  43.     {1,0,0,0,1,0,0,0,0,1}, //5  
  44.     {1,0,1,0,0,0,1,0,0,1}, //6  
  45.     {1,0,1,1,1,0,1,1,0,1}, //7  
  46.     {1,1,0,0,0,0,0,0,0,0}, //8  
  47.     {1,1,1,1,1,1,1,1,1,0}  //9  
  48.     //   0 1 2 3 4 5 6 7 8 9  
  49.     };   
  50.    
  51.     //复制到迷宫  
  52.     memcpy(Maze,mz,sizeof(mz));  
  53.    
  54.     paths = 0;  
  55. }  
  56.    
  57. //从(x,y)位置开始走;初始为(0,0)  
  58. void MazeTrack(int x,int y)  
  59. {  
  60.     ///////////////////////////////////////  
  61.     //当前点加入到路径  
  62.     Point p={x,y};  
  63.     Path.push_back(p);  
  64.     Maze[x][y] = 1;         //设置为已走,不可走  
  65.    
  66.     //cout<<"来到("<
  67.    
  68.     ///////////////////////////////////////  
  69.     //如果该位置是出口,输出结果  
  70.     if(x == EXIT_X && y== EXIT_Y)  
  71.     {  
  72.         cout<<"找到一条道路"<
  73.         paths++;  
  74.           
  75.         //输出路径  
  76.         vector::iterator it;  
  77.         for(it=Path.begin();it!=Path.end();++it)  
  78.         {  
  79.             cout<<"("<x<<","<y<<") ";  
  80.         }  
  81.         cout<
  82.    
  83.         //判断是否更优  
  84.         if(First)//如果是找到的第一条路径,直接复制到最优路径  
  85.         {  
  86.             for(it=Path.begin();it!=Path.end();++it)  
  87.             {  
  88.                 BestPath.push_back(*it);  
  89.             }  
  90.             First = false;  
  91.         }  
  92.         else //不是第一条,则判断是否更短  
  93.         {  
  94.             //更短,复制到最优路径  
  95.             if(Path.size()
  96.             {  
  97.                 BestPath.clear();  
  98.                 for(it=Path.begin();it!=Path.end();++it)  
  99.                 {  
  100.                     BestPath.push_back(*it);  
  101.                 }  
  102.             }  
  103.         }  
  104.     }  
  105.    
  106.     ///////////////////////////////////////  
  107.     //判断(x,y)位置的上、下、左、右是否可走  
  108.    
  109.     if((x-1)>=0 && Maze[x-1][y]==0)//上(x-1,y);存在且可走  
  110.     {  
  111.         MazeTrack(x-1,y);  
  112.     }  
  113.    
  114.     if((x+1)
  115.     {  
  116.         MazeTrack(x+1,y);  
  117.     }  
  118.    
  119.     if((y-1)>=0 && Maze[x][y-1]==0)//左(x,y-1);存在且可走  
  120.     {  
  121.         MazeTrack(x,y-1);  
  122.     }  
  123.    
  124.     if((y+1)
  125.     {  
  126.         MazeTrack(x,y+1);  
  127.     }  
  128.    
  129.     ///////////////////////////////////////  
  130.     //返回上一步  
  131.     Path.pop_back();  
  132.     Maze[x][y] = 0;         //设置为未走  
  133. }  
  134.    
  135.    
  136. int main(int argc, char* argv[])  
  137. {  
  138.     //初始化迷宫  
  139.     InitMaze();  
  140.           
  141. /*  //显示迷宫 
  142.     for(int i=0;i
  143.         for(int j=0;j
  144.             cout<
  145.         cout<
  146.     }*/  
  147.    
  148.     //回溯法走迷宫  
  149.     MazeTrack(ENTER_X,ENTER_Y);  
  150.    
  151.     //显示最优的路径  
  152.     cout<<"可行路径总条数为"<
  153.     vector::iterator it;  
  154.     for(it=BestPath.begin();it!=BestPath.end();++it)  
  155.     {  
  156.         cout<<"("<x<<","<y<<") ";  
  157.     }  
  158.     cout<
  159.     return 0;  
  160. }  

 

PS:用WPF实现了一个简单的图形化迷宫程序。白色表示通道,红色表示墙,最短的路径用黄色显示。目前实现了一个10*10的迷宫自动搜素最短通路,右侧显示搜索过程中得到的每一个可行通路。
由于构造一个迷宫比较复杂,所以暂时“迷宫设置”功能没有做实现,至于手动一步步查看搜素过程的动画也没有做实现。

回溯法详解_第3张图片回溯法详解_第4张图片

实现的大致思路如下:迷宫的数据使用二维数据mazeData表示。迷宫的显示使用Grid控件表示,每个方格处添加一个Rectangle控件,如果该方格mazeData值为0,则填充白色值为1,则填充红色,值为2则填充黄色。


XAML代码为:

 

[html] view plain copy

  1.         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
  2.         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
  3.         Title="迷宫" Height="496" Width="673" Loaded="Window_Loaded">  
  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.                           
  34.                               
  35.                               
  36.                               
  37.                           
  38.                           
  39.                               
  40.                               
  41.                           
  42.                           
  43.                           
  44.                           
  45.                           
  46.                                                                      
  47.                           
  48.                               
  49.                               
  50.                               
  51.                               
  52.                           
  53.   
  54.                           
  55.                               
  56.                               
  57.                               
  58.                               
  59.                           
  60.   
  61.                           
  62.                               
  63.                               
  64.                               
  65.                               
  66.                           
  67.                       
  68.                       
  69.                   
  70.                   
  71.                       
  72.                           
  73.                           
  74.                               
  75.                               
  76.                               
  77.                           
  78.                       
  79.                   
  80.               
  81.           
  82.           
  83.           
  84.               
  85.               
  86.           
  87.       
  88.   

 

对应的MainWindow.xaml.cs代码为:

 

[csharp] view plain copy

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5. using System.Windows;  
  6. using System.Windows.Controls;  
  7. using System.Windows.Data;  
  8. using System.Windows.Documents;  
  9. using System.Windows.Input;  
  10. using System.Windows.Media;  
  11. using System.Windows.Media.Imaging;  
  12. using System.Windows.Navigation;  
  13. using System.Windows.Shapes;  
  14.   
  15. namespace MazeAnimation  
  16. {  
  17.   
  18.   
  19.   
  20.     ///   
  21.     /// Interaction logic for MainWindow.xaml  
  22.     ///   
  23.     public partial class MainWindow : Window  
  24.     {  
  25.         public struct Point  
  26.         {  
  27.             public int x;  
  28.             public int y;  
  29.             public Point(int a, int b) { x = a; y = b; }  
  30.         };  
  31.   
  32.         public bool bAutoRun = true;  
  33.         public int mazeHeight = 10;  
  34.         public int mazeWidth = 10;  
  35.   
  36.         int[,] mazeData = new int[10, 10]  
  37.             {  
  38.                 {0,0,1,1,1,1,1,1,1,1}, //0  
  39.                 {1,0,0,1,1,0,0,1,0,1}, //1  
  40.                 {1,0,0,1,0,0,0,1,0,1}, //2  
  41.                 {1,0,0,0,0,1,1,0,0,1}, //3  
  42.                 {1,0,1,1,1,0,0,0,0,1}, //4  
  43.                 {1,0,0,0,1,0,0,0,0,1}, //5  
  44.                 {1,0,1,0,0,0,1,0,0,1}, //6  
  45.                 {1,0,1,1,1,0,1,1,0,1}, //7  
  46.                 {1,1,0,0,0,0,0,0,0,0}, //8  
  47.                 {1,1,1,1,1,1,1,1,1,0}  //9  
  48.             //   0 1 2 3 4 5 6 7 8 9  
  49.             };  
  50.   
  51.         public int enterX = 0;  
  52.         public int enterY = 0;  
  53.         public int exitX = 9;  
  54.         public int exitY = 9;  
  55.         public int runSpeed = 100;  
  56.   
  57.         public int paths = 0; //总条数  
  58.         public Stack path = new Stack(); //一条找到的路径  
  59.         public Stack bestPath = new Stack();//最优路径  
  60.         public bool bFrist = true;  
  61.   
  62.   
  63.         public MainWindow()  
  64.         {  
  65.             InitializeComponent();  
  66.         }  
  67.   
  68.         //显示迷宫,白色0表示通道,红色1表示不可走,黄色2表示最优的路径,绿色3表示已经走过的路径  
  69.         private void DisplayMaze()  
  70.         {  
  71.             gdMaze.Children.Clear();  
  72.             //设置可走和不可走  
  73.             for (int i = 0; i < mazeHeight; i++)  
  74.             {  
  75.                 for (int j = 0; j < mazeWidth; j++)  
  76.                 {  
  77.                     Rectangle rect = new Rectangle();  
  78.                     rect.SetValue(Grid.RowProperty, i);  
  79.                     rect.SetValue(Grid.ColumnProperty, j);  
  80.   
  81.                     if (mazeData[i, j] == 0)  
  82.                     {  
  83.                         rect.Fill = Brushes.White;  
  84.                     }  
  85.                     else if (mazeData[i, j] == 1)  
  86.                     {  
  87.                         rect.Fill = Brushes.Red;  
  88.                     }  
  89.                     else if (mazeData[i, j] == 2)  
  90.                     {  
  91.                         rect.Fill = Brushes.Yellow;  
  92.                     }  
  93.                     else if (mazeData[i, j] == 3)  
  94.                     {  
  95.                         rect.Fill = Brushes.Blue;  
  96.                     }  
  97.                     gdMaze.Children.Add(rect);  
  98.                 }  
  99.             }  
  100.         }  
  101.   
  102.         //初始化迷宫  
  103.         private void InitMaze()  
  104.         {  
  105.   
  106.             gdMaze.Background = Brushes.LightGray;  
  107.             gdMaze.ShowGridLines = true;  
  108.   
  109.             for (int i = 0; i < mazeHeight; i++)  
  110.             {  
  111.                 gdMaze.RowDefinitions.Add(new RowDefinition());  
  112.             }  
  113.   
  114.             for (int i = 0; i < mazeWidth; i++)  
  115.             {  
  116.                 gdMaze.ColumnDefinitions.Add(new ColumnDefinition());  
  117.             }  
  118.   
  119.             DisplayMaze();  
  120.         }  
  121.   
  122.         //从(x,y)位置开始走;初始为(0,0)  
  123.         private void MazeTrack(int x, int y)  
  124.         {  
  125.             ///////////////////////////////////////  
  126.             //当前点加入到路径  
  127.             Point p = new Point(x, y);  
  128.             path.Push(p);  
  129.   
  130.             mazeData[x, y] = 3;         //设置为已走,不可走             
  131.             //DisplayMaze();  
  132.             //System.Threading.Thread.Sleep(runSpeed);//休眠  
  133.   
  134.   
  135.             ///////////////////////////////////////  
  136.             //如果该位置是出口,输出结果  
  137.             if (x == exitX && y == exitY)  
  138.             {  
  139.                 string msg = "找到一条道路(逆序)\n";  
  140.                 tbLog.AppendText(msg);  
  141.   
  142.                 paths++;  
  143.   
  144.                 //输出路径  
  145.                 foreach (Point pnt in path)  
  146.                 {  
  147.                     msg = "(" + pnt.x + "," + pnt.y + ")";  
  148.                     tbLog.AppendText(msg);  
  149.                 }  
  150.                 tbLog.AppendText("\n\n");  
  151.   
  152.                 //判断是否更优  
  153.                 if (bFrist)//如果是找到的第一条路径,直接复制到最优路径  
  154.                 {  
  155.                     foreach (Point pnt in path)  
  156.                     {  
  157.                         bestPath.Push(pnt);  
  158.                     }  
  159.   
  160.                     bFrist = false;  
  161.                 }  
  162.                 else //不是第一条,则判断是否更短  
  163.                 {  
  164.                     //更短,复制到最优路径  
  165.                     if (path.Count < bestPath.Count)  
  166.                     {  
  167.                         bestPath.Clear();  
  168.                         foreach (Point pnt in path)  
  169.                         {  
  170.                             bestPath.Push(pnt);  
  171.                         }  
  172.                     }  
  173.                 }  
  174.             }  
  175.   
  176.             ///////////////////////////////////////  
  177.             //判断(x,y)位置的上、下、左、右是否可走  
  178.   
  179.             if ((x - 1) >= 0 && mazeData[x - 1, y] == 0)//上(x-1,y);存在且可走  
  180.             {  
  181.                 MazeTrack(x - 1, y);  
  182.             }  
  183.   
  184.             if ((x + 1) < mazeHeight && mazeData[x + 1, y] == 0)//下(x+1,y);存在且可走  
  185.             {  
  186.                 MazeTrack(x + 1, y);  
  187.             }  
  188.   
  189.             if ((y - 1) >= 0 && mazeData[x, y - 1] == 0)//左(x,y-1);存在且可走  
  190.             {  
  191.                 MazeTrack(x, y - 1);  
  192.             }  
  193.   
  194.             if ((y + 1) < mazeWidth && mazeData[x, y + 1] == 0)//右(x,y+1);存在且可走  
  195.             {  
  196.                 MazeTrack(x, y + 1);  
  197.             }  
  198.   
  199.             ///////////////////////////////////////  
  200.             //返回上一步  
  201.             path.Pop();  
  202.             mazeData[x, y] = 0;         //设置为未走  
  203.   
  204.             //DisplayMaze();  
  205.             //System.Threading.Thread.Sleep(runSpeed);//休眠  
  206.         }  
  207.   
  208.   
  209.         private void Window_Loaded(object sender, RoutedEventArgs e)  
  210.         {  
  211.             //初始化变量  
  212.             tbMazeHeight.Text = mazeHeight.ToString();  
  213.             tbMazeWidth.Text = mazeWidth.ToString();  
  214.             tbEnterX.Text = enterX.ToString();  
  215.             tbEnterY.Text = enterY.ToString();  
  216.             tbExitX.Text = exitX.ToString();  
  217.             tbExitY.Text = exitY.ToString();  
  218.   
  219.             cbAutoRun.IsChecked = bAutoRun;  
  220.             tbAutoRunSpeed.Text = runSpeed.ToString();  
  221.   
  222.             //初始化迷宫  
  223.             InitMaze();  
  224.         }  
  225.   
  226.         //点击开始  
  227.         private void btnStart_Click(object sender, RoutedEventArgs e)  
  228.         {  
  229.             string msg = "开始走迷宫\n";  
  230.             tbLog.AppendText(msg);  
  231.             MazeTrack(enterX, enterY);  
  232.   
  233.             //显示最优的路径  
  234.             msg = "\n可行路径总条数为" + paths + "\n最优路径为\n";  
  235.             tbLog.AppendText(msg);  
  236.   
  237.             foreach (Point pnt in bestPath)  
  238.             {  
  239.                 msg = "(" + pnt.x + "," + pnt.y + ")";  
  240.                 tbLog.AppendText(msg);  
  241.   
  242.                 mazeData[pnt.x, pnt.y] = 2;  
  243.   
  244.             }  
  245.             DisplayMaze();  
  246.         }  
  247.   
  248.         //下一步  
  249.         private void btnNext_Click(object sender, RoutedEventArgs e)  
  250.         {  
  251.             string msg = "手动开始走迷宫 暂未实现\n";  
  252.             tbLog.AppendText(msg);  
  253.         }  
  254.   
  255.     }  
  256. }  

 

转载本文请注明作者和出处

作者 :JarvisChu

出处:http://blog.csdn.net/jarvischu

你可能感兴趣的:(回溯法详解)