Use Dancing Links(DLX) Slove Exact Cover Problems
以前在AMS上看到一篇关于Dancing Links的介绍(http://www.ams.org/samplings/feature-column/fcarc-kanoodle),可是一直没有自己实现过。端午放假,又恰好看比赛,于是又熬夜写代码,终于实现了一次DLX.网上关于DLX算法大都以数独为背景,可是AMS上的那篇文章先讲的是一个叫做Kanoodle的玩具。Kanoodle游戏的目的是把12块形状各不相同的小东西放入一个5*11的矩形框内.没有空隙,也没有覆盖,如图:

一种合法的放置为;

这其实可以转化为一个Exect Cover问题,有两个限制条件:
1: 5*11的矩形框没一个框必须被覆盖
2: 每个小块必须有放在5*11矩形框内某处 这个问题转化成Dancing Links后有5*11+12列,5*11代表5*11的矩形格子是否有被覆盖,后面的12表示12个小块有无放置进去.
用Dlx解决问题到后面都是操作一个庞大的,稀疏的0/1矩阵.只要能生成这个矩阵,所有的问题就简单了.对于Kanoodle,这个矩阵的生成思路是这样;任意一个块以任意的方式放到5*11的矩形框内有两个效果:
A,5*11的矩形框内某些位置被覆盖.
B,这一块已经被放置进5*11矩形框.
Kanoodle有12个小块如图:

由于存在平移,旋转,对称变换,因此每个小块都对应着不同的方法.
比如A: A由于没有对称性,所以存在8个变换后的A,分别是A,A旋转90度,A旋转180度,A旋转270度,以及A做镜像变换后的这4种变换(需要注意的是不存在旋转45度这样的变换,旋转45度后是没法放入5*11格子的).因此A最终可以变换成8块(
4个2*3的块,4个3*2的块),对于一个2*3的块的不同放法有(11-2+1)*(5-3+1)=30种,一个3*2的块的不同放法有
(11-3+1)*(5-2+1)=36
因此所有A的不同放法有4*(30+36)=264种,即A在这个矩阵中占据264行。其它块的不同放法可以同样计算出,只是有点块由于对称性只存在4种,2种,1种变换后的不同块,经计算,所有块的所有不同放法共有1789种,因此最后的矩阵有1789行(与文章里面的2222行有出入,不知道是不是少计算了哪些情况还是文章有误).
至于合法的放置到底有多少种,目前还不得而知,但目前程序已经找到的解已经上万,但这其中有些解是同构的,所有不同构的解数恐怕得借助群论的知识,考虑对称下的等价.以下是找到的一些解

代码如下(代码会在D盘根目录下生成一个BitMap.txt文件,此文件是一个0/1矩阵)
/******************************************************************
Aurthor: XiangChengMing@centerm.com.cn
Usage : 使用DLX解Kanoodle
*******************************************************************/
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "Time.h"
typedef struct
{
unsigned char wide;
unsigned char length;
unsigned char BitMap[4][4];
}Piece;
typedef struct Node
{
int r,c;
struct Node *right;
struct Node *left;
struct Node *up;
struct Node *Down;
}Node;
#define BOOL char
#define TRUE 1
#define FALSE 0
Piece Array[12]=
{
/* Piece A */
{
2,
3,
{
0,1,0,0,
0,1,0,0,
1,1,0,0,
0,0,0,0,
}
},
/* Piece B */
{
2,
3,
{
0,1,0,0,
1,1,0,0,
1,1,0,0,
0,0,0,0,
}
},
/* Piece C */
{
2,
4,
{
0,1,0,0,
0,1,0,0,
0,1,0,0,
1,1,0,0,
}
},
/* Piece D */
{
2,
4,
{
0,1,0,0,
0,1,0,0,
1,1,0,0,
0,1,0,0,
}
},
/* Piece E */
{
2,
4,
{
0,1,0,0,
0,1,0,0,
1,1,0,0,
1,0,0,0,
}
},
/* Piece F */
{
2,
2,
{
1,0,0,0,
1,1,0,0,
0,0,0,0,
0,0,0,0,
}
},
/* Piece G */
{
3,
3,
{
0,0,1,0,
0,0,1,0,
1,1,1,0,
0,0,0,0,
}
},
/* Piece H */
{
3,
3,
{
0,0,1,0,
0,1,1,0,
1,1,0,0,
0,0,0,0,
}
},
/* Piece I */
{
3,
2,
{
1,0,1,0,
1,1,1,0,
0,0,0,0,
0,0,0,0,
}
},
/* Piece J */
{
1,
4,
{
1,0,0,0,
1,0,0,0,
1,0,0,0,
1,0,0,0,
}
},
/* Piece K */
{
2,
2,
{
1,1,0,0,
1,1,0,0,
0,0,0,0,
0,0,0,0,
}
},
/* Piece L */
{
3,
3,
{
0,1,0,0,
1,1,1,0,
0,1,0,0,
0,0,0,0,
}
},
};
int nPiece;
Piece All[96];
char Table[96];
int ColoumCounter[68];
int columCounter=0;
int Resultcounter=0;
Node *Coloum;
Node *Row;
Node Head;
BOOL flag[1789];
BOOL InitDLX()
{
int i;
nPiece = 0;
Coloum = (Node*)malloc( 67*sizeof(Node) );
Row = (Node*)malloc( 1789*sizeof(Node) );
if(NULL == Coloum || NULL == Row) return FALSE;
memset(All ,0x00 ,sizeof(All));
memset(flag ,0x00 ,sizeof(flag));
//刚开始时只有一个Head
Head.Down = &Head;
Head.up = &Head;
Head.right = &Head;
Head.left = &Head;
for( i = 0 ;i < 67 ;i ++)
{
Coloum[i].c = i;//列标志
Coloum[i].r = 0;
ColoumCounter[i] = 0;//列节点计数
Coloum[i].Down = &Coloum[i];
Coloum[i].up = &Coloum[i];
//栈链插入
Coloum[i].left = &Head;
Coloum[i].right = Coloum[i].left->right;
Coloum[i].left->right = &Coloum[i];
Coloum[i].right->left = &Coloum[i];
}
for( i = 0 ;i < 1789 ;i ++ )
{
Row[i].r = i;
Row[i].c = 67;
Row[i].right = &Row[i];
Row[i].left = &Row[i];
Row[i].up = &Head;
Row[i].Down = Row[i].up->Down;
Row[i].up->Down = &Row[i];
Row[i].Down->up = &Row[i];
}
return TRUE;
}
Node * GetNode()
{
Node *p;
p = (Node*)malloc( sizeof(Node) );
return p;
}
//根据行列插入一个节点
BOOL insertrc(int c,int r)
{
Node *p = GetNode();
if(p == NULL)
{
return FALSE;
}
p->c = c;
p->r = r;
ColoumCounter[c] ++;//列节点数递增
p->left = &Row[/**/r/**/];//Bug,QQ空间插入代码有问题
p->right = p->left->right;
p->left->right = p;
p->right->left = p;
p->up = &Coloum[c];
p->Down = p->up->Down;
p->up->Down = p;
p->Down->up = p;
return TRUE;
}
//删除整行
void Remove_C(int c)
{
Node *p;
Node *Tr;
Node *Tc;
p = &Coloum[c];
p->right->left = p->left;
p->left->right = p->right;
for( Tr = p->Down ;Tr != p ;Tr = Tr->Down)
{
for(Tc = Tr->left ;Tc != Tr ;Tc = Tc->left)
{
ColoumCounter[Tc->c]--;
Tc->up->Down = Tc->Down;
Tc->Down->up = Tc->up;
}
Tr->left->right = Tr->right;
Tr->right->left = Tr->left;
}
}
//恢复整行
void Resume_C(int c)
{
Node *p;
Node *Tr;
Node *Tc;
p = &Coloum[c];
for( Tr = p->Down ;Tr != p ;Tr = Tr->Down)
{
Tr->left->right = Tr;
Tr->right->left = Tr;
for(Tc = Tr->left ;Tc != Tr ;Tc = Tc->left)
{
ColoumCounter[Tc->c] ++;
Tc->Down->up = Tc;
Tc->up->Down = Tc;
}
}
p->left->right = p;
p->right->left = p;
}
void Print_Piece( Piece obj)
{
int i,j;
printf ("\n%d %d \n",obj.wide,obj.length);
for (i=0;i<4;i++)
{
for( j = 0 ;j < 4 ;j ++)
printf("%d ",obj.BitMap[i][j]);
printf("\n");
}
printf("\n");
}
/*把输入的图形平移到左上对齐 */
void Normalize(Piece *input,Piece *Output)
{
int i ,j ,sx ,sy;
memset(Output ,0x00 ,sizeof(Piece) );
Output->wide = input->wide;
Output->length = input->length;
for( i = 0 ;i < 4 ;i ++)
for(j = 0 ;j < 4 ;j ++)
{
if(input->BitMap[i][j] != 0)
{
sx = i;
i = j = 4;
}
}
for( i = 0 ;i < 4 ;i ++)
for( j = 0 ;j < 4 ;j ++)
{
if(input->BitMap[j][i] != 0 )
{
sy = i;
i = j = 4;
}
}
for(i = 0 ;i < input->length ; i ++)
for( j = 0 ;j < input->wide ;j ++)
{
Output->BitMap[i][j] = input->BitMap[i+sx][j+sy];
}
}
/*左旋转90度变换 */
void Rotate_R90(Piece *input,Piece *Output)
{
int i ,j;
Piece Temp;
Temp.length = input->wide;
Temp.wide = input->length;
for(j = 0 ;j < 4 ;j ++)
for( i = 0 ;i < 4 ;i ++)
Temp.BitMap[3-i][j] = input->BitMap[j][i];
Normalize(&Temp,Output);
}
/*对称变换 */
void mirror(Piece *input,Piece *output)
{
int i ,j ;
memset(output ,0x00 ,sizeof(Piece) );
output->length = input->length;
output->wide = input->wide;
for( i = 0 ;i < input->length ;i ++)
for( j = 0 ;j < input->wide ;j ++)
{
output->BitMap[i][input->wide-j-1] = input->BitMap[i][j];
}
}
//判断两个图像是否是一个
char IsSame(Piece a,Piece b)
{
int i ,j;
if((a.length != b.length) || (a.wide != b.wide))
return 0;
for( i = 0 ;i < 4 ;i ++)
for( j = 0 ;j < 4 ;j ++ )
{
if( a.BitMap[i][j] != b.BitMap[i][j]) return 0;
}
return 1;
}
/*预处理每个块*/
int PreProcess(Piece obj,int xx)
{
Piece Buf[8];
Piece Temp ,Rotated;
int i ,k ,counter = 0;
int j = 0;
mirror( &obj , &Rotated );
Temp = obj;
Buf[counter++] = Temp;
j = ( 6 - Temp.length ) * ( 12 - Temp.wide );
/* 不动,转90.转180,转270*/
for( i = 0 ;i < 4 ;i++ )
{
Rotate_R90( &Temp ,&Temp );
for( k = 0 ;k < counter ;k ++ )
{
if( 1 == IsSame(Buf[k] ,Temp) ) break;
}
if( k == counter )
{
Buf[counter++] = Temp;
}
}
Temp = Rotated;
for( i = 0 ;i < 4 ;i ++)
{
Rotate_R90( &Temp ,&Temp );
for( k = 0 ;k < counter ;k ++)
{
if( 1 == IsSame( Buf[k] ,Temp) ) break;
}
if( k == counter)
{
Buf[counter++] = Temp;
}
}
for( i = 0 ;i < counter ;)
{
All[nPiece] = Buf[i++];
Table[nPiece++] = xx;
}
return counter;
}
//把每块的各种可能翻译成行值,并插入到矩阵中
void PutOnePiece(int index,int px,int py,char rowBuf[67])
{
int i ,j ;
memset( rowBuf ,'0' ,67 );
rowBuf[55+Table[index]] = '1';
for(i = 0 ;i < All[index].length ;i ++ )
for ( j= 0 ;j < All[index].wide ;j++ )
{
if(All[index].BitMap[i][j] == 1)
{
rowBuf[11*(py+i)+px+j] = '1';
insertrc(11*(py+i)+px+j,columCounter);
}
}
insertrc(55+Table[index],columCounter);
}
BOOL Kanoodle(int step)
{
int i ,j ,k ,h = 0;
Node * Tp;
Node * tt;
FILE * fpBitmap;
char szBuf[69];
char BitMap[67];
if (Head.right == &Head )//链表已空,已经得到解
{
Resultcounter++;
fpBitmap=fopen("D:\\BitMap.text","r+");
printf("\n第%d组解所选取的各行为:\n",Resultcounter);
for(i=0;i<1789;i++)
{
if(flag[i]!=0)
{
printf("%d ",i);
fseek(fpBitmap , 69*i , SEEK_SET);
fread( BitMap ,67 ,1 ,fpBitmap);
for(j=0;j<12;j++)
{
if(BitMap[55+j] == '1') break;
}
for( k = 0 ;k < 55 ;k++ ) if(BitMap[k] == '1') szBuf[k] = 'A' + j;
}
}
printf("\n第%d组解的图形为\n",Resultcounter);
for( j = 0 ;j < 55 ;j++ )
{
if (j % 11 == 0) printf("\n ");
printf("%c ",szBuf[j]);
}
fclose(fpBitmap);
return FALSE;//继续寻找别的解
}
//寻找一节点最少的列,这样能加快搜索
Tp = Head.right;
for( tt = Tp->right ;tt != &Head ;tt = tt->right)
{
if(ColoumCounter[tt->c] < ColoumCounter[Tp->c])
{
Tp = tt;
}
if( ColoumCounter[Tp->c] <= 1 )break;
}
if( ColoumCounter[Tp->c] < 1 ) return FALSE;//此列已经不存在入口,不可能存在这样的解,直接返回FALSE
i = Tp->c;
Remove_C(i);
//尝试从此列的各个入口选择不同的行,看能否构成解
for( Tp = Coloum[i].Down ;Tp != &Coloum[i] ;Tp = Tp->Down)
{
Tp->right->left = Tp;
flag[Tp->r] = 1;
for( tt = Tp->left ;tt != Tp ; t t= tt->left )
{
if( tt->c == 67 )
{
continue;
}
Remove_C( tt->c );
}
Tp->right->left = Tp->left;
if(Kanoodle(step+1) == TRUE)
{
return TRUE;
}
else
{
//回溯
Tp->right->left = Tp;
flag[Tp->r] = 0;
for( tt = Tp->left ;tt != Tp ;tt = tt->left)
{
if(tt->c == 67) continue;
Resume_C( tt->c );
}
Tp->right->left = Tp->left;
}
Tp->left->right = Tp->right;
}
Resume_C(i);
return FALSE;
}
int main()
{
int i ,j ,k ,h = 0;
FILE *fpBitmap;
char szBuf[69];
time_t tval;
struct tm *now;
#ifdef Debug_Print_Row
Node *p,*pp;
#endif
tval = time(NULL);
now = localtime(&tval);
printf("\n\n开始寻找解:\n"
" %d/%02d/%02d %d:%02d:%02d\n\n",
now->tm_mon+1, now->tm_mday, now->tm_year,
now->tm_hour, now->tm_min, now->tm_sec);
InitDLX();
fpBitmap = fopen("D:\\BitMap.text","wr+");
szBuf[67] = '\n';
szBuf[68] = 0x00;
for( i = 0 ; i < 12 ; i++)
{
PreProcess( Array[i] , i );
}
for(i=0;i<nPiece;i++)
for(j=0;j<=11-All[i].wide;j++)
for(k=0;k<=5-All[i].length;k++)
{
PutOnePiece( i , j ,k ,szBuf );
columCounter ++;
fwrite( &szBuf ,68 ,1 ,fpBitmap );
}
fclose(fpBitmap);
printf("矩阵初始化完成,总共 %d 行 \n\n",columCounter);
#ifdef Debug_Print_Row
for( p = &Row[0] ; p != &Head ; p = p->up , h ++)
{
for( pp = p->left ; pp != p ;pp = pp->left )
{
printf("%02d ",pp->c);
}
printf("\n");
}
#endif
Kanoodle(0);
printf("Find \n%d Result:",Resultcounter);
tval = time(NULL);
now = localtime(&tval);
printf( "\n结束时间:\n"
"%d/%02d/%02d %d:%02d:%02d\n\n",
now->tm_mon+1, now->tm_mday, now->tm_year,
now->tm_hour, now->tm_min, now->tm_sec);
return 0;
}
浙公网安备 33010602011771号