·精确覆盖问题

精确覆盖问题的定义:给定一个由0-1组成的矩阵,是否能找到一个行的集合,使得集合中每一列都恰好包含一个1。

例如:如下的矩阵

就包含了这样一个集合(第1、4、5行)。

·常规的解法

采用回溯法

每一次枚举选择的行,可行则继续,若无论怎么选都不能再继续,回溯。

这里引用大佬的例子:

原址:https://www.cnblogs.com/grenet/p/3145800.html

矩阵1:

先假定选择第1行,如下所示:

如上图中所示,红色的那行是选中的一行,这一行中有3个1,分别是第3、5、6列。

由于这3列已经包含了1,故,把这三列往下标示,图中的蓝色部分。蓝色部分包含3个1,分别在2行中,把这2行用紫色标示出来

根据定义,同一列的1只能有1个,故紫色的两行,和红色的一行的1相冲突。

那么在接下来的求解中,红色的部分、蓝色的部分、紫色的部分都不能用了,把这些部分都删除,得到一个新的矩阵

矩阵2:

行分别对应矩阵1中的第2、4、5行

列分别对应矩阵1中的第1、2、4、7列

于是问题就转换为一个规模小点的精确覆盖问题

在新的矩阵中再选择第1行,如下图所示

还是按照之前的步骤,进行标示。红色、蓝色和紫色的部分又全都删除,导致新的空矩阵产生,而红色的一行中有0(有0就说明这一列没有1覆盖)。说明,第1行选择是错误的

那么回到之前,选择第2行,如下图所示

按照之前的步骤,进行标示。把红色、蓝色、紫色部分删除后,得到新的矩阵

矩阵3:

行对应矩阵2中的第3行,矩阵1中的第5行

列对应矩阵2中的第2、4列,矩阵1中的第2、7列

由于剩下的矩阵只有1行,且都是1,选择这一行,问题就解决

于是该问题的解就是矩阵1中第1行、矩阵2中的第2行、矩阵3中的第1行。也就是矩阵1中的第1、4、5行

从上面的求解过程来看,实际上求解过程可以如下表示

1、从矩阵中选择一行

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

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

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

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

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

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

这样做的话就会遇到以下问题:

  1. 如何储存每一步的状态;

  2. 如何输出正确结果。

算法大师Donald E.Knuth提出的舞蹈链便是一种巧妙的解决方法。

·Dancing Links

储存方式

我们都知道链表具有便于插入和删除的优点,

如:a,b,c顺次相连,

删除b:    b.left.right = b.right, b.right.left = b.left;//即:a.right = c, c.left = a;没有改变b中的内容
再插入b:  b.left.right = b.right.left = b; //即:a.right = b.left = b;可利用b中的内容

再看常规做法中的关键步骤——删除行(列) 和 插入(恢复)行(列),恰好可以利用这两点便利,不仅节省空间(因为不必每一步都重开矩阵),时间上效率也高。

Dancing Links 使用的是交叉十字循环双向链

交叉:每一行一条(。。)链表,每一列再来一条(。。)链表。

由于我们只考虑覆盖那些行列,只关心为1的位置,所以将为1的位置存起来,

如下图所示:(一张著名的图。。)

对应矩阵:

注意到第一行的元素:

       head和C1、C2……都是辅助元素:

       head主要起到寻找未覆盖列和判定是否找到一种方案,head.right就是还未覆盖的某一列列标,head.right==head时表明所有列都已被覆盖。

       C1、C2…可以看作列标,删除和恢复列都将从这里开始,同时也保证删除列后还能找到恢复列的“入口”。

所以可以定义结构体如下:

struct node
{node *L, *R, *U, *D, *col;int x;node(node *a = 0, node *b = 0, node *c = 0, node *d = 0,node *e = 0, int f = 0):L(a), R(b), U(c), D(d), col(e), x(f) {};
}

其中, L,R,U,D分别指向左、右、上、下的元素,col指向所在行的列标元素,x是行号。

至于构造函数。。。纯粹是我乱搞的。。。。

删除(remove)

考虑到精确覆盖问题的定义,我们的目标是覆盖所有列,所以每一次找到还未覆盖的一列进行操作。
       假设我们找到了列标为c的一列,我们先假装要覆盖这一列。
       首先就要删除这一列,而我们只需在行链表上删除列标即可,因为进入列都是通过列标实现的。然后不论我们选哪一行来覆盖,所有能覆盖这一列的行都不能再选了,于是这一列上每一个元素(除了列标)所在的行都要删除。但是,有可能会出现一种不妙的状况——  c->D == c, 即无法覆盖这一列!这时,就应该返回false表示决策错误了。如果成功删除了这一列,自然就返回true了。
代码如下:

bool remove(node *c)
{c->L->R = c->R, c->R->L = c->L;if (c->D == c) return false;for (node *i = c->D; i != c; i = i->D)for (node *j = i->R; j != i; j = j->R){j->U->D = j->D;j->D->U = j->U;}return true;
}

          删除行时也要注意保留“入口”,即第7行的:j = i->R 和 j != i。

          Ps:如果感觉不好理解,可以在本文最后的完整代码里和主体函数DLX()联系着看。

恢复

恢复就相对简单一些了。

第一步:恢复该列相关的行;

第二步:恢复该列。

上代码:

void recover(node *c)
{for (node *i = c->D; i != c; i = i->D)for (node *j = i->R; j != i; j = j->R)j->U->D = j->D->U = j;c->L->R = c->R->L = c;
}

主体部分

主体部分主要是回溯的过程。

首先要找到未覆盖的列,head->right即是(见上文的“储存方式”一节),若head->right == head,说明找到解,返回true。

然后就是删除这一列,这时判断一下remove函数返回值,若为false可直接返回false表不可行。

成功删除列后,我们发现我们之前只删除了相关的行,没有真正地“选”哪一行,于是我们枚举一下选哪一行,并把这一行能覆盖的列都删除。做完这一工作,我们就可以继续处理剩下的未覆盖列了。

处理剩下的列我们依然调用主体函数,当它返回了true时,表明已经找到解,我们枚举的这一行当然也在解中,直接输出。否则,我们就要继续尝试选择另一行,在此之前,先恢复之前被我们删掉的行。

若枚举完所有行都没有可行解,返回false。

代码如下:

bool DLX()
{node *c = head->R;if (c == head) return true;if (!remove(c)) return false;for (node *i = c->D; i != c; i = i->D){for (node *j = i->R; j != i; j = j->R)remove(j->col);if (DLX()){printf ("%d ", i->x);return true;}for (node *j = i->R; j != i; j = j->R)recover(j->col);}return false;
}

总结

如此,指针飞舞,大概就是Dancing Links名称的来源吧。

完整的代码(输出一组可行解):

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;struct node
{node *L, *R, *U, *D, *col;int x;node(node *a = 0, node *b = 0, node *c = 0, node *d = 0,node *e = 0, int f = 0):L(a), R(b), U(c), D(d), col(e), x(f) {};
}*head, *tail[100005];
int n, m;bool remove(node *c)
{c->L->R = c->R, c->R->L = c->L;if (c->D == c) return false;for (node *i = c->D; i != c; i = i->D)for (node *j = i->R; j != i; j = j->R){j->U->D = j->D;j->D->U = j->U;}return true;
}
void recover(node *c)
{for (node *i = c->D; i != c; i = i->D)for (node *j = i->R; j != i; j = j->R)j->U->D = j->D->U = j;c->L->R = c->R->L = c;
}
bool DLX()
{node *c = head->R;if (c == head) return true;if (!remove(c)) return false;for (node *i = c->D; i != c; i = i->D){for (node *j = i->R; j != i; j = j->R)remove(j->col);if (DLX()){printf ("%d ", i->x);return true;}for (node *j = i->R; j != i; j = j->R)recover(j->col);}return false;
}
int main()
{head = new node();scanf ("%d%d", &n, &m);for (int i = 1; i <= m; i++)tail[i] = new node();for (int i = 2; i < m; i++)tail[i]->L = tail[i - 1], tail[i]->R = tail[i + 1];for (int i = 1; i <= m; i++)tail[i]->U = tail[i]->D = tail[i];head->L = tail[m], head->R = tail[1], head->D = head->U = head;tail[m]->L = tail[m - 1], tail[m]->R = head;tail[1]->R = tail[2], tail[1]->L = head;for (int i = 1; i <= n; i++){node *last = NULL;for (int j = 1; j <= m; j++){int a;scanf ("%d", &a);if (a){node *newnode = new node(NULL, NULL, tail[j], tail[j]->D, tail[j]->D, i);tail[j]->D = tail[j]->D->U = newnode;tail[j] = newnode;if (!last) newnode->L = newnode->R = newnode;else{newnode->L = last, newnode->R = last->R;last->R = last->R->L = newnode;}last = newnode;}}}if (!DLX())printf ("No solution!\n");return 0;
}

由于奇特的代码风格,代码有点宽,恳请勿喷。。。

转载于:https://www.cnblogs.com/Rhein-E/p/8969773.html

[学习笔记]舞蹈链(Dancing Links)C++实现(指针版)相关推荐

  1. 设计模式学习笔记——责任链(Chain of Responsibility)模式

    设计模式学习笔记--责任链(Chain of Responsibility)模式 @(设计模式)[设计模式, 责任链模式, chain of responsibility] 设计模式学习笔记责任链Ch ...

  2. Timo学习笔记 :Python基础教程(第三版)第四章 当索引行不通时

    第四章 当索引行不通时 Timo学习笔记 :Python基础教程(第三版)第三章 使用字符串 这是word编辑的最后一章笔记,第五章开始将直接用这个模板记录. 本章笔记很少,也很简单.很多方法可以到要 ...

  3. Visio 学习笔记 —— 编辑链接点

    Visio 学习笔记 -- 编辑链接点 添加 选择 删除 参考资料 平时画流程图还没那么明显.但是画时序图时,这个链接点不够用,非常别扭. Shift + Ctrl + 1 激活链接点工具. 添加 C ...

  4. JS学习笔记 原型链和利用原型实现继承

    原型链 原型链是一种关系,实例对象和原型对象之间的关系,关系是通过原型(__proto__)来联系的 实例对象中有__proto__,是对象,叫原型,不是标准的属性,浏览器使用,并且有的游览器不支持 ...

  5. [学习笔记]树链剖分

    基本思想 树链剖分一种对树进行划分的算法,它先通过轻重边剖分将树分为多条链,保证每条边属于且只属于一条链,然后再通过数据结构来维护每一条链. 一些定义 树链:树上的路径. 剖分:把路径分类为重链和轻链 ...

  6. 【OpenCV入门学习笔记1】:Mat对象的指针操作和掩膜操作

    b站:https://www.bilibili.com/video/BV1uW411d7Wf?p=5 下面是我在b站上看视频学习的笔记和操作的示例代码 实例代码 #include<opencv2 ...

  7. keras学习笔记-黑白照片自动着色的神经网络-Beta版

    正文共3894个字,8张图,预计阅读时间11分钟. Alpha版本不能很好地给未经训练的图像着色.接下来,我们将在Beta版本中做到这一点--将上面的将神经网络泛化. 以下是使用Beta版本对测试图像 ...

  8. Hyperledger Fabric学习笔记(四)- fabric单机部署 solo 版

    一.前言: 1.底下的配置文件很重要,一配置错了就有问题了,下面附出这篇文章所需要的配置文件,下载链接:https://download.csdn.net/download/u012561176/15 ...

  9. 蓝牙学习笔记(一)——蓝牙相关概念和术语整理(小白版)

    前言:作为一个新手小白在学习蓝牙的过程中,经常会遇到各种专业术语.得益于强大的搜索引擎,在网上不断搜集资料(搬运工┭┮﹏┭┮)整理以下内容. 单模蓝牙/双模蓝牙   我们常说的单模蓝牙是指只有一种蓝牙 ...

最新文章

  1. oracle的cols,Oracle cols_as_rows 比对数据
  2. python 写入网络视频文件很慢_OpenCV视频写入详解_Python,视频保存0kb问题
  3. 最小生成树-prim算法模板
  4. Netty防止内存泄漏措施
  5. maven实战总结,工作中常见操作
  6. Redis数据库操作指令
  7. 云+X案例展 | 金融类:青云QingCloud助力泰康人寿云计算演进之路
  8. Tips--开源心音数据库整理(我愿称之为史上最全)
  9. ubuntu 安装 mono报错 E: Unable to correct problems, you have held broken packages.
  10. 使用Windows Sysprep来封装系统
  11. 使用python提取英语句子中的英文单词(初学)
  12. 公众号运营,如何快速找到内容定位?
  13. pg之使用pg_upgrade进行大版本升级
  14. 返利机器人源码php,RebateBot:非常棒的返利机器人 基于 golang+vue+android-xposed
  15. android handler的机制和原理_Android完整知识体系路线(菜鸟-资深-大牛必进之路)
  16. ui设计师面试技巧总结
  17. 前端基础-JavaScript
  18. 全网最流氓还擦边的App,被华为封杀了!
  19. Blood闪烁特效制作
  20. 模拟伯努利分布的C语言程序(算法)

热门文章

  1. 通过资源名称得到资源id
  2. LINUX 循环fork()
  3. [转]取本页URL地址的方法是(总结):
  4. ios-deploy out of date (1.9.4 is required). To upgrade with Brew: brew upgrade ios-deploy
  5. spingMVC拦截器 -单个、多个、设想
  6. 计算机网络学习笔记(17. 计算机网络作业一)
  7. 2018年计算机二级知识点,2018年计算机二级考试公共基础知识点:栈及其基本运算...
  8. ocr 图片纠正_2020年10种最佳OCR软件(免费和付费工具) NO.6
  9. div 设置a4大小_CSS设定A4纸张大小
  10. CCF CSP201312-4有趣的数