舞蹈链java实现_浅谈舞蹈链

本文通过实例介绍了舞蹈链(Dancing Links)如何用于解决精确覆盖问题,包括问题定义、数据结构(双向链表和双向十字链表)以及Java代码实现。通过舞蹈链数据结构,可以在接近线性的时间复杂度内高效地处理这类问题。
摘要由CSDN通过智能技术生成

舞蹈链解决精确覆盖问题

一、问题引入:

有n 个人, 每个人有一些想吃的菜. 只有你给这个人所有他想吃的菜,他才会吃.

可是你只有m 种菜, 每样一份.你必需把菜卖完. 问最多能满足多少人.

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

假设有5种菜,4个人 ,1表示他喜欢吃这种菜。

(1)0 1 0 1 0

(2)1 0 0 0 0

(3)1 1 1 0 0

(4)0 0 1 0 1

可见最多满足第1 、2 、4。

答案得出的过程。

Step1:我们假设先满足第一人,那么第3人是不能选的(因为每种菜只有一份,3不能和1抢第二种菜)。

Step2: 接着选择第2个人,很完美,两人都可以满足。

Step3:接着选第4个人(第三个人在Step1就淘汰了。)也可以满足他。

这个例子太好了,换一下。

(1)0 0 1 0 1

(2)1 1 1 0 1

(3)0 0 1 1 1

(4)0 0 1 1 1

Step1:发现要覆盖第一列,必须满足第二个人。由于1.3.4人和2都喜欢吃第3种菜,所以都不能满足。

所以只能满足第二个人。但是这时菜不能完全卖完,此问题无解。

总结一下求解的过程:

(1)选择任意一行。

(2)删除选择这一行后不能选择的其他行。

(3)继续选择

(4)若最后没有可选择的行,但是没有完全覆盖,回到第(1)步,修改之前任意选择的行重复步骤。

到这挺好理解的...

二、

发现这过程需要存储矩阵和回溯的过程。那么怎样做比较高效呢。

舞蹈链是一种数据结构,缓存和回溯中效率惊人。不需要额外空间,接近于线性的时间,指针在数据之间

跳跃着,像舞蹈一样所以称之为舞蹈链。(dancing links).

舞蹈链用的数据结构是十字链表。

我们先从双向链表过渡到十字链表吧。

(1)双向链表

A1 A2 A3,

A1.right=A2,A2.left=A1,A3.right.right=A1.

在很多实际运用中,把双向链的首尾相连,构成循环双向链.

删除A2时,A1.right=A3,A3.left=A1,注意A2只是从双向链表中删除了,但是A2的left和right的信息并没有变。

由于不需要开空间,时间也是挺快的。

(2)接下来是双向十字链表。

每个元素有四个指针,left,right,up,down.

7b86386defa8ecfe49f6f25fe5be0abd.png

注意链首和链尾是双向连接的,一定要注意双向。

列首有我们虚构的点,在第0行,这是为了方便搜索,以后代码呈现。

三、用法。

删除和恢复元素。(先了解后看代码)

规则:

沿列删除时删除左右, 保留上下.

沿行删除时删除上下, 保留左右.

恢复时依然沿之前的方向, 根据自己的信息把自己插进去

四、

那么开始的问题引入,我们怎么用舞蹈链来解决呢。

........................................................假装你思考过了的样子.........................orz

首先每一行的行首表示每个人,列表示每个人爱吃的菜,然后求精确覆盖就可以啦。

五、code

当然很恶心。

intl[maxn],r[maxn],u[maxn],d[maxn],tn,ren[maxn],cai[maxn];void shanchu(intx) {for(int p=r[x]; p!=x; p=r[p]) {for(int q=u[p]; q!=p; q=u[q]) {for(int s=r[q]; s!=q; s=r[s]) {if(d[x]!=s) {

d[u[s]]=d[s];

u[d[s]]=u[s];

}

}

}

}

}void huifu(intx) {for(int p=l[x]; p!=x; p=l[p]) {for(int q=d[p]; q!=p; q=d[q]) {for(int s=l[q]; s!=q; s=l[s]) {

u[d[s]]=d[u[s]]=s;

}

}

}

}intmain() {

memset(cai,0,sizeof(cai));

scanf("%d%d",&n,&m);

ren[0]=tn=1;

l[1]=r[1]=1;

d[1]=u[1]=1;for(int i = 1; i <= n; i++) {

ren[i]=++tn;

l[tn]=r[tn]=tn;//左右方向建表

d[ren[i-1]]=tn;//上下方向

u[tn]=ren[i-1];

d[tn]=1;//指向列首。

u[1]=tn//列首指向列尾。

int totcai,caii,lastcai=tn;

scanf("%d",&totcai);for(int j = 0; j < totcai; ++j) {

scanf("%d", &caii);++tn;

r[last=tn;

l[tn]=lastcai;

r[tn]=ren[i];

l[ren[i]]=tn;

lastcai=tn;if(!cai[caii]) { //cai数组指的是caii最后一次出现的位置编号。

cai[caii]=u[tn]=d[tn]=tn;

}else{

u[tn]=cai[caii];

d[tn]=d[cai[caii]];

u[d[tn]]=tn;

d[u[tn]]=tn;

cai[caii]=tn;

}

}

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值