用舞蹈链解数独问题

转载自:http://fayaa.com/code/view/1738/


把debug文件夹中的exe文件放在指定路径,这里放在F盘

然后打开cmd文件输入

F:\53.exe 030000000010200060000600400004000800007000000000001003000460005000800000050000001

输出结果


数独测试案例
030000000010200060000600400004000800007000000000001003000460005000800000050000001
436198572719254368285637419194372856327586194568941723971463285642815937853729641


001000400000000002000045000020060310000001020000097680000006100000009000000000205
351972468746183952892645731427568319968431527135297684579826143214359876683714295
351972468946183752872645931429568317768431529135297684597826143214359876683714295

//from: http://code.google.com/p/klsudoku/source/checkout
//半瓶墨水修改于 2009 Sept 18
//References
//  http://en.wikipedia.org/wiki/Knuth's_Algorithm_X
//  http://en.wikipedia.org/wiki/Dancing_Links
//  http://en.wikipedia.org/wiki/Exact_cover#Sudoku

#include<iostream>
using namespace std;
#define RR 729   //81*9 创建出9个舞蹈链,分别代表填入的数字?
#define CC 324   //81*4 约束条件?
#define INF 1000000000
int mem1[81+1];
int mem2[81+1];
int *mem = mem1;
char ch[81+1];
int cnt[CC+1];
int scnt=0; //solution count

struct node {
    int r,c;
    node *up;
    node *down;
    node *left;
    node *right;
}head,all[RR*CC+99],row[RR],col[CC];
int all_t;
 
//用链表解释就是一直插在第一个结点,以前的结点右推。
inline void link(int r,int c) {//第r行,第c列
    cnt[c]++;//第c列的结点增加了一个
    node *t=&all[all_t++];//将指针指向下一个,就像线性表添加元素一样
    t->r=r;//t的行数等于r
    t->c=c;//t的列数等于c

	t->left=&row[r];//t的左边等于第r行结点
    t->right=row[r].right;//t的右边等于第r行结点的右边
    t->left->right=t;     //t的左边的右边等于t
    t->right->left=t;     //t的右边的左边等于t

    t->up=&col[c];//t的上边等于第c列结点
    t->down=col[c].down;//t的下边等于第c列下边
    t->up->down=t;//t的上边的下边等于t
    t->down->up=t;//t的下边的上边等于t
}

inline void remove(int c) {//删除这列的结点和结点所在行的结点
    node *t,*tt;
	//删除列结点
    col[c].right->left=col[c].left;//该列结点的右边的左边等于该列结点的左边
    col[c].left->right=col[c].right;//该列结点的左边的右边等于该列结点的右边

    for(t=col[c].down;t!=&col[c];t=t->down) {//访问该列的所有结点 直到回到列结点
        for(tt=t->right;tt!=t;tt=tt->right) {//访问该列所有结点所在的每一行
            cnt[tt->c]--;//该列的结点减少一个

			//删除该结点所在行中的一个结点
            tt->up->down=tt->down;//该结点的上边的下边等于该结点的下边
            tt->down->up=tt->up;//该结点的下边的上边等于该结点的上边
        }

		//删除该结点
        t->left->right=t->right;//t的左边的右边等于t的右边
        t->right->left=t->left;//t的右边的左边等于t的左边
    }
}

inline void resume(int c) {
    node *t,*tt;
	//遍历该列结点
    for(t=col[c].down;t!=&col[c];t=t->down) {        
        t->right->left=t;//恢复t结点
        t->left->right=t;//恢复t结点
        for(tt=t->left;tt!=t;tt=tt->left) {//一直访问左边,直到回到t
            cnt[tt->c]++;
            tt->down->up=tt;
            tt->up->down=tt;
        }
    }    
    col[c].left->right=&col[c];
    col[c].right->left=&col[c];
}

void print_solution(int*m){
  int ans[81];
  for(int i=1;i<=81;i++) {
    int t=m[i]/9%81;
    int val=m[i]%9+1;
    ans[t]=val;
  }
  for(i=0;i<81;i++)
    printf("%d",ans[i]);
  printf("\n");
}

void solve(int k)
{
    if(head.right==&head) {//got one solution
      if (!scnt) {//如果是第一个解决方案的话
        memcpy(mem2, mem1, sizeof(mem1));
        mem = mem2;
      }
      scnt++;
      return;//这里第一种解决方案得到后,返回继续 选行 来看有没有第二种解决方案
    }
    node*t,*tt;
    int min=INF,tc;//min=1000000000;
	//从头结点开始一直向右 直到回到头结点
	//挑选结点数量最小的那一行,如果数量小于等于1直接用这行
	cout<<k<<endl;
    for(t=head.right;t!=&head;t=t->right) {
        if(cnt[t->c]<min) {
            min=cnt[t->c];
            tc=t->c;
            if(min<=1)break;
        }
    }
	//min==0的时候会把列删除然后再把列恢复然后返回,说明之前选错了行导致出现了结点为0的列,重新往下选择一行。
    remove(tc);//移除这一列
	//扫描这一列 直到 回到列结点
    for(t=col[tc].down;t!=&col[tc];t=t->down) {
        mem[k]=t->r;//mem[k]存储t的行数,最后可以通过行数来推断数独的几行几列填入了哪个数字   
	//	cout<<&(t->left)<<' '<<t->left->r<<' '<<t->left->c<<' '<<&(t->left->right)<<' '<<t->left->right->r<<' '<<t->left->right->c<<' '<<&t<<' '<<t->r<<' '<<t->c<<endl;
	//	cout<<t->left->right->right->r<<' '<<t->left->right->right->c<<endl;

		//如果没有这一步的话,在下面for循环的过程中会陷入死循环
        t->left->right=t;//经检查这两个指针所指向的地址不同


		//开始访问t的右边 直到回到t。但是由于t在remove(tc)的过程中左右被跳过,所以tt!=t可能会一直成立,所以需要上一步来保证能回到t
        for(tt=t->right;tt!=t;tt=tt->right) {
            remove(tt->c);//移除该行中所有带结点的列
        }
	//		cout<<&(t->left)<<' '<<t->left->r<<' '<<t->right->c<<' '<<&(t->left->right)<<' '<<t->left->right->r<<' '<<t->left->right->c<<' '<<&t<<' '<<t->r<<' '<<t->c<<endl;
	//		cout<<&(t->right)<<' '<<t->right->r<<' '<<t->right->c<<endl;

		//等到该行的所有结点都删除以后,把t结点彻底地删除
        t->left->right=t->right;//t左边的右边等于t的右边   删除t


        solve(k+1);//给下一个找行
        if (scnt >= 2)//如果解决方案大于等于两个
          return;

		//同上,避免死循环
        t->right->left=t;
		//恢复所有被删除的列
        for(tt=t->left;tt!=t;tt=tt->left) {
            resume(tt->c);
        }
		//恢复t结点
        t->right->left=t->left;
    }
	//恢复tc列
    resume(tc);
    return;//一旦跑出来了说明之前选错了行,且如果一直回溯到一开始然后没有更多的行可以选择且scnt为0就说明没有解决方案
}
 
//int mem1[]  --->   0-81
//int mem2[]  --->   0-81
//int *mem    --->   mem1[]
//char ch     --->   0-81
//int cnt[]   --->   0-324       用于记录0-324列,这一列有多少个结点
//int scnt    --->   0
//node head   --->   head node
//node all[]  --->   0-236294    构建729*324+99列的舞蹈链
//node row[]  --->   0-728       构建729列的舞蹈链,用于1-9的填入,每个数字用81列来表示
//node col[]  --->   0-323       构建324列的舞蹈链,用于满足4个约束条件
//int RR      --->   729
//int CC      --->   324
int main(int argc, char**argv) {
  if (argc != 2) return 1;//第一个元素是程序所在路径 第二个元素是数独,用0表示空缺位置,81个数字构成第二个元素
  const char *p = argv[1];//将指针指向第二个元素
  size_t len = strlen(p);//获取p指针指向的数组长度
  if (len!=81) return 1;//如果不是81个数字就不是数独
  int i;
  for (i=0;i<81;i++) {//遍历这81个字母
    if ((*p) == '0')//如果访问到0
		ch[i] = '.';//用.号代替
    else
        ch[i] = *p;//访问到数字
    p++;//访问81个字母
  }
  ch[81] = '\0';//第81位赋终止符

  if(ch[0]=='e')return 1;//如果第一位是字母'e'就结束程序
  all_t=1;//----------------------------------------------------------all_t赋值为1
  memset(cnt,0,sizeof(cnt));//把cnt整个赋值为0
  head.left=&head;//头结点的左边是头结点
  head.right=&head;//头结点的右边是头结点
  head.up=&head;//头结点的上面是头结点
  head.down=&head;//头结点的下面是头结点
  head.r=RR;//-------------------------------------------------------行数等于729
  head.c=CC;//-------------------------------------------------------列数等于324

  //for  0-323
  for(i=0;i<CC;i++) {
    col[i].c=i;//----------------------------------------------------324列舞蹈链 用0-323赋值给c
    col[i].r=RR;//---------------------------------------------------把 729 赋给 r
    col[i].up=&col[i];//它的上面等于自己
    col[i].down=&col[i];//它的下面等于自己

	//头结点右边列的编号从左到右是323到0

    col[i].left=&head;//它的左边等于头结点
    col[i].right=head.right;//它的右边等于头结点的右边

    col[i].left->right=&col[i];//它的左边的右边等于自己
    col[i].right->left=&col[i];//它的右边的左边等于自己
  }

  //for 0-728
  for(i=0;i<RR;i++) {
    row[i].r=i;//----------------------------------------------------729行舞蹈链,行数等于i
    row[i].c=CC;//---------------------------------------------------列数等于324
    row[i].left=&row[i];//它的左边等于自己
    row[i].right=&row[i];//它的右边等于自己

	//头结点下边行的编号从上到下是728到0

    row[i].up=&head;//它的上边等于头结点
    row[i].down=head.down;//它的下边等于头结点的下边
    row[i].up->down=&row[i];//它的上边的下边等于自己
    row[i].down->up=&row[i];//它的下边的上边等于自己
  }

  //for 0-728
  //访问所有行,数独舞蹈链中的第i行 表示 数独中的第r行第c列中填入数字val
  for(i=0;i<RR;i++) {
    int r=i/9/9%9;//0-80  r为0   81-161 r为1 …… 648-728 r为8    表示数独中的行    映射:舞蹈链行->数独行
    int c=i/9%9;//0-8  c为0   9-17 c为1   18-26  c为2   ……   72-80为8  循环直至720-728为8  81个为一周期   表示数独中的列  映射:舞蹈链行->数独列
    int val=i%9+1;//0为1  1为2  2为3  ……  8为9   9个为一周期   表示数字1-9   映射:舞蹈链行->1-9数字
    if(ch[r*9+c]=='.'||ch[r*9+c]==val+'0') {//r表示第r行,c表示第c列,如果数独的第r行第c列是'.'号或是数字1-9
		//如果数独的第r行第c列是'.'号则它的所有行都建立舞蹈链结点
		//如果数独的第r行第c列是数字则它的指定行都建立舞蹈链结点
		link(i,r*9+val-1);//处理约束条件1:每个格子只能填一个数字    0-80列
        link(i,81+c*9+val-1);//处理约束条件2:每行1-9这9个数字只能填一个   81-161列
        int tr=r/3;
        int tc=c/3;
        link(i,162+(tr*3+tc)*9+val-1);//处理约束条件3:每列1-9的这9个数字都得填一遍
        link(i,243+r*9+c);//处理约束条件4:每宫1-9的这9个数字都得填一遍
    }
  }

  //for 0-728
  //把728个行结点全部删除
  for(i=0;i<RR;i++) {
    row[i].left->right=row[i].right;//每一行左边的右边等于行数的右边
    row[i].right->left=row[i].left;//每一行右边的左边等于行数的左边
  }
  solve(1);//调用solve函数
  printf("\n");
  if (scnt>1){
    print_solution(mem1);
    print_solution(mem2);
    printf("\n2 or mroe solutions found\n");
  }else if (scnt==1) {
    print_solution(mem1);
    printf("\none solution found\n");
  }else {
    printf("\nNO solution\n");
  }
  return 0;
}


  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值