查找图中哈密顿路径数的算法

时间:2011-03-16 23:44:22

标签: algorithm path np-complete

我正在尝试解决Hamiltonian Path问题的略微修改版本。修改它的起点和终点是给我们的,而不是确定是否存在解,我们想找到解的(可能是0)。

图表作为2D数组提供给我们,节点是数组的元素。此外,我们只能水平或垂直移动,而不是对角移动。毋庸置疑,我们不能从一个城市到两个城市,因为要做到这一点,我们需要两次访问一个城市。

我写了一个强力解决方案,试图在每个节点上尝试所有4个(边缘上的节点为3或2个)可能的移动,然后计算解决方案的数量(当它达到目标并且已经看到所有其他节点时) ),但它在适度大小的输入上运行了荒谬的时间(比如7x7阵列)。

我也想过使用bidirectional search,因为我们知道目标,但这并没有真正帮助,因为我们不仅仅希望条纹满足,我们还要确保所有节点都已经参观。此外,可能是当所有节点在两个条纹之间耗尽时,它们以不能连接的方式结束。

我觉得有些东西我不知道这让我只有一个蛮力的解决方案。我知道这个问题本身就是NP完全的,但我想知道是否有任何有关蛮力的改进。有人可以提出别的建议吗?

- 编辑 -

我提到使用双向搜索并没有真正帮助,我想澄清为什么我这么认为。考虑一个2x3图,左上角和右下角节点分别是起点和目标。让双向搜索的条纹从开始向左移动,从目标开始向左移动。在2次移动之后,所有节点都将被访问但是没有办法加入条纹,因为我们只能从一个节点向一个方向移动。但是,正如David在下面的回答中指出的那样,也许可以使算法有一些修改。

6 个答案:

答案 0 :(得分:3)

根据Wolfram Alpha

  

......唯一已知的确定方法   给定的一般图是否有   哈密​​顿路径是承接的   详尽的搜索

我相信你会想要找到一条哈密尔顿路径,然后将它分成两条路径,使得分裂点尽可能清楚地将两条路径分开。然后你可以在子图中找到排列(当然是递归!)

我不知道确切的算法,但是那种分而治之的方法就是我要开始的地方。

答案 1 :(得分:1)

您仍然可以使用双向搜索,只需在搜索中添加约束,以便之前看到的节点不会成为搜索的候选者。

你可以采取的另一种方法是将搜索分解为更小的搜索范围。

例如,尝试通过解决:

解决原始问题
  

对于每个节点,n,它不是起始节点或结束节点,查找从开始到n(set1)和从n到结尾(set2)的所有路径。

找到set1set2后,您可以丢弃其交叉产品中具有除节点n之外的公共节点的所有元素。

答案 2 :(得分:1)

有人在https://mathoverflow.net/questions/36368/efficient-way-to-count-hamiltonian-paths-in-a-grid-graph-for-a-given-pair-of-vert上问了一个与你的问题非常类似的问题,并且(1)他们没有得到大量的“这里是如何有效地做到”的回应(这可能意味着没有一个简单的方法),(2)Mathematica显然需要5个小时来计算7x7网格上相对角之间的路径,所以你可能不会做任何非常错误的事情,并且(3)答案中有一些有趣的指针。

答案 3 :(得分:0)

在7x7阵列(即总共7 * 7 = 49个节点)上,具有O(n!)算法或O(2 ^ n * n ^ 2)算法将花费太多时间。

考虑到这个特定图形的特殊特征(例如,每个节点最多有4个边缘),也许有某种方法可以加快速度,但快速解决方案似乎不太可能(除非有人偶然找到哈密顿量的多项式时间算法)路径问题本身)。

答案 4 :(得分:0)

我可以使用带有位掩码的DP来解决n的最大值为20或更多的问题。创建一个2d dp表。 dp [i] [j]表示您在第i个顶点上的情况的答案,并且j存储有关访问顶点的信息。这是C ++代码。

使用的宏:

#define    oncnt    __builtin_popcount
typedef    vector<int>   vi;

主要外部:

vi ad[21];
 int n,m;

 int dp[20][(1<<19)+1];

int sol(int i,int mask)
{
    //cout<<i+1<<" "<<oncnt(mask)<<"\n";

    if(i==n-1)
    return 1;

    if(mask&(1<<i))
    return 0;

    int &x=dp[i][mask];
    if(x!=-1)
    return x;

    x=0;

    for(int j=0;j<ad[i].size();j++)
    {
        int k=ad[i][j];
        if(mask&(1<<k))
        continue;
        if(k==n-1&&oncnt(mask)!=n-2)
        continue;
        if(k!=n-1&&oncnt(mask)==n-2)
        continue;
// The above two pruning statements are necessary.
        x=madd(x,sol(k,mask|(1<<i)));

    }

    return x;

}

在Main内部:

cin>>n>>m;

for(int i=0;i<=n-1;i++)
 {
    for(int j=0;j<=(1<<(n-1));j++)
    dp[i][j]=-1;
 }


int a,b;
for(int i=1;i<=m;i++)
{
    cin>>a>>b;
    a--;
    b--;
    ad[a].pb(b);
}


 cout<<sol(0,0);

答案 5 :(得分:0)

我发现这种方法非常快,并且能够将其推广到六边形网格上:https://hal.archives-ouvertes.fr/hal-00172308/document。诀窍是通过网格推动边界,同时跟踪可能的路径。我的实现可以在几秒钟内处理 20x20 的网格。