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

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

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

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

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

暴力搜索需要$O(2^n \times 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=,MAXM=,MAXV=(>>)+;
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=;i<=m;i++)
{
ps[i]=;
up[i]=dn[i]=i;
lt[i]=i-;rt[i]=i+;
}
rt[m]=;lt[]=m;
for(int i=;i<=n;i++)
ph[i]=-;
} //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]<)//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[]==)
{
// tot_ans=d;
return ;
}
int c=rt[];
for(int i=rt[];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+))
return ;
for(int j=lt[i];j!=i;j=lt[j])
rebuild(col[j]);
}
rebuild(c);
return ;
}
}dlx; DancingLinks

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

数据结构4——浅谈DancingLinks的思想及应用的更多相关文章

  1. 数据结构3——浅谈zkw线段树

    线段树是所有数据结构中,最常用的之一.线段树的功能多样,既可以代替树状数组完成"区间和"查询,也可以完成一些所谓"动态RMQ"(可修改的区间最值问题)的操作.其 ...

  2. 浅谈桶排思想及[USACO08DEC]Patting Heads 题解

    一.桶排思想 1.通过构建n个空桶再将待排各个元素分配到每个桶.而此时有可能每个桶的元素数量不一样,可能会出现这样的情况:有的桶没有放任何元素,有的桶只有一个元素,有的桶不止一个元素可能会是2+: 2 ...

  3. 【数据结构】浅谈倍增求LCA

    思路 运用树上倍增法可以高效率地求出两点x,y的公共祖先LCA 我们设f[x][k]表示x的2k辈祖先 f[x][0]为x的父节点 因为从x向根节点走2k 可以看成从x走2k-1步 再走2k-1步 所 ...

  4. 浅谈React编程思想

    React是Facebook推出的面向视图层开发的一个框架,用于解决大型应用,包括如何很好地管理DOM结构,是构建大型,快速Web app的首选方式. React使用JavaScript来构建用户界面 ...

  5. 浅谈Java面向对象思想

    本人免费整理了Java高级资料,涵盖了Java.Redis.MongoDB.MySQL.Zookeeper.Spring Cloud.Dubbo高并发分布式等教程,一共30G,需要自己领取.传送门:h ...

  6. cdq分治浅谈

    $cdq$分治浅谈 1.分治思想 分治实际上是一种思想,这种思想就是将一个大问题划分成为一些小问题,并且这些小问题与这个大问题在某中意义上是等价的. 2.普通分治与$cdq$分治的区别 普通分治与$c ...

  7. 浅谈算法和数据结构: 七 二叉查找树 八 平衡查找树之2-3树 九 平衡查找树之红黑树 十 平衡查找树之B树

    http://www.cnblogs.com/yangecnu/p/Introduce-Binary-Search-Tree.html 前文介绍了符号表的两种实现,无序链表和有序数组,无序链表在插入的 ...

  8. 浅谈java类集框架和数据结构(2)

    继续上一篇浅谈java类集框架和数据结构(1)的内容 上一篇博文简介了java类集框架几大常见集合框架,这一篇博文主要分析一些接口特性以及性能优化. 一:List接口 List是最常见的数据结构了,主 ...

  9. 浅谈PHP数据结构之栈

    今天開始进阶自己的PHP,首先一切的编程语言都须要修炼自己的"内功",何为程序猿的"内功",我想大概就是数据结构和算法了吧 .毕竟是灵魂,是普通程序猿到高级程序 ...

随机推荐

  1. 菜鸟版JAVA设计模式—外观模式

    外观模式是一种比較easy理解的模式,作用非常easy.就是解耦合. 结构也是非常easy,一个外观类.这个外观类持有非常多的业务类. 再由客户类去调用这个外观类去实现一些列的业务操作... 这个模式 ...

  2. 用泛型创建SqlServerHelper类实现增删改查(一)

    使用泛型,可以构建对数据库单表的基本增删改查. 首先有一数据库 Test_SqlServerHelper ,有2表 接下来创建项目,对数据库进行增删改查. 直接贴代码:(SqlServerHelper ...

  3. Django的Form(二)

    上一篇已经简单介绍了Django的Form,现在开始进阶操作了 ..... 创建Form类的时候,主要涉及到Form字段和Form的插件,字段用于做form验证,插件用来生成HTML DjiangoF ...

  4. .Net 异步方法, await async 使用

    最近朋友问起await  和 async第一次听说这个await ,就查了一下这个await使用在于 异步方法async 中,中文意思就是等待,经过一系列的百度参考简单的明白了这个东西的意思,  异步 ...

  5. Android查缺补漏(View篇)--自定义 View 中 wrap_content 无效的解决方案

    自定义 View 中 wrap_content 无效的解决方案 做过自定义 View 的童鞋都会发现,直接继承 View 的自定义控件需要重写 onMeasure() 方法,并设置 wrap_cont ...

  6. 3D位置语音,引领吃鸡游戏体验升级

    欢迎大家前往云加社区,获取更多腾讯海量技术实践干货哦~ 作者:腾讯游戏云 导语:在刚刚结束的首届腾讯用户开放日上,腾讯音视频实验室带着3D位置音效解决方案,向所有用户亮相,为用户提供360度立体空间的 ...

  7. ASP.NET Core 一步步搭建个人网站(3)_菜单管理

    上一章,我们实现了用户的注册和登录,登录之后展示的是我们的主页,页面的左侧是多级的导航菜单,定位并展示用户需要访问的不同页面.目前导航菜单是写死的,考虑以后菜单管理的便捷性,我们这节实现下可视化配置菜 ...

  8. JMeter获取CSV文件行数

    import java.io.BufferedReader; import java.io.FileReader; BufferedReader br=new BufferedReader(new F ...

  9. springboot swagger-ui结合

    随着移动互联的发展,前后端的分离已经是趋势.前后端已不是传统部门的划分,而是它们各有一套的生态系统,包括不同的开发语言.不同的开发流程.构建方式.测试流程等.做前端的不需要会maven作为构建工具,后 ...

  10. C#Linq技术中SelectMany(...)函数的内部实现的伪代码

    我们先来假设这种场景: 一个学校中有多个年级,一个年级有多个班级,一个班级里有多个学生.这里我们只需要班级.年级.和学生这三个概念: 让我们先来定义Class类和Student类: // 注意,Cla ...