在学习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. Android长按事件和点击事件问题处理,OnItemLongClickListener和OnItemClickListener冲突问题

    今天在做demo时,须要设置ListView的item的长按和点击事件.OnItemLongClickListener和OnItemClickListener,然而点击事件能够实现,可是在长按操作时会 ...

  2. UVA 540(队列)

    Description  Team Queue  Queues and Priority Queues are data structures which are known to most comp ...

  3. HDU 4923 Room and Moor (多校第六场C题) 单调栈

    Problem Description PM Room defines a sequence A = {A1, A2,..., AN}, each of which is either 0 or 1. ...

  4. 十三、 Spring Boot 启动加载数据 CommandLineRunner

    实际应用中,我们会有在项目服务启动的时候就去加载一些数据或做一些事情这样的需求. 为了解决这样的问题,spring Boot 为我们提供了一个方法,通过实现接口 CommandLineRunner 来 ...

  5. 三.RabbitMQ之异步消息队列(Work Queue)

    上一篇文章简要介绍了RabbitMQ的基本知识点,并且写了一个简单的发送和接收消息的demo.这一篇文章继续介绍关于Work Queue(工作队列)方面的知识点,用于实现多个工作进程的分发式任务. 一 ...

  6. 如何解决更新被拒绝,因为远程版本库包含您本地尚不存在的提交。这通常是因为另外 提示:一个版本库已向该引用进行了推送。再次推送前,您可能需要先整合远程变更 提示:(如 'git pull ...')。

    不要通过网页提交,通过网页提交一次,然后在终端再次push的时候,会认为网上代码仓库已经被其他地方提交过一次代码,此时会拒绝终端push 这个时候只能是pull,然后才能再次在终端提交. 也就是说,避 ...

  7. ATM程序设计

    package com.arthur.object; import java.util.Scanner; /*** * 简单的ATM存取款程序 1.登陆页面 2.登陆 3.菜单:1,查询,2,存款,3 ...

  8. IDEA使用有道翻译插件

    使用IDEA编写代码或者查看源码的时候有时候需要使用的翻译功能,虽然已经有繁多的翻译服务提供了桌面版的软件,但是并不大适合使用在阅读或者编写代码这个场景.IDEA丰富的插件库为我们提供了一些翻译插件, ...

  9. 运算符关键字。数据区别大小写。日期范围。判空的两种写法。NOT IN的两种写法。IN范围可含NULL,但NOT IN值范围不能含NULL。

    比较:>,<,=,>=,<=,<>(!=) 逻辑:AND,OR,NOT 范围:BETWEEN...AND... 范围:IN,NOT IN 判空:IS NULL, I ...

  10. Vue2 后台管理系统解决方案

    基于Vue.js 2.x系列 + Element UI 的后台管理系统解决方案. github地址:https://github.com/lin-xin/manage-system demo地址:ht ...