Dancing link x(DLX)算法 模板讲解

来源:互联网 发布:java json转换 编辑:程序博客网 时间:2024/05/16 09:32

Dancing link 是解决覆盖类问题的一种高效的方法。
具体的讲解可以参考一篇文章http://www.cnblogs.com/grenet/p/3145800.html,这篇文章图文并茂地讲述了算法思想和步骤,十分推荐。
为了方便理解,放一道模板题:
Exact cover
There is an N*M matrix with only 0s and 1s, (1 <= N,M <= 1000). An exact cover is a selection of rows such that every column has a 1 in exactly one of the selected rows. Try to find out the selected rows.
Input
There are multiply test cases. First line: two integers N, M; The following N lines: Every line first comes an integer C(1 <= C <= 100), represents the number of 1s in this row, then comes C integers: the index of the columns whose value is 1 in this row.
Output
First output the number of rows in the selection, then output the index of the selected rows. If there are multiply selections, you should just output any of them. If there are no selection, just output “NO”.
Sample Input

6 73 1 4 72 1 43 4 5 73 3 5 64 2 3 6 72 2 7

Sample Output

3 2 4 6

令人不爽的是,纵然有这么多讲解,也有现成的代码,可是对于水平低一点的程序猿来说看起来还是很吃力的(包括我在内)。费了一番功夫,我终于算是看懂了代码,所以贴上来,还请大家批评指教

#include <iostream>#include <cstdio>using namespace std;const int maxnode=100010;//最大节点数const int maxm=1010;//最大列数const int maxn=1010;//最大行数class dlx{public:    //实际行列数,实际节点数    int n,m,size;    //分别表示每个节点的右边结点,左边,上边,下边;节点所在列,结点所在行头节点,结点所在行,每一列的1的数量    int R[maxnode],L[maxnode],U[maxnode],D[maxnode],col[maxnode],H[maxn],row[maxnode],S[maxm];    //表示最后选中的行的数量,ans记录答案    int res,ans[maxn];    //初始化函数    void init(int x,int y)    {        //行列数记录        n=x;        m=y;        int i;        //数组的初始化        for(i=0;i<=m;i++)        {            //每一列的1的数量初始化为0            S[i]=0;            //由于已知只有第一行,故上下邻接点都是自己            U[i]=i;            D[i]=i;            //左右邻接点            L[i]=i-1;            R[i]=i+1;        }        //使得第一行成为一个环        L[0]=m;        R[m]=0;        //目前最大结点数为行数        size=m;        //头节点,由于没有数据输入,故初始化为一个取不到的数        for(i=0;i<=n;i++)        {            H[i]=-1;        }    }    //用于把每一个新加入的结点添加到已知的表中,r为行标号,c为列标号    void link(int r,int c)    {        //首先节点数+1,当前节点编号为size,当前节点所在列为c,且所在列1的数量+1        ++S[col[++size]=c];        //记录当前节点行标号        row[size]=r;        //竖直方向,头插法,即把新加入的结点插入到列头节点之后一个的位置,改变邻接关系完成插入操作        U[size]=c;        D[size]=D[c];        U[D[c]]=size;        D[c]=size;        //水平方向,        //如果是该行第一个,则把该节点作为这一行头节点        if(H[r]<0)        {            //自己连成环            H[r]=L[size]=R[size]=size;        }        //否则,同样头插法,把当前节点插入到行头节点之后(右边)一位,改变邻接关系,可以带入含义方便理解        else        {            R[size]=R[H[r]];//例如,当前节点右边的结点应当是头节点右边的结点            L[size]=H[r];            L[R[H[r]]]=size;            R[H[r]]=size;            //row[size]=r;        }    }    //删除结点c    //删除节点c,以及c上下节点所在的行,每次调用这个函数,    //都是从列头节点开始向下删除,这里c也可以理解为第c列    void remove(int c)    {        //把列头节点删掉(改变邻接关系,c左边的直接连到右边,在遍历时由于找不到所以移除)        R[L[c]]=R[c];        L[R[c]]=L[c];        int x,y;        //c的上下结点怎么找呢?因为行列都是环,所以一直找就好了,找到起点就是完整的一圈        //由于只有结点才加入表,所以竖直找到的都是结点        for(x=D[c];x!=c;x=D[x])        {            //删除所在行,只要删除行上面的结点,也就认为删除了行            for(y=R[x];y!=x;y=R[y])            {                //移除行结点的列方面的连接,即删除行,因为遍历不到了                D[U[y]]=D[y];                U[D[y]]=U[y];                S[col[y]]--;            }        }    }    //恢复节点c,以及c上下节点所在的行(同上,也可以理解为从第c列的头节点开始恢复    void resume(int c)    {        int i,j;        //从头节点开始        for(i=U[c];i!=c;i=U[i])        {            //对于同一行的每一个,恢复上下邻接关系            for(j=L[i];j!=i;j=L[j])            {                U[D[j]]=j;                D[U[j]]=j;                S[col[j]]++;            }        }        //对于列头节点,删除的时候只改变了c左右的连接情况,c的没改变,所以把c的信息作为起点,恢复邻接        //c左边的右边和右边的左边都是自己        L[R[c]]=R[L[c]]=c;    }    //实现主程    bool dance(int cnt)//cnt表示递归深度    {        //当原点的右边结点是自己时,所有的已经全部删除完毕,符合条件,结束,找到结果        if(R[0]==0)        {            res=cnt;            return true;        }        int cur;        cur=R[0];        //找到一个结点数最小的        for(int i=cur;i!=0;i=R[i])        {            if(S[cur]>S[i])            {                cur=i;            }        }        remove(cur);//找到节点数最少的列,当前元素不是原图上0,1的节点,而是列头节点,删除        int i,j;        //对于每一行        for(i=D[cur];i!=cur;i=D[i])        {            //假设当前行就是选中的答案,即使不是也无所谓,找到正确答案的时候会覆盖掉原来的值            ans[cnt]=row[i];            //删除列上面的结点及其所在的行,保证那些行不会被选中而造成同一列有多个1            for(j=R[i];j!=i;j=R[j])            {                remove(col[j]);            }            //对删除完毕的矩阵递归操作,成功,则返回true            if(dance(cnt+1))            {                return true;            }            //矩阵恢复            for(j=L[i];j!=i;j=L[j])            {                resume(col[j]);            }        }        resume(cur);        return false;    }};dlx test;int main(){    int nn,mm;    while(scanf("%d%d",&nn,&mm)!=EOF)    {        test.init(nn,mm);        int cn=0,j;        for(int i=1;i<=nn;i++)        {        scanf("%d",&cn);        while(cn--)        {            //挨个加入构建的矩阵            scanf("%d",&j);            test.link(i,j);        }        }        if(!test.dance(0))        {            printf("NO\n");        }        else        {            //结果输出            printf("%d",test.res);            for(int i=0;i<test.res;i++)                printf(" %d",test.ans[i]);            printf("\n");        }    }    return 0;}

字字皆心血,还望对大家有帮助!!!

原创粉丝点击