Dancing Links 学习 AND 代码详解

今天花时间学习了下Dancing Links,其核心思想是降低在搜索中的范围,减少复杂。降低的方法就是将用链式结构构造的图中不需要的点去掉。如果回溯再恢复。

这个方法依赖的数据结构是用数组存储的十字链表L[NN],R[NN],U[NN],D[NN] 左右上下的链接

构造数据结构:

head,cnt,L[NN],R[NN],U[NN],D[NN],H[NN],COL[NN],S[NN],ROW[NN]

head就是头结点,cnt就是在构图时结点的编号,S[NN]是某一列上有多少个元素,COL[NN],ROW[NN]是一个点对应的行列数,H[NN]是在构图中一行的前一个结点编号

主要的数据结构就是这样。接下来看代码

#define head 100
int U[N],D[N],L[N],R[N];
int C[N],H[N],ans[N];//C[N]表示N的列标,H[N]表示N的行标,ans[]用来储存结果
bool dance(int k)
{
     int c = R[head];
     if(c==head) {
           Output();//Output the solution
           return true;
     }
     remove(c);
     for(int i=D[c]; i!=c; i=D[i])
     {
           ans[k] = H[i];
           for(int j=R[i]; j!=i; j=R[j]) remove(C[j]);
           if(dance(k+1)) return true;
           for(int j=L[i]; j!=i; j=L[j]) resume(C[j]);//这里原因和resume中的一样
     }
     resume(c);
     return false;
}

void remove(int c)
{
     L[R[c]] = L[c];
     R[L[c]] = R[c];
     for(int i=D[c]; i!=c; i=D[i]) {
           for(int j=R[i]; j!=i; j=R[j]) {
                U[D[j]] = U[j];
                D[U[j]] = D[j];
           }
     }
}
/*
resume还有另一种写法,就是将L,R的改变放在最后,循环中U,D调换位置。这其实没什么影响因为L,R都是一条语句不影响其他
U,D都是单个操作,而外层循环必须是U的循环,即一个个拿下来在按顺序放回去,不然会错误,自己用纸试一下
*/
void resume(int c)
{
     L[R[c]] = c;
     R[L[c]] = c;
     for(int i=U[c]; i!=c; i=U[i]) {
           for(int j=R[i]; j!=i; j=R[j]) {
                 U[D[j]] = j;
                 D[U[j]] = j;
           }
     }
}

上面知识Dancing Links 的几个基本操作还有涉及构图。下面以POJ 3740为例:

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int NN=600;
int head,cnt,L[NN],R[NN],U[NN],D[NN],H[NN],C[NN],S[NN],ROW[NN];
//上下左右 一行的最右,C结点对应的列,S该列的个数
int N,M;
/*
这里插入一个元素,则对竖直方向改变不大 原本的上一个元素的D,表头的U,当前的D指向表头,U指向表头暂存的
对于水平方向,注意开头的那个元素的L,前一个元素的R,自己的L=前一个,R=开头。开头标号的暂存在前一个的R中
*/
void insert(int i,int j)
{
    C[++cnt]=j;
    S[j]++;
    D[cnt]=j;
    U[cnt]=U[j];
    if(H[i])
	{
		R[cnt]=R[H[i]];
		L[cnt]=H[i];
		R[H[i]]=cnt;
		L[R[cnt]]=cnt;
	}
	else     R[cnt]=L[cnt]=cnt;

	D[U[j]]=cnt;
	U[j]=cnt;
	H[i]=cnt;
}

void remove(int c)//删除第c列上的元素所在的行
{
    R[L[c]]=R[c];
    L[R[c]]=L[c];
    //删除c,c是列指针,删除了c就代表删除了整列,因为递归后不可能访问到c了

    for (int i=D[c]; i!=c; i=D[i]) //c所在列上的元素i
        for (int j=R[i]; j!=i; j=R[j]) //i和j一行的,删掉j
        {
            U[D[j]]=U[j];
            D[U[j]]=D[j];
            S[C[j]]--;//j所在列的元素(‘1’的个数)-1;
        }
}

void resume(int c)//对应的恢复操作
{
     L[R[c]] = c;
     R[L[c]] = c;
     for(int i=U[c]; i!=c; i=U[i]) {
           for(int j=R[i]; j!=i; j=R[j]) {
                 U[D[j]] = j;
                 D[U[j]] = j;
           }
     }
}

bool dance()
{
	int i,j;
    if (R[head]==head)//列指针删完了,任务完成
    {
        puts("Yes, I found it");
        return true;
    }

    int s=0x3fff,c;
    for ( i=R[head]; i!=head; i=R[i]) if (S[i]<s) s=S[i],c=i;
    //找元素最少的列c,一种优化

    remove(c);//删除列c
    for ( i=D[c]; i!=c; i=D[i])
    {
        for ( j=R[i]; j!=i; j=R[j]) remove(C[j]);//删除所有可能的冲突元素
        if (dance()) return true;
        for ( j=L[i]; j!=i; j=L[j]) resume(C[j]);
    }
    resume(c);

    return false;
}
/*
初始化注意,由于有head,则构图时下标都从1开始,每个元素都是自然下标
cnt的赋值不要忘记
*/
void init()
{
	int i,j,k;
	for(i=0;i<=M;i++)
	{
		L[i+1]=i;
		R[i]=i+1;
		U[i]=D[i]=C[i]=i;
		S[i]=0;
	}
	L[0]=M,R[M]=0;
	cnt=M;
	for(i=1;i<=N;i++)
	{
		H[i]=0;
		for(j=1;j<=M;j++)
		{
			scanf("%d",&k);
			if(k)
				insert(i,j);
		}
	}
}
int main()
{
    int c,i;
    head=0;
    while (~scanf("%d%d",&N,&M))
    {
       init();
	   if(!dance())
		   puts("It is impossible");
	}
    return 0;
}

还有一种就是原始的dfs:

#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <math.h>
using namespace std;
int N,M,T;
int maps[20][310];
int rows[310];
int dfs(int n,int cnt)
{
	if(cnt==N) return 1;
	int i,j,k;

	for(i=n;i<M;i++)
	{
		k=cnt;
		for(j=0;j<N;j++)
			if(maps[i][j]==1 && rows[j]==1)
				break;
		if(j<N)
			continue;
		for(j=0;j<N;j++)
			if(maps[i][j]==1)
				rows[j]=1,k++;
		if(dfs(i+1,k))
			return 1;
		for(j=0;j<N;j++)
			if(maps[i][j]==1)
				rows[j]=0;

	}
	return 0;
}
int main(){

    while(scanf("%d%d",&M,&N)!=EOF)
	{
		int i,j;
		memset(rows,0,sizeof(rows));
		memset(maps,0,sizeof(maps));
		for(i=0;i<M;i++)
			for(j=0;j<N;j++)
				scanf("%d",&maps[i][j]);
		if(dfs(0,0))
			printf("Yes, I found it\n");
		else
			printf("It is impossible\n");

    }

    return 0;
}
时间: 2024-08-11 07:49:45

Dancing Links 学习 AND 代码详解的相关文章

PyTorch 计算机视觉的迁移学习教程代码详解 (TRANSFER LEARNING FOR COMPUTER VISION TUTORIAL )

PyTorch 原文: https://pytorch.org/tutorials/beginner/transfer_learning_tutorial.html 参考文章: https://www.cnblogs.com/king-lps/p/8665344.html https://blog.csdn.net/shaopeng568/article/details/95205345 https://blog.csdn.net/yuyangyg/article/details/8001857

tiny_cnn代码详解(3)——层间继承关系

在上一篇博文中我们顺利将tiny_cnn的程序调试通过,在这篇博文中我们尝试从整体角度给出对tiny_cnn这个深度学习框架的解读,重点论述一下其各个层直接类封装的继承关系. 一.卷积神经网络快速入门 tiny_cnn作为卷积神经网络的一种实现形式,在探讨其框架结构之前,首先需要简要介绍一些卷积神经网络相关的知识.首先,给出经典卷积神经网络的网络结构: 这个是经典的LeNet-5的网络结构图,五层网络.最早用于支票上的手写数字识别,也是最早的商业化的深度学习模型.从上图中可以看出,卷积神经网络主

Github-jcjohnson/torch-rnn代码详解

Github-jcjohnson/torch-rnn代码详解 [email protected] http://www.cnblogs.com/swje/ 作者:Zhouwan  2016-3-18 声明: 1)本文仅供学术交流,非商用.所以每一部分具体的参考资料并没有详细对应.如果某部分不小心侵犯了大家的利益,还望海涵,并联系博主删除. 2)本人才疏学浅,整理总结的时候难免出错,还望各位前辈不吝指正,谢谢. 请联系:[email protected] 或[email protected] 本研

开胃小菜——impress.js代码详解

README 友情提醒,下面有大量代码,由于网页上代码显示都是同一个颜色,所以推荐大家复制到自己的代码编辑器中看. 今天闲来无事,研究了一番impress.js的源码.由于之前研究过jQuery,看impress.js并没有遇到太大的阻碍,读代码用了一个小时,写这篇文章用了近三个小时,果然写文章比读代码费劲多了. 个人感觉impress.js的代码量(算上注释一共不到1000行)和难度(没有jQuery的各种black magic= =)都非常适合新手学习,所以写一个总结,帮助大家理解源码. 考

JBPM学习(六):详解流程图

概念: 流程图的组成: a. 活动 Activity / 节点 Node b. 流转 Transition / 连线(单向箭头) c. 事件 1.流转(Transition) a) 一般情况一个活动中可以指定一个或多个Transition i. 开始活动(Start)中只能有一个Transition. ii. 结束活动(End)中没有Transition. iii. 其他活动中有一条或多条Transition b) 如果Transition只有一个,则可以不指定名称(名称是null):如果有多个

iOS学习--UIScrollView 原理详解

iOS学习--UIScrollView 原理详解 http://blog.csdn.net/yanfangjin/article/details/7898189 ScrollView UIScrollView UIScrollView为了显示多于一个屏幕的内容或者超过你能放在内存中的内容. Scroll View为你处理缩小放大手势,UIScrollView实现了这些手势,并且替你处理对于它们的探测和回应.其中需要注意的子类是UITableView以及UITextView(用来显示大量的文字).

后缀数组学习笔记【详解|图】

后缀数组学习笔记[详解] 老天,一个后缀数组不知道看了多少天,最后终于还是看懂了啊! 最关键的就是一会儿下标表示排名,一会用数值表示排名绕死人了. 我不知道手跑了多少次才明白过来.其实我也建议初学者手跑几遍,但是一定要注意数组的意义,否则就是无用功. 数组含义: s[ ]:输入的字符串,预处理的时候会在末尾加上一个0 sa[ ]:它的下标就是后缀排名 x[ ] = t[ ]:用来保存第一关键字排名,注意!它的数值是排名.初始时恰好是字符串的ASCII码.字典序嘛! y[ ] = t2[ ]:它的

算法导论学习---红黑树详解之插入(C语言实现)

前面我们学习二叉搜索树的时候发现在一些情况下其高度不是很均匀,甚至有时候会退化成一条长链,所以我们引用一些"平衡"的二叉搜索树.红黑树就是一种"平衡"的二叉搜索树,它通过在每个结点附加颜色位和路径上的一些约束条件可以保证在最坏的情况下基本动态集合操作的时间复杂度为O(nlgn).下面会总结红黑树的性质,然后分析红黑树的插入操作,并给出一份完整代码. 先给出红黑树的结点定义: #define RED 1 #define BLACK 0 ///红黑树结点定义,与普通的二

Swift学习——Swift基础详解(一)

注:由于基础部分在Swift Tour 中已经大体的说明了,所以在详解中不会达到100%的原文释义 Constants and Variables  常量和变量 常量和变量都需要声明名称和类型(作为程序员,这些基础也就不说了),常量一次赋值不能改变,变量的值可以改变 Declaring Constants and Variables   声明常量和变量 常量和变量在使用之前必须要声明,使用let关键字定义常量,var关键字定义变量 下面的例子可以用来定义用户登录的时候最大的尝试次数: let m