Python 谜语:方形拼图

Python 谜语:方形拼图,python,puzzle,Python,Puzzle,在过去的几天里,我克制自己不去攻读硕士学位,一直专注于这个(看似简单的)难题: 这是一个10*10的网格,由100个可供选择的地方组成。目标是从一个角开始,根据一些简单的“遍历规则”遍历所有位置,并达到数字100(如果您是程序员,则从0开始,则为99:) 遍历的规则是: 1.两个空间沿纵轴和横轴跳跃 2.沿对角线跳一个空格 3.每个广场你只能参观一次 为了更好地可视化,下面是一个有效的遍历示例(直到第8步): 出于厌倦,我一直在手工制作这个拼图。多年来,我一直试图用手解决这个问题,但我从来

在过去的几天里,我克制自己不去攻读硕士学位,一直专注于这个(看似简单的)难题:


这是一个10*10的网格,由100个可供选择的地方组成。目标是从一个角开始,根据一些简单的“遍历规则”遍历所有位置,并达到数字100(如果您是程序员,则从0开始,则为99:)

遍历的规则是:
1.两个空间沿纵轴和横轴跳跃
2.沿对角线跳一个空格
3.每个广场你只能参观一次

为了更好地可视化,下面是一个有效的遍历示例(直到第8步):


出于厌倦,我一直在手工制作这个拼图。多年来,我一直试图用手解决这个问题,但我从来没有超过96。听起来容易吗?自己试试看:)

因此,为了解决这个问题,我用Python开发了一个简短的(大约100行代码)程序。我是这门语言的初学者,我想看看我能做些什么。
该程序简单地应用了详尽的尝试和错误解决技术。换句话说:暴力深度优先搜索

我的问题由此产生:不幸的是,这个程序无法解决这个问题,因为状态空间太大了,搜索永远不会在没有找到解决方案的情况下结束。它可以毫不费力地升到98号(并打印出来),但这不是一个完整的解决方案。
该程序还打印出到目前为止所覆盖的搜索树的长度。几分钟后,从(比如)第65个元素开始的遍历列表将覆盖到最后,只覆盖一条路径。这个数字在指数增长的时间段内减少。我已经运行了相当长的一段时间的代码,不能超过50个障碍,现在我确信

除非我永远运行它,否则这种简单的方法似乎是不够的。那么,我如何改进我的代码,使其更快、更高效,从而提供解决方案呢

基本上,我期待看到关于如何:

  • 捕获并利用特定于此问题的领域知识
  • 应用编程技巧/技巧克服疲劳

    …并最终实现一个实质性的解决方案

  • 提前谢谢


    修订版
    感谢Dave Webb将问题关联到它所属的域:


    这和骑士的非常相似 与搬家有关的旅游问题 骑士围着棋盘不停地走 重游同一广场。基本上 这是同样的问题,但与 不同的“遍历规则”



    这与在棋盘上移动骑士而不重游同一方格的问题非常相似。基本上,这是相同的问题,但“遍历规则”不同

    我记得通过递归处理骑士之旅的关键优化是,按照目的地广场上可用移动次数的递增顺序进行下一步移动。这鼓励了搜索者在一个区域内密集移动并填充它,而不是将整个区域放大,留下永远无法访问的小岛广场。(这是。)


    另外,要确保你已经尽可能地考虑了对称性。例如,在最简单的级别上,起始方块的x和y只需要增加到5,因为(10,10)与板旋转时的(1,1)相同。

    这只是问题的一个示例。德国维基百科声称这是NP难的。

    可以进行优化,以检查孤岛(即没有有效邻居的未访问空间),然后退出遍历,直到消除孤岛。这将发生在某个树遍历的“便宜”一侧附近。我想问题是,减少成本是否值得。

    我决定研究这个问题,看看是否可以将其分解为5x5个解决方案,一个解决方案的结尾从另一个解决方案的拐角处跳出来

    第一个假设是5x5是可解的。它又快又快

    所以我运行solve(0,5)并查看结果。我在Excel中画了一个10x10编号的网格,还有一个5x5编号的网格用于翻译。然后,我只是在结果中搜索了#]](结束单元格),这将是距离下一个5x5开始的一个跳跃。(例如,对于第一个正方形,我搜索了“13]”

    供参考:

    10 x 10 grid                       5 x 5 grid 
     0  1  2  3  4 |  5  6  7  8  9     0  1  2  3  4
    10 11 12 13 14 | 15 16 17 18 19     5  6  7  8  9
    20 21 22 23 24 | 25 26 27 28 29    10 11 12 13 14
    30 31 32 33 34 | 35 36 37 38 39    15 16 17 18 19
    40 41 42 43 44 | 45 46 47 48 49    20 21 22 23 24
    ---------------+---------------
    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 89
    90 91 92 93 94 | 95 96 97 98 99
    
    以下是一个可能的解决方案:

    第一个正方形:[0,15,7,19,16,1,4,12,20,23,8,5,17,2,10,22,14,11,3,18,6,9,24,21,13]将其对角跳跃到下一个5 x 5的第一个拐角处5(10×10)

    第二个方块:[0,12,24,21,6,9,17,2,14,22,7,15,18,3,11,23,20,5,8,16,19,4,1,13,10]将其与10x10中的最后一个方块25放在一起,即从55跳两下

    第三个方块:[0,12,24,21,6,9,17,5,20,23,8,16,19,4,1,13,10,2,14,11,3,18,15,7,22]将其与10x10中的最后一个方块97放在一起,即从94跳两下

    第四个正方形可以是任何有效的解决方案,因为终点并不重要。但是,从5x5到10x10的解决方案映射比较困难,因为正方形从另一个角开始。不是翻译,而是运行solve(24,5)并随机选择一个:[24,9,6,21,13,10,2,17,5,20,23,8,16,1,4,12,0,15,18,3,11,14,22,7,19]

    现在知道5x5解决方案在端点合法移动到下一个5x5角的情况下是有效的,这应该可以通过编程实现。5x5解的数量为552,这意味着存储解以便进一步计算和重新映射非常容易

    除非我做错了,否则这将为您提供一个可能的解决方案(在上述5x5解决方案中分别定义为一到四个):

    def trans5(i、col5、row5):
    如果i<5:返回5*col5+50*row5+i
    如果i<10:返回5+5*col5+50*row5+i
    如果我<
    
    def trans5(i, col5, row5):
        if i < 5: return 5 * col5 + 50 * row5 + i
        if i < 10: return 5 + 5 * col5 + 50 * row5 + i
        if i < 15: return 10 + 5 * col5 + 50 * row5 + i
        if i < 20: return 15 + 5 * col5 + 50 * row5 + i
        if i < 25: return 20 + 5 * col5 + 50 * row5 + i
    
    >>> [trans5(i, 0, 0) for i in one] + [trans5(i, 1, 0) for i in two] + [trans5(i, 0, 1) for i in three] + [trans5(i, 1, 1) for i in four]
        [0, 30, 12, 34, 31, 1, 4, 22, 40, 43, 13, 10, 32, 2, 20, 42, 24, 21, 3, 33, 11, 14, 44, 41, 23, 5, 27, 49, 46, 16, 19, 37, 7, 29, 47, 17, 35, 38, 8, 26, 48, 45, 15, 18, 36, 39, 9, 6, 28, 25, 50, 72, 94, 91, 61, 64, 82, 60, 90, 93, 63, 81, 84, 54, 51, 73, 70, 52, 74, 71, 53, 83, 80, 62, 92, 99, 69, 66, 96, 78, 75, 57, 87, 65, 95, 98, 68, 86, 56, 59, 77, 55, 85, 88, 58, 76, 79, 97, 67, 89]
    
    #! /usr/bin/env perl
    use Modern::Perl;
    
    {
      package Grid;
      use Scalar::Util qw'reftype';
    
      sub new{
        my($class,$width,$height) = @_;
        $width  ||= 10;
        $height ||= $width;
    
        my $self = bless [], $class;
    
        for( my $x = 0; $x < $width; $x++ ){
          for( my $y = 0; $y < $height; $y++ ){
            $self->[$x][$y] = undef;
          }
        }
    
        for( my $x = 0; $x < $width; $x++ ){
          for( my $y = 0; $y < $height; $y++ ){
            $self->[$x][$y] = Grid::Elem->new($self,$x,$y);;
          }
        }
    
        return $self;
      }
    
      sub elem{
        my($self,$x,$y) = @_;
        no warnings 'uninitialized';
        if( @_ == 2 and reftype($x) eq 'ARRAY' ){
          ($x,$y) = (@$x);
        }
        die "Attempted to use undefined var" unless defined $x and defined $y;
        my $return = $self->[$x][$y];
        die unless $return;
        return $return;
      }
    
      sub done{
        my($self) = @_;
        for my $col (@$self){
          for my $item (@$col){
            return 0 unless $item->visit(undef);
          }
        }
        return 1;
      }
    
      sub reset{
        my($self) = @_;
        for my $col (@$self){
          for my $item (@$col){
            $item->reset;
          }
        }
      }
    
      sub width{
        my($self) = @_;
        return scalar @$self;
      }
    
      sub height{
        my($self) = @_;
        return scalar @{$self->[0]};
      }
    }{
      package Grid::Elem;
      use Scalar::Util 'weaken';
    
      use overload qw(
        "" stringify
        eq equal
        == equal
      );
    
      my %dir = (
        #       x, y
        n  => [ 0, 2],
        s  => [ 0,-2],
        e  => [ 2, 0],
        w  => [-2, 0],
    
        ne => [ 1, 1],
        nw => [-1, 1],
    
        se => [ 1,-1],
        sw => [-1,-1],
      );
    
      sub new{
        my($class,$parent,$x,$y) = @_;
        weaken $parent;
        my $self = bless {
          parent => $parent,
          pos    => [$x,$y]
        }, $class;
    
        $self->_init_possible;
    
        return $self;
      }
    
      sub _init_possible{
        my($self) = @_;
        my $parent = $self->parent;
        my $width  = $parent->width;
        my $height = $parent->height;
        my($x,$y)  = $self->pos;
    
        my @return;
        for my $dir ( keys %dir ){
          my($xd,$yd) = @{$dir{$dir}};
          my $x = $x + $xd;
          my $y = $y + $yd;
    
          next if $y < 0 or $height <= $y;
          next if $x < 0 or $width  <= $x;
    
          push @return, $dir;
          $self->{$dir} = [$x,$y];
        }
        return  @return if wantarray;
        return \@return;
      }
    
      sub list_possible{
        my($self) = @_;
        return unless defined wantarray;
    
        # only return keys which are
        my @return = grep {
          $dir{$_} and defined $self->{$_}
        } keys %$self;
    
        return  @return if wantarray;
        return \@return;
      }
    
      sub parent{
        my($self) = @_;
        return $self->{parent};
      }
    
      sub pos{
        my($self) = @_;
        my @pos = @{$self->{pos}};
        return @pos if wantarray;
        return \@pos;
      }
    
      sub visit{
        my($self,$v) = @_;
        my $return = $self->{visit} || 0;
    
        $v = 1 if @_ == 1;
        $self->{visit} = $v?1:0 if defined $v;
    
        return $return;
      }
    
      sub all_neighbors{
        my($self) = @_;
        return $self->neighbor( $self->list_possible );
      }
      sub neighbor{
        my($self,@n) = @_;
        return unless defined wantarray;
        return unless @n;
    
        @n = map { exists $dir{$_} ? $_ : undef } @n;
    
        my $parent = $self->parent;
    
        my @return = map {
          $parent->elem($self->{$_}) if defined $_
        } @n;
    
        if( @n == 1){
          my($return) = @return;
          #die unless defined $return;
          return $return;
        }
        return  @return if wantarray;
        return \@return;
      }
    
      BEGIN{
        for my $dir ( qw'n ne e se s sw w nw' ){
          no strict 'refs';
          *$dir = sub{
            my($self) = @_;
            my($return) = $self->neighbor($dir);
            die unless $return;
            return $return;
          }
        }
      }
    
      sub stringify{
        my($self) = @_;
        my($x,$y) = $self->pos;
        return "($x,$y)";
      }
    
      sub equal{
        my($l,$r) = @_;
        "$l" eq "$r";
      }
    
      sub reset{
        my($self) = @_;
        delete $self->{visit};
        return $self;
      }
    }
    
    # Main code block
    {
      my $grid = Grid->new();
    
      my $start = $grid->elem(0,0);
      my $dest  = $grid->elem(-1,-1);
    
      my @all = solve($start,$dest);
      #say @$_ for @all;
      say STDERR scalar @all;
    }
    
    sub solve{
      my($current,$dest,$return,@stack) = @_;
      $return = [] unless $return;
      my %visit;
      $visit{$_} = 1 for @stack;
    
      die if $visit{$current};
    
      push @stack, $current->stringify;
    
      if( $dest == $current ){
        say @stack;
    
        push @$return, [@stack];
      }
    
      my @possible = $current->all_neighbors;
      @possible = grep{
        ! $visit{$_}
      } @possible;
    
      for my $next ( @possible ){
        solve($next,$dest,$return,@stack);
      }
    
      return @$return if wantarray;
      return  $return;
    }
    
    # Solve square puzzle
    import operator
    
    class Node:
    # Here is how the squares are defined
        def __init__(self, ID, base):
            self.posx = ID % base
            self.posy = ID / base
            self.base = base
        def isValidNode(self, posx, posy):
            return (0<=posx<self.base and 0<=posy<self.base)
    
        def getNeighbors(self):
            neighbors = []
            if self.isValidNode(self.posx + 3, self.posy): neighbors.append(self.posx + 3 + self.posy*self.base)
            if self.isValidNode(self.posx + 2, self.posy + 2): neighbors.append(self.posx + 2 + (self.posy+2)*self.base)
            if self.isValidNode(self.posx, self.posy + 3): neighbors.append(self.posx + (self.posy+3)*self.base)
            if self.isValidNode(self.posx - 2, self.posy + 2): neighbors.append(self.posx - 2 + (self.posy+2)*self.base)
            if self.isValidNode(self.posx - 3, self.posy): neighbors.append(self.posx - 3 + self.posy*self.base)
            if self.isValidNode(self.posx - 2, self.posy - 2): neighbors.append(self.posx - 2 + (self.posy-2)*self.base)
            if self.isValidNode(self.posx, self.posy - 3): neighbors.append(self.posx + (self.posy-3)*self.base)
            if self.isValidNode(self.posx + 2, self.posy - 2): neighbors.append(self.posx + 2 + (self.posy-2)*self.base)
            return neighbors
    
    
    # the nodes go like this:
    # 0 => bottom left
    # (base-1) => bottom right
    # base*(base-1) => top left
    # base**2 -1 => top right
    def solve(start_nodeID, base):
        all_nodes = []
        #Traverse list is the list to keep track of which moves are made (the id numbers of nodes in a list)
        traverse_list = [start_nodeID]
        for i in range(0, base**2): all_nodes.append(Node(i, base))
        togo = dict()
        #Togo is a dictionary with (nodeID:[list of neighbors]) tuples
        togo[start_nodeID] = all_nodes[start_nodeID].getNeighbors()
        solution_count = 0
    
    
        while(True):
            # The search is exhausted
            if not traverse_list:
                print "Somehow, the search tree is exhausted and you have reached the divine salvation."
                print "Number of solutions:" + str(solution_count)
                break
    
            # Get the next node to hop
            try:
                current_node_ID = togo[traverse_list[-1]].pop(0)
            except IndexError:
                del togo[traverse_list.pop()]
                continue
    
            # end condition check
            traverse_list.append(current_node_ID)
            if(len(traverse_list) == base**2):
                #OMG, a solution is found
                #print traverse_list
                solution_count += 1
                #Print solution count at a steady rate
                if(solution_count%100 == 0): 
                    print solution_count
                    # The solution list can be returned (to visualize the solution in a simple GUI)
                    #return traverse_list
    
    
            # get valid neighbors
            valid_neighbor_IDs = []
            candidate_neighbor_IDs = all_nodes[current_node_ID].getNeighbors()
            valid_neighbor_IDs = filter(lambda id: not id in traverse_list, candidate_neighbor_IDs)
    
            # if no valid neighbors, take a step back
            if not valid_neighbor_IDs:
                traverse_list.pop()
                continue
    
            # if there exists a neighbor which is accessible only through the current node (island)
            # and it is not the last one to go, the situation is not promising; so just eliminate that
            stuck_check = True
            if len(traverse_list) != base**2-1 and any(not filter(lambda id: not id in traverse_list, all_nodes[n].getNeighbors()) for n in valid_neighbor_IDs): stuck_check = False
    
            # if stuck
            if not stuck_check:
                traverse_list.pop()
                continue
    
            # sort the neighbors according to accessibility (the least accessible first)
            neighbors_ncount = []
            for neighbor in valid_neighbor_IDs:
                candidate_nn = all_nodes[neighbor].getNeighbors()
                valid_nn = [id for id in candidate_nn if not id in traverse_list]
                neighbors_ncount.append(len(valid_nn))
            n_dic = dict(zip(valid_neighbor_IDs, neighbors_ncount))
            sorted_ndic = sorted(n_dic.items(), key=operator.itemgetter(1))
    
            sorted_valid_neighbor_IDs = []
            for (node, ncount) in sorted_ndic: sorted_valid_neighbor_IDs.append(node)
    
    
    
            # if current node does have valid neighbors, add them to the front of togo list
            # in a sorted way
            togo[current_node_ID] = sorted_valid_neighbor_IDs
    
    
    # To display a solution simply
    def drawGUI(size, solution):
        # GUI Code (If you can call it a GUI, though)
        import Tkinter
        root = Tkinter.Tk()
        canvas = Tkinter.Canvas(root, width=size*20, height=size*20)
        #canvas.create_rectangle(0, 0, size*20, size*20)
        canvas.pack()
    
        for x in range(0, size*20, 20):
            canvas.create_line(x, 0, x, size*20)
            canvas.create_line(0, x, size*20, x)
    
        cnt = 1
        for el in solution:
            canvas.create_text((el % size)*20 + 4,(el / size)*20 + 4,text=str(cnt), anchor=Tkinter.NW)
            cnt += 1
        root.mainloop()
    
    
    print('Start of run')
    
    # it is the moment
    solve(0, 10)
    
    #Optional, to draw a returned solution
    #drawGUI(10, solve(0, 10))
    
    raw_input('End of Run...')