常用算法之回溯法

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

1. 简单概述

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

基本思想类同于:

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

      【

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

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

                                二叉树的层序遍历

      】

2. 详细描述

        详细的描述则为:

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

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

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

3. 回溯法应用

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

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

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

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

1. 递归

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


 
 
  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. 递推

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


 
 
  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背包问题,从所给重量、价值不同的物品中挑选几个物品放入背包,使得在满足背包不超重的情况下,背包内物品价值最大。它的解空间就是一个典型的子集树。

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


 
 
  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. }

2. 排列树

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


 
 
  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. }

四. 经典问题                                    

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

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,表示此位置可以摆放一个皇后。


        代码:


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

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. 当前点推出路径,设置为可走

       代码:


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

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


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


XAML代码为:


 
 
  1. <Window x:Class="MazeAnimation.MainWindow"
  2. xmlns= "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x= "http://schemas.microsoft.com/winfx/2006/xaml"
  4. Title= "迷宫" Height= "496" Width= "673" Loaded= "Window_Loaded">
  5. <Grid>
  6. <Grid.RowDefinitions>
  7. <RowDefinition Height="30"> </RowDefinition>
  8. <RowDefinition Height="*"> </RowDefinition>
  9. <RowDefinition Height="120"> </RowDefinition>
  10. </Grid.RowDefinitions>
  11. <Grid.ColumnDefinitions>
  12. <ColumnDefinition Width="463"> </ColumnDefinition>
  13. <ColumnDefinition Width="*"> </ColumnDefinition>
  14. </Grid.ColumnDefinitions>
  15. <DockPanel Name="dpTips" Grid.Row="0" Grid.ColumnSpan="2" Background="AliceBlue" >
  16. <Label FontSize="16" Foreground="#FFAD1616" HorizontalAlignment="Center">迷宫的动态演示 </Label>
  17. </DockPanel>
  18. <Grid Name="gdMaze" Grid.Row="1" Grid.Column="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
  19. </Grid>
  20. <ScrollViewer Grid.Row="1" Grid.Column="1" Margin="5" HorizontalAlignment="Stretch" HorizontalScrollBarVisibility="Auto">
  21. <TextBox Name="tbLog" Background="Beige"> </TextBox>
  22. </ScrollViewer>
  23. <DockPanel Name="dpSetting" Grid.Row="2" Grid.Column="0" VerticalAlignment="Stretch">
  24. <TabControl Name="tcMazeSetting" Background="#FFE5D9D9" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
  25. <TabItem Header="迷宫设置" Name="tabItemMaze">
  26. <Grid>
  27. <Grid.RowDefinitions>
  28. <RowDefinition Height="*"> </RowDefinition>
  29. <RowDefinition Height="*"> </RowDefinition>
  30. <RowDefinition Height="*"> </RowDefinition>
  31. </Grid.RowDefinitions>
  32. <Grid.ColumnDefinitions>
  33. <ColumnDefinition Width="60"> </ColumnDefinition>
  34. <ColumnDefinition Width="*"> </ColumnDefinition>
  35. </Grid.ColumnDefinitions>
  36. <Label Content="大小:" Name="label1" Grid.Row="0" Grid.Column="0"/>
  37. <Label Content="入口:" Name="label2" Grid.Row="1" Grid.Column="0"/>
  38. <Label Content="出口:" Name="label3" Grid.Row="2" Grid.Column="0"/>
  39. <StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal">
  40. <Label Content="高:"> </Label>
  41. <TextBox Name="tbMazeHeight" HorizontalAlignment="Left" MinWidth="40"> </TextBox>
  42. <Label Content="宽:"> </Label>
  43. <TextBox Name="tbMazeWidth" HorizontalAlignment="Left" MinWidth="40"> </TextBox>
  44. </StackPanel>
  45. <StackPanel Grid.Row="1" Grid.Column="1" Orientation="Horizontal">
  46. <Label Content="X="> </Label>
  47. <TextBox Name="tbEnterX" HorizontalAlignment="Left" MinWidth="40"> </TextBox>
  48. <Label Content="Y="> </Label>
  49. <TextBox Name="tbEnterY" HorizontalAlignment="Left" MinWidth="40"> </TextBox>
  50. </StackPanel>
  51. <StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
  52. <Label Content="X="> </Label>
  53. <TextBox Name="tbExitX" HorizontalAlignment="Left" MinWidth="40"> </TextBox>
  54. <Label Content="Y="> </Label>
  55. <TextBox Name="tbExitY" HorizontalAlignment="Left" MinWidth="40"> </TextBox>
  56. </StackPanel>
  57. </Grid>
  58. </TabItem>
  59. <TabItem Header="演示设置" Name="tabItemDemo">
  60. <StackPanel Orientation="Vertical" HorizontalAlignment="Stretch">
  61. <CheckBox Name="cbAutoRun" Content="自动执行" Margin="10"> </CheckBox>
  62. <StackPanel Orientation="Horizontal">
  63. <Label Content="执行速度:" Margin="10"> </Label>
  64. <TextBox Name="tbAutoRunSpeed" MinWidth="50" Margin="10"> </TextBox>
  65. <Label Content="毫秒" Margin="0,10,0,10"> </Label>
  66. </StackPanel>
  67. </StackPanel>
  68. </TabItem>
  69. </TabControl>
  70. </DockPanel>
  71. <StackPanel Orientation="Horizontal" Grid.Row="2" Grid.Column="1" HorizontalAlignment="Center">
  72. <Button Name="btnStart" Content="自动开始" Height="40" Width="70" Margin="5" Click="btnStart_Click"> </Button>
  73. <Button Name="btnNext" Content="手动下一步" Height="40" Width="70" Margin="5" Click="btnNext_Click"> </Button>
  74. </StackPanel>
  75. </Grid>
  76. </Window>

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


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



参考文献: 《计算机算法设计与分析》(王晓东)


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

作者 :JarvisChu

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值