码迷,mamicode.com
首页 > 编程语言 > 详细

舞蹈链(Dancing Links)C++实现(指针版)

时间:2018-04-28 23:51:58      阅读:1494      评论:0      收藏:0      [点我收藏+]

标签:tool   strong   来源   定义   ast   \n   tps   print   rip   

·精确覆盖问题

精确覆盖问题的定义:给定一个由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。

恢复

         恢复就相对简单一些了。

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

        第二步:恢复该列。

        上代码:

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;
}
由于奇特的代码风格,代码有点宽,恳请勿喷。。。

舞蹈链(Dancing Links)C++实现(指针版)

标签:tool   strong   来源   定义   ast   \n   tps   print   rip   

原文地址:https://www.cnblogs.com/Rhein-E/p/8969773.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!