• 数据结构4——浅谈DancingLinks的思想及应用


    在学习DancingLinks之前,我们先来回顾一下我们以前学过的回溯法。

    我们学习基础的回溯法的时候,我们都是先判断是否达到解,然后继续搜索。

    对于搜到的下一个点,将他标记为使用过( vis[i]=1; ),然后进入下一层搜索。

    当解决精确覆盖问题(给定几个集合,使得找出其中一个或几个集合,满足这些集合中的元素互不重复,然后覆盖$[1,n]$的每一个数)的时候,我们发现普通的回溯算法不好写,而且我们需要模拟一个01矩阵。例如下面这个矩阵,他表示有四个集合$S_1,S_2,S_3,S_4$,其中有$3$列,当第$i$行第$j$列为1时,表示集合$S_i$中有元素$j$。我们要求的精准覆盖,就是找出几个集合,满足他们交集为空,并集刚好覆盖每一列。例如下面的$S_1$和$S_3$。(刚好覆盖3列,且没有重复)

    $$egin{pmatrix} 1 & 0 & 1 \ 0 & 1 & 1 \  0 & 1 & 0 \ 1 & 1 & 0 end{pmatrix}$$

    暴力搜索需要$O(2^n imes m)$的时间复杂度。然后我们需要一个复杂度相对优秀的数据结构帮助我们写回溯算法。于是Donald E.Knuth发明了舞蹈链。这个数据结构在缓存和回溯的过程中效率惊人,不需要额外的空间,以及近乎线性的时间。而在整个求解过程中,指针在数据之间跳跃着,就像精巧设计的舞蹈一样,由此得名。

    Dancing Links用的数据结构是交叉十字循环双向链。

    因为精确覆盖问题所模拟的矩阵往往是稀疏矩阵(矩阵中,0的个数多于1),Dancing Links仅仅记录矩阵中值是1的元素。

    然后在回溯算法中,我们就把标记为已用改成删除这一列。

    然后按照dfs的模板打一下。

    那么怎么实现插入和删除呢?我们考虑普通的链表,它的插入和删除就是找到一个节点,然后把它前面和后面的连起来(删除)或者分别连接前一个和后一个(插入)。舞蹈链也类似,当搜索到有一个集合是就是删除集合中所有为1的列。

    于是我们整理出了一个回溯的过程(X算法)。

    1、从矩阵中选择一行

    2、根据定义,标示矩阵中其他行的元素

    3、删除相关行和列的元素,得到新矩阵

    4、如果新矩阵是空矩阵,并且之前的一行都是1,那么求解结束,跳转到6;新矩阵不是空矩阵,继续求解,跳转到1;新矩阵是空矩阵,之前的一行中有0,跳转到5

    5、说明之前的选择有误,回溯到之前的一个矩阵,跳转到1;如果没有矩阵可以回溯,说明该问题无解,跳转到7

    6、求解结束,把结果输出

    7、求解结束,输出无解消息

    因为我们使用了DancingLinks实现X算法,所以这个算法又叫DLX算法。

    具体实现上,我们对于每一个结点记下它的左边一列(lt)右边一列(rt)上面一行(up)下面一行(dn),然后注意初始时按照输入插入( insert(r,c) 表示集合$S_r$有元素$c$),回溯时除了删除,还有恢复(就像写普通搜索时vis要恢复为0一样)。恢复刚好与删除相反。

    那么有同学就会提出疑问:是不是会出现删除之后还没恢复就在同一层继续求解呢(这样会导致答案错误)?答案是不会。因为我们的dance()函数是按照dfs顺序,当没有恢复的时候不会再同一层再往下搜(即先删除先恢复的性质)。

    那么这样我们就把DancingLinks的基础知识点就讲完了。

    附:Cpp代码(恢复代码中有关tot_ans和ans[]的内容,最后可以输出覆盖方案)。

    struct DancingLinks
    {
        //init
        static const int MAXN=1010,MAXM=1010,MAXV=(1000000>>3)+10;
        int n,m,sz;
        int up[MAXV],dn[MAXV],lt[MAXV],rt[MAXV],row[MAXV],col[MAXV];
        int ph[MAXN],ps[MAXM];//记录行的选择情况和列的覆盖情况
    //    int tot_ans,ans[MAXN];
        
        void init(int _n,int _m)
        {
            n=_n;m=_m;sz=m;
            for(int i=0;i<=m;i++)
            {
                ps[i]=0;
                up[i]=dn[i]=i;
                lt[i]=i-1;rt[i]=i+1;
            }
            rt[m]=0;lt[0]=m;
            for(int i=1;i<=n;i++)
                ph[i]=-1;
        }
        
        //operation
        void insert(int r,int c)
        {
            ps[c]++;
            col[++sz]=c;
            row[sz]=r;
            up[sz]=c;
            up[dn[c]]=sz;
            dn[sz]=dn[c];
            dn[c]=sz;
            if(ph[r]<0)//head
                ph[r]=lt[sz]=rt[sz]=sz;  
            else
            {
                lt[sz]=ph[r];
                  rt[sz]=rt[ph[r]];
                  lt[rt[ph[r]]]=sz;
                  rt[ph[r]]=sz;
            }
            return;
        }
        void remove(int c)
        {
            lt[rt[c]]=lt[c];
            rt[lt[c]]=rt[c];
            for(int i=dn[c];i!=c;i=dn[i])
                for(int j=rt[i];j!=i;j=rt[j])
                {
                    up[dn[j]]=up[j];  
                    dn[up[j]]=dn[j];  
                    ps[col[j]]--;  
                }
        }
        void rebuild(int c)
        {
            for(int i=up[c];i!=c;i=up[i])
                for(int j=lt[i];j!=i;j=lt[j])
                {
                    up[dn[j]]=dn[up[j]]=j;
                    ps[col[j]]++;  
                }
            lt[rt[c]]=rt[lt[c]]=c;
        }
        
        //dance
        bool dance(int d)
        {
            if(rt[0]==0)
            {
    //            tot_ans=d;
                return 1;
            }
            int c=rt[0];  
            for(int i=rt[0];i;i=rt[i])
                if(ps[i]<ps[c])  
                    c=i;
            remove(c);
            for(int i=dn[c];i!=c;i=dn[i])  
            {  
    //            ans[d]=row[i];
                for(int j=rt[i];j!=i;j=rt[j])
                    remove(col[j]);
                if(dance(d+1))
                    return 1;
                for(int j=lt[i];j!=i;j=lt[j])  
                    rebuild(col[j]);  
            }  
            rebuild(c);
            return 0;
        }
    }dlx;
    
    DancingLinks

    参考资料:跳跃的舞者,舞蹈链(Dancing Links)算法——求解精确覆盖问题

  • 相关阅读:
    mySQL 规格严格
    单点登录JASIG研究分析
    Liferay 5.1.1 安装与整合CAS
    使用 CAS 在 Tomcat 中实现单点登录
    单点登录JASIG研究分析
    Liferay 5.1.1 安装与整合CAS
    SSO(Single Signon) in Action
    使用 CAS 在 Tomcat 中实现单点登录
    Netbeans 插件模块(Plugin Module)的开发
    NetBeans 时事通讯(刊号 # 22 Aug 18, 2008)
  • 原文地址:https://www.cnblogs.com/frankchenfu/p/8278563.html
Copyright © 2020-2023  润新知