Dancing Links 学习 AND 代码详解

来源:互联网 发布:js给数组赋值 编辑:程序博客网 时间:2024/06/05 11:25

今天花时间学习了下Dancing Links,其核心思想是降低在搜索中的范围,减少复杂。降低的方法就是将用链式结构构造的图中不需要的点去掉。如果回溯再恢复。

这个方法依赖的数据结构是用数组存储的十字链表L[NN],R[NN],U[NN],D[NN] 左右上下的链接

构造数据结构:

head,cnt,L[NN],R[NN],U[NN],D[NN],H[NN],COL[NN],S[NN],ROW[NN]

head就是头结点,cnt就是在构图时结点的编号,S[NN]是某一列上有多少个元素,COL[NN],ROW[NN]是一个点对应的行列数,H[NN]是该行的最后一个点的编号

主要的数据结构就是这样。接下来看代码

#define head 100int U[N],D[N],L[N],R[N];int C[N],H[N],ans[N];//C[N]表示N的列标,H[N]表示N的行标,ans[]用来储存结果bool dance(int k){     int c = R[head];     if(c==head) {           Output();//Output the solution           return true;     }     remove(c);          for(int i=D[c]; i!=c; i=D[i])     {           ans[k] = H[i];           for(int j=R[i]; j!=i; j=R[j]) remove(C[j]);           if(dance(k+1)) return true;           for(int j=L[i]; j!=i; j=L[j]) resume(C[j]);//这里原因和resume中的一样     }          resume(c);     return false;}void remove(int c){     L[R[c]] = L[c];     R[L[c]] = R[c];     for(int i=D[c]; i!=c; i=D[i]) {           for(int j=R[i]; j!=i; j=R[j]) {                U[D[j]] = U[j];                D[U[j]] = D[j];           }     }}/*resume还有另一种写法,就是将L,R的改变放在最后,循环中U,D调换位置。这其实没什么影响因为L,R都是一条语句不影响其他U,D都是单个操作,而外层循环必须是U的循环,即一个个拿下来在按顺序放回去,不然会错误,自己用纸试一下*/void resume(int c){     L[R[c]] = c;     R[L[c]] = c;     for(int i=U[c]; i!=c; i=U[i]) {           for(int j=R[i]; j!=i; j=R[j]) {                 U[D[j]] = j;                 D[U[j]] = j;           }     }}

上面知识Dancing Links 的几个基本操作还有涉及构图。下面以POJ 3740为例:

#include <cstdio>#include <cstring>#include <iostream>using namespace std;const int NN=600;int head,cnt,L[NN],R[NN],U[NN],D[NN],H[NN],C[NN],S[NN],ROW[NN];//上下左右 一行的最右,C结点对应的列,S该列的个数int N,M;/*这里插入一个元素,则对竖直方向改变不大 原本的上一个元素的D,表头的U,当前的D指向表头,U指向表头暂存的对于水平方向,注意开头的那个元素的L,前一个元素的R,自己的L=前一个,R=开头。开头标号的暂存在前一个的R中*/void insert(int i,int j){    C[++cnt]=j;    S[j]++;    D[cnt]=j;    U[cnt]=U[j];    if(H[i]){R[cnt]=R[H[i]];L[cnt]=H[i];R[H[i]]=cnt;L[R[cnt]]=cnt;}else     R[cnt]=L[cnt]=cnt;D[U[j]]=cnt;U[j]=cnt;H[i]=cnt;}void remove(int c)//删除第c列上的元素所在的行{    R[L[c]]=R[c];    L[R[c]]=L[c];    //删除c,c是列指针,删除了c就代表删除了整列,因为递归后不可能访问到c了    for (int i=D[c]; i!=c; i=D[i]) //c所在列上的元素i        for (int j=R[i]; j!=i; j=R[j]) //i和j一行的,删掉j        {            U[D[j]]=U[j];            D[U[j]]=D[j];            S[C[j]]--;//j所在列的元素(‘1’的个数)-1;        }}void resume(int c)//对应的恢复操作{     L[R[c]] = c;       R[L[c]] = c;       for(int i=U[c]; i!=c; i=U[i]) {             for(int j=R[i]; j!=i; j=R[j]) {                   U[D[j]] = j;                   D[U[j]] = j;             }       }  }bool dance(){int i,j;    if (R[head]==head)//列指针删完了,任务完成    {        puts("Yes, I found it");        return true;    }    int s=0x3fff,c;    for ( i=R[head]; i!=head; i=R[i]) if (S[i]<s) s=S[i],c=i;    //找元素最少的列c,一种优化    remove(c);//删除列c    for ( i=D[c]; i!=c; i=D[i])    {        for ( j=R[i]; j!=i; j=R[j]) remove(C[j]);//删除所有可能的冲突元素        if (dance()) return true;        for ( j=L[i]; j!=i; j=L[j]) resume(C[j]);    }    resume(c);    return false;}/*初始化注意,由于有head,则构图时下标都从1开始,每个元素都是自然下标cnt的赋值不要忘记*/void init(){int i,j,k;for(i=0;i<=M;i++){L[i+1]=i;R[i]=i+1;U[i]=D[i]=C[i]=i;S[i]=0;}L[0]=M,R[M]=0;cnt=M;for(i=1;i<=N;i++){H[i]=0;for(j=1;j<=M;j++){scanf("%d",&k);if(k)insert(i,j);}}}int main(){    int c,i;    head=0;    while (~scanf("%d%d",&N,&M))    {       init();   if(!dance())   puts("It is impossible");}    return 0;}

还有一种就是原始的dfs:

#include <iostream>#include <stdio.h>#include <algorithm>#include <string.h>#include <math.h>using namespace std;int N,M,T;int maps[20][310];int rows[310];int dfs(int n,int cnt){if(cnt==N) return 1;int i,j,k;for(i=n;i<M;i++){k=cnt;for(j=0;j<N;j++)if(maps[i][j]==1 && rows[j]==1)break;if(j<N)continue;for(j=0;j<N;j++)if(maps[i][j]==1)rows[j]=1,k++;if(dfs(i+1,k))return 1;for(j=0;j<N;j++)if(maps[i][j]==1)rows[j]=0;}return 0;}int main(){    while(scanf("%d%d",&M,&N)!=EOF){int i,j;memset(rows,0,sizeof(rows));memset(maps,0,sizeof(maps));for(i=0;i<M;i++)for(j=0;j<N;j++)scanf("%d",&maps[i][j]);if(dfs(0,0))printf("Yes, I found it\n");elseprintf("It is impossible\n");    }    return 0;}

重复覆盖与精确覆盖做法稍有不同

精确覆盖:
首先选择当前要覆盖的列(含1最少的列),将该列和能够覆盖到该列的行全部去掉,再枚举添加的方法。
枚举某一行r,假设它是解集中的一个,那么该行所能覆盖到的所有列都不必再搜,所以删除该行覆盖到的所有列,又由于去掉的列相当于有解,所以能够覆盖到这些列的行也不用再搜,删之。
重复覆盖:
首先选择当前要覆盖的列(同上),将该列删除,枚举覆盖到该列的所有行:对于某一行r,假设它是解集中的一个,那么该行所能覆盖到的列都不必再搜,所以删除该行覆盖到的所有列。
注意此时不用删去覆盖到这些列的行,因为一列中允许有多个1。
这里有一个A*的优化:估价函数h意义为从当前状态最好情况下需要添加几条边才能覆盖。


1 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 4个月的婴儿恶心干呕怎么办 生了小孩后胆汁酸偏高怎么办 9个月宝宝吃盐了怎么办 两个月宝宝母乳拉大便太稀怎么办呀 两个月的宝宝不拉大便怎么办 两个月宝宝五天没拉大便怎么办 4个月宝宝不拉大便怎么办 2个月宝宝3天没拉大便怎么办 宝宝拉不出大便老是憋的哭怎么办 九个月的宝宝不爱吃水果怎么办 顺产侧切伤口发炎化脓有臭味怎么办 一岁宝宝感冒发烧39度怎么办 宝宝二岁半了只吃水果不吃饭怎么办 8个月小孩发烧39度怎么办 咳嗽吃了很多药都不见效果怎么办 生完孩子半个月奶水越来越少怎么办 买到了坏了的水果商家不赔怎么办 小孩奶不够吃又不吃奶粉怎么办 10个月的宝宝便秘很严重怎么办 四岁的宝宝突然不怎么吃饭怎么办 1岁7个月宝宝突然不爱吃饭怎么办 宝宝发烧好了之后不吃辅食怎么办 吃母乳的宝宝不喝奶粉怎么办 奶水不足宝宝又不喝奶粉怎么办 6个月宝宝断奶哭闹不用奶瓶怎么办 小孩不爱吃饭怎么办该吃些什么 二个月宝宝只认母乳不喝牛奶怎么办 满月宝宝只认奶粉不认母乳怎么办 宝宝四个月只认奶瓶不认母乳怎么办 新生儿只认奶瓶不认母乳怎么办 7个月宝宝不吃辅食怎么办 8个月小孩不吃辅食怎么办 9个月小孩不吃辅食怎么办 十一个月宝宝拉肚子拉水怎么办 小孩好动上课也坐不住会怎么办 幼儿大班关于环保的手抄报怎么办 捷达车打开丝位子风扇总转怎么办 小班个案分析:好动的孩子该怎么办 孩子突然过于的兴奋不睡觉是怎么办 刚出生的宝宝听力未通过怎么办 孩子多动症被老师打不上学怎么办