最近研究了马踏棋盘的问题,分享一下心得。
问题描述:将国际象棋的骑士放置任意位置,使其按规则不重复走完所有的棋格。
该问题的实质应该是:哈密顿路径的遍历。
首先画图分析一下具体环境:
马踏棋盘(骑士周游列国)__贪心算法
如图所示,每个棋格有且仅有八个前进方向,标记为“1…8”,每个方向不一定合法(超出棋盘)
这时可以用递归回溯的思想对路径进行探索,思路是:
从方向1开始探索路径,一条路走到黑,发现走不了就回头走其他的路
再具体点的思路就是:

 for i →1 to 8  //依次对八个方向进行探寻
        if travel finish   return  //如果探索完毕返回
             position→ new positon  //获取新的棋格x,为原来棋格的儿子棋格
             if  position legal && new  //如果新棋格x没超出棋盘 并且没有被探索过
                  travel(position)  //对新棋格x进行探索
                  position  reset  //从探索中回到棋格x中
                  travel time reset  //探索次数重置

更具体的可行性代码如下(第一次写,很随意很垃圾):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace 马塔棋盘
{
    class Program
    {
        static int tag = 1;
        static int maxtri = 7;
      
        //static int tempx, tempy;
        static int[,] way = new int[8,8];
        static void Main(string[] args)
        {
            
            //int x = int.Parse(Console.ReadLine());
            //int y = int.Parse(Console.ReadLine());
            way[0,0] = tag;
            travel(0,0);
            //foreach (var item in way)
            //{
            //    Console.Write(item + "\t");
            //}
            Console.ReadKey();
        }
        
        public static void nextStep(int step,ref int x,ref int y)//棋格前进的方向
        {
            switch (step)
            {
                case (0):
                    {
                        x += 1;
                        y += 2;
                        break;
                    }
                case (1):
                    {
                        x += 2;
                        y += 1;
                        break;
                    }
                case (2):
                    {
                        x += 2;
                        y += -1;
                        break;
                    }
                case (3):
                    {
                        x += 1;
                        y += -2;
                        break;
                    }
                case (4):
                    {
                        x += -1;
                        y += -2;
                        break;
                    }
                case (5):
                    {
                        x += -2;
                        y += -1;
                        break;
                    }
                case (6):
                    {
                        x += -2;
                        y += 1;
                        break;
                    }
                case (7):
                    {
                        x += -1;
                        y += 2;
                        break;
                    }
                default:
                    break;
            }
          
        }
        public static void travel(int x, int y)
        {        
            if (tag>=64)//探索路径完毕,进行打印输出
            {
                Console.WriteLine("travel is finished"); 
                int count = 0;
                foreach (var item in way)
                {
                    if (count==8)
                    {
                        count = 0;
                        Console.WriteLine();
                    }
                    Console.Write(item + "\t");
                    count++;
                }
                return;
            }         
                for (int i = 0; i <= maxtri; i++)
                {
                    //tempx = x; tempy = y;
                    int a = x; int b = y;
                    nextStep(i, ref a, ref b);// Console.WriteLine("{0},{1},{2}", i, x, y);
                    if ((a <= maxtri&&a>=0) && (b <= maxtri&&b>=0))//判断新的路径是否合法
                    {
                        //Console.WriteLine("{0},{1},{2}", i, x, y);
                        if ((way[a, b] == 0))
                        {
                            tag++;
                            way[a, b] = tag;//棋格x标记已探索,探索第几步
                            travel(a, b);//对棋格x进行更新一步探索
                            tag--;//对棋格x探索完成后,不管成功还是失败,把探索位重置
                            way[a, b] = 0;//对棋格x探索完成后,不管成功还是失败,把探索位重置
                        }
                    }
                }  
        }    
    }
}

运行结果:
马踏棋盘(骑士周游列国)__贪心算法
马踏棋盘(骑士周游列国)__贪心算法
然后发现这种写法很垃圾,很多问题…
缺点1:跑得慢 ,要跑好几秒才能跑出来
缺点2:某些点很难跑出结果 ,比如顶点(0,0)跑得比较快,(2,2)就跑不出来
缺点3:肯定有,不过阿拉已经对它没兴趣了

仔细分析下,发现这所谓的递归回溯其实就是高级一点的穷举法
穷举法要跑8的64次方种可能出来
递归回溯只是添加了一个策略,“不能跑就不跑了” 判断依据是——棋格是否非法
想要更短的响应时间,应该添加更严格的判断策略–贪心策略

在实际生活中有很多贪心策略的应用,比如:
(1)家里有一堆快过期的大米 ,有一堆 新的大米,为了不浪费大米,先把快过期的大米吃完再吃新的大米,这样就可以吃到所有的大米了
(2)商贩找零钱,先50 再20 10块这样…
阿拉认为贪心策略应该是专注在最恶劣条件或最优越条件中取值。

本题适用最恶劣条件取值。思路:
比较棋格的孙子棋格,数量最少的孙子棋格的方向最先进行探索。
为什么最少的优先,因为最少的通路说明它很容易“死掉”,越到后面越容易找不到。

整体思路在原来的框架下加多一个最优选择方向的策略就可以了

for i →1 to 8  //依次对八个方向进行探寻
        if travel finish   return  //如果探索完毕返回
             position→ new positon  //**依照贪心策略**获取新的棋格x,为原来棋格的儿子棋格
             if  position legal && new  //如果新棋格x没超出棋盘 并且没有被探索过
                  travel(position)  //对新棋格x进行探索
                  position  reset  //从探索中回到棋格x中
                  travel time reset  //探索次数重置

怎么实行这个策略,其实只要添加相应的函数getDirection(),为每个棋格的八个方向进行排序,孙子越少搜索优先级越高
这时需要一些额外的数据存储结构
waycount[x,y] // 记录坐标为x,y的棋格可通路的儿子棋格数目
dir[x,y,i] //记录坐标为x,y的棋格的方向(有8个方向,i取值1~8,i越小,搜索优先级越高

for  i 1→8 
    temp[i] = way[x,y]  //记录坐标为x,y的棋格的儿子棋格数
sort(temp,dir[,,]// 按照结点少优先排序原则进行排序

具体代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace 骑士周游列国__贪心算法
{
    class Program
    {
        static int row = 8;
        static int column = 8;
        static int step = 1;//步调
        static int no = 0; //方案数目
        static int[,] board = new int[row,column];
        static int[,] waycount = new int[row, column];//保存每个棋位联通的子棋位个数
        static int[, ,] direction = new int[row, column, 8];//保存每个棋位最优前进的方向
        static int[,] destination ={{1,2},{2,1},{2,-1},{1,-2},{-1,-2},{-2,-1},{-2,1},{-1,2}};//保存每个棋位的方向
        static void Main(string[] args)
        {
         
            getDirection();
            board[0, 0] = 1;
            travel(0, 0);
            Console.ReadKey();
        }

        public static void travel(int x, int y)//棋子周游列国
        {
            for (int i = 0; i < 8; i++)
            {
                if (step==row*column)
                {
                    print();
                    no++;
                    Console.ReadKey();
                    return;
                }
                int x1 = x + destination[direction[x,y,i], 0];
                int y1 = y + destination[direction[x,y,i], 1]; 
                if (check(x1, y1))
                {
                    if (board[x1,y1] == 0)
                    {
                        board[x1, y1] = ++step;
                        travel(x1, y1);
                        --step;
                        board[x1, y1] = 0;
                    }                 
                }
              
            }
        }//棋子周游列国

        public static void getDirection()//得到每个棋位的方向 ,在调用travel函数之前一定要调用,可以在awake()函数里调用
        {
            getWayCount();//得到每个棋位的通路数,这个可以放在star()函数里面
            int[] count = new int[8];//保存每个棋位的孙棋位个数
            for (int x = 0; x < row; x++)
            {
                for (int y = 0; y < column; y++)
                {
                    for (int i = 0; i < 8; i++)
                    {
                        int x1 = x + destination[i, 0];
                        int y1 = y + destination[i, 1];
                        if (check(x1,y1))
                        {
                            count[i] = waycount[x1, y1];
                        }
                        else
                        {
                            count[i] = 100;
                        }
                    }
                    for (int i = 0; i < 8; i++)
                    {
                        direction[x, y, i] = getMin(count, 0);
                    }
                }
            }
        }

        public static void getWayCount()//得到每个棋位的子通路数 这个函数只能运行一次,每运行多一次,waycount的值翻一倍 目前在getDirection()中被调用
        {
            for (int x = 0; x < row; x++)
            {
                for (int y = 0; y < column; y++)
                {
                    for (int i = 0; i < 8; i++)
                    {
                        int x1 = x + destination[i, 0];
                        int y1 = y + destination[i, 1];
                        if (check(x1,y1))
                        {
                            waycount[x, y]++;
                        }
                    }
                }
            }
        }

        public static int getMin(int[] array, int j)//取得数组最小值
        {
            int min = 999;
            int minNo = j;
            for (int i = j; i < array.Length; i++)
            {
                if (array[i] <= min)
                {
                    min = array[i];
                    minNo = i;
                }
            }
            array[minNo] = 999;
            return minNo;
        }

        public static bool check(int x, int y)//检查棋子是否超出棋盘边界
        {
            if ((x <= row - 1  &&  x >= 0)  &&  (y <= column-1  &&  y >= 0))
                return true;
            return false;
        }

        public static void print()//打印周游列国踪迹
        {
            Console.WriteLine("The travel is finished!");
            Console.WriteLine("Plan {0} is :", no);
            for (int i = 0; i < row; i++)
            {
                for (int j = 0; j < column; j++)
                {
                    Console.Write(board[i, j]+"\t");
                }
                Console.WriteLine();
            }
        }
    }
}

运行结果:
马踏棋盘(骑士周游列国)__贪心算法
马踏棋盘(骑士周游列国)__贪心算法

相关文章: