最长的多米诺骨牌链/序列

Longest domino chain/sequence

本文关键字:序列 多米诺骨牌      更新时间:2023-10-16

我需要找到最长的多米诺骨牌链,给定一组随机挑选的12个多米诺骨牌。我已经递归地生成了多米诺骨牌的所有可能性(使用0到12的面值有91种可能性)。多米诺骨牌由一块上面有两个正方形的"砖"组成:[A|b],其中0=<a、 b<=12.因此,多米诺骨牌的示例可以是[12,0]或[6,3]等。如果相邻的两半具有相同的值,则多米诺骨牌可以连接。

多米诺骨牌可以翻转以适应比赛。例如,给定[8,4],[9,4]可以翻转以使对[8,4][4,9]

以下(与此问题相关)方法可用于此类:

void flipEnds(); // Flips the domino
int getLeft() const;
int getRight() const;
bool hasBeenPlayed() const;
void setPlayed(bool value);

因此,这个问题的样本数据如下:

 myDomino #0: [1 12 ]
    myDomino #1: [0  5 ]
    myDomino #2: [7  9 ]
    myDomino #3: [2  7 ]
    myDomino #4: [7 12 ]
    myDomino #5: [4  8 ]
    myDomino #6: [8 10 ]
    myDomino #7: [3 11 ]
    myDomino #8: [11 12 ]
    myDomino #9: [10 11 ]
    myDomino #10: [2  9 ]
    myDomino #11: [2  4 ]

这更像是一道数学题,但我如何才能找到最长的多米诺骨牌链?我认为它必须递归地完成。

多米诺骨牌的序列可以表示为{#3,Y},{#4,N},{#0,Y}。。。第一个数字是你手中多米诺骨牌的数字,第二个数字是它是否翻转。我们想检查每一个可能的序列,但要尽早删除明显非法的序列。

假设您有一个函数testSequence(sequence),它测试序列是否有效。保留两个列表,一个是当前序列currentSeq,另一个是尚未选择unused的多米诺骨牌。

递归可能类似

checkAllSeq( currentSeq, unused ) {
   foreach( domino in unused ) {
      unused2 = unused - domino   // remove domino from unused list
      seq1 = currentSeq + {domino,true}   // add unfliped domino to sequence to test
      if( testSequence(seq1) ) {
          checkAllSeq(seq1,unused2)       // recurse
      }
      // now do it with the domino flipped
      seq2 = currentSeq + {domino,false}
      if( testSequence(seq2) ) {
          checkAllSeq(seq2,unused2)
      }
   }
}

我遗漏了几件事。这只是测试所有可能的序列,对结果没有任何作用。

我认为您可以很容易地将此问题表述为树遍历,以获得强力解决方案。

树的"根"是多米诺骨牌的首选。该节点的子节点将是可以添加到其中的每个多米诺骨牌。每降低一级都会在多米诺骨牌链的长度上添加一个。

此外,请记住,每个添加的多米诺骨牌都可以添加到链的"头"或"尾",这将增加给定节点可能的子节点数量。

许多链将被缩短,因为你没有选择了——换句话说,树中的许多节点将没有子节点。这将加快您的搜索速度。

一旦这样表述,你的问题就是遍历树,找到树中最长的链。听起来像是一个很好的递归应用程序(:

请注意,对于任何可以递归解决的问题,总是可以获得迭代解决方案,反之亦然。但我同意递归更容易解决这个问题(通常是这样)。

对于任何递归问题,都必须处理三种情况:初始情况、中间情况和终止情况。在这里,最初的情况是当你没有玩多米诺骨牌时(所以你可以在任何方向上玩任何多米诺骨牌);中间的情况是当你被要求玩一个与最近的多米诺骨牌相匹配的时候;终端情况是没有多米诺骨牌可以添加到最后一个多米诺骨牌上,或者所有多米诺骨牌都玩完了。

你需要记录两个多米诺骨牌列表:一个用于当前最知名的比赛,另一个用于目前的比赛尝试。

如果你能玩完所有的多米诺骨牌,那就早点回来;否则,可能的最佳输入实际上将花费最长的时间。

这是一种幼稚的方法,它是NP难的(多米诺骨牌两端具有相同数字的可能性不会对问题产生显著影响;它只会给每条边的权重增加0.5);使用启发法可能是值得的。

无论如何,我强烈建议先对你的多米诺骨牌进行排序,如果有重复的话就出错(记住要将翻转规范化),因为重复需要一种不同的方法来解决问题。

我也需要这个,所以我对它进行了编码。这是我的解决方案,大致基于上面salix alba的伪代码:

#include <iostream>
#include <string>
using namespace std;
struct Tile 
{
    int val[2];
};
Tile *tiles = NULL;
int nbtiles = 0;
struct Sequence 
{
    int flip;
    int used;
};
Sequence *seq = NULL;    
int *order = NULL;
int maxlevel = 0;
int usage (char *name)
{
    cerr << "usage: " << name << " start_value tile_left tile_right {tile_left tile_right} ... " << endl;
    return 1;
}
int readTiles (int argc, char *argv[])
{
    if ((argc & 0x1) != 0) 
        return (usage (argv[0]));
    if (argc < 4)
        return (usage (argv[0]));
    nbtiles = (argc - 2) / 2;
    tiles = new Tile[nbtiles];
    int side = 0;
    int itile = 0;
    for (int i = 2; i < argc; i++)
    {
        tiles[itile].val[side] = atoi (argv[i]);
        side = 1 - side;
        if (side == 0)
        {
            itile++;
        }
    }
    return 0;
}
int findLongest (int start, int level)
{
    int ret = 0;
    //The "if" below inefficient, but don't care, done at most nbtiles times
    if (level > maxlevel) 
    {
        cout << "new high level " << level;
        for (int lev = 0; lev < level; lev++)
        {
            int used = seq[lev].used;
            if (used) order[used] = lev;
        }
        for (int ind = 1; ind < level; ind++)
        {
            int lev = order[ind];
            int flip = seq[lev].flip;
            cout << " (" << tiles[lev].val[flip] << "," << tiles[lev].val[1 - flip] << ")";
        }
        cout << endl;
        maxlevel = level;
    }
    
    for (int itile = 0; itile < nbtiles; itile++)
    {
        if (seq[itile].used == 0)
        {
            for (int flip = 0; flip < 2; flip++)
            {
                if (tiles[itile].val[flip] == start)
                {
                    seq[itile].flip = flip;
                    seq[itile].used = level;
                    findLongest (tiles[itile].val[ 1 - flip], level + 1);
                    seq[itile].used = 0;
                }
            }
        }
    }
    return ret;
}
int main (int argc, char *argv[])
{
    int ret = readTiles (argc, argv);
    if (ret != 0) return ret;
    
    //for (int i = 0; i < nbtiles; i++)
        //cout << tiles[i].val[0] << " " << tiles[i].val[1] << endl;
    int start = atoi (argv[1]);
    seq = new Sequence[nbtiles];
    order = new int[nbtiles];
    findLongest (start, 1);
    return 0;
}