首页 > 代码库 > Dancing Links 学习 AND 代码详解

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;
}


Dancing Links 学习 AND 代码详解