DLX精确覆盖问题

众所周知,覆盖问题是NP问题,也就是只能通过暴力的手段解决,无法通过数据结构优化时间复杂度,而Dancing Links(DLX、舞蹈链、十字链表)可以以很高的效率处理类似的问题。

首现给出两种问题的定义:(本文只讲精确覆盖问题)

1、精确覆盖问题

给定一个01矩阵,我们需要选出最少的行数,使得每一列恰好只有一个1

2、重复覆盖问题

给定一个01矩阵,我们需要选出最少的行数,使得每一列至少有一个1

设01矩阵有n行m列,对于第一类问题,如果用暴力做法,每次枚举选出那些行,然后判断每种选法是否可行,复杂度为选择O(2^n),判断O(nm),总复杂度为O(2^n\cdot nm),非常之高。

不过我们仔细观察不难发现:

1、可以先看1的数量少的列,因为这些我们必须要在其中选择一行,1越少枚举的次数就越少

2、当我们选中某一行后,其他的带有当前列1的行都要删去,因为已经选定了,不能在选择更多的1了

3、我们选中的当行的其他列的1也可以通过这种方式删去,原理同上

举个例子:

 对于当前这个6x7的矩阵,我们观察每一列1的数量,第1、4、7行有3个1,其他都是2个1,那么选择第二列,第二列的第3、5行为1,选择第三行,然后划去重复的行和确定的列:

那么在选完第三行处理完毕后的矩阵变成了

随后继续选取1最少的列第5列

 选择第六行并划去重复的行和确定的列,当划去重复的行与列时,剩下的矩阵为空矩阵,那么就代表所有的列都被覆盖到了,返回递归的层数即为选择的行数。记录此时选择的行数,然后回到上一步,此时要把刚才删除的行与列都恢复

然后选择第五列中的其他行,也就是第一行继续递归操作

…………

        这样的流程便是X算法。为了满足X算法大量删除、恢复行与列的操作,Donald E. Knuth 想到了用双向十字链表来维护这些操作。而在双向十字链表上不断跳跃的过程被形象地比喻成「跳跃」,因此被用来优化 X 算法的双向十字链表也被称为「Dancing Links」。

        十字链表,顾名思义,有四个指针,分别指向上下左右的下一个为1的地址,下面是将一个矩阵存进十字链表的步骤:

void add(int& hh, int& tt, int x, int y)
{
    row[idx] = x, col[idx] = y, s[y] ++ ;
    u[idx] = y, d[idx] = d[y], u[d[y]] = idx, d[y] = idx;
    r[hh] = l[tt] = idx, r[idx] = tt, l[idx] = hh;
    tt = idx ++ ;
}


for (int i = 1; i <= n; i ++ )
{
    int hh = idx, tt = idx;
    for (int j = 1; j <= m; j ++ )
    {
        int x;
        scanf("%d", &x);
        if (x) add(hh, tt, i, j);
    }
}

总结一下X算法的流程:

1、对于现在的矩阵M,选择并标记一行r,将r添加至S中;

2、如果尝试了所有的 r 却无解,则算法结束,输出无解;

3、标记与 r 相关的行r_iC_i(相关的行和列与 X 算法 中第 2 步定义相同,下同);

4、删除所有标记的行和列,得到新矩阵 M';

5、如果 M' 为空,且 r 为全1,则算法结束,输出被删除的行组成的集合S;

如果 M' 为空,且 r 不全为1,则恢复与 r 相关的行 r_i 以及列 c_i,跳转至步骤 1;

如果 M' 不为空,则跳转至步骤 1。

有了Dancing-Links,删除和恢复行在递归里就很容易写了:

bool dfs()
{
    if (!r[0]) return true;
    int p = r[0];
    for (int i = r[0]; i; i = r[i])
        if (s[i] < s[p])
            p = i;
    remove(p);
    for (int i = d[p]; i != p; i = d[i])
    {
        ans[ ++ top] = row[i];
        for (int j = r[i]; j != i; j = r[j]) remove(col[j]);
        if (dfs()) return true;
        for (int j = l[i]; j != i; j = l[j]) resume(col[j]);
        top -- ;
    }
    resume(p);
    return false;
}

        再来看一题DLX的常用题型。在搜索一章中求解数独问题时,当时用的是搜索+剪枝优化,无论是代码量还是难度都是非常之大的。学过DLX算法之后就可以用十字链表来处理数独问题了:

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值