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

 

 

posted on 2011-04-24 13:16  TinyCoder  阅读(121)  评论(0)    收藏  举报