数独九宫格

数独九宫格游戏

一.题目说明 

数独的游戏规则:

1、在9×9的大九宫格内,已给定若干数字,其他宫位留白,玩     家需要自己按照逻辑推敲出剩下的空格里是什么数字。
   2、必须满足的条件:每一行与每一列都有1到9的数字,每个小    九宫格里也有1到9的数字,并且一个数字在每行、每列及每   个小九宫格里只能出现一次,既不能重复也不能少。
   3、每个数独游戏都可根据给定的数字为线索,推算解答出来。

按照数独的游戏规则,用计算机实现已知数独的求解和数独题目的出题。

二.数据结构说明

数据结构一维数组、二维数组以及类似于“栈”的数据结构。

主要操作有:进栈,出栈,栈顶元素的操作等等

三.抽象数据类型

五个全局变量数组,其中两个二维数组,三个一维数组。

int a[10][10]

int sd[82]

int fix[82]

int possible[82][10]

int stack[82]

四.算法设计

程序可以考虑人工智能的算法。采用人机的游戏方式,这里采用“回溯”的方法解决数独问题。

基本框架如下:

 

 图片粘贴不了

 

五.数独程序代码:

   #include"stdio.h" //标准输入输出头文件

#include"conio.h" //包含getch()的头文件

#include"stdlib.h" //包含rand()的头文件

#include"assert.h" //包含assert()的头文件

#include"time.h" //包含srand()的头文件

int a[10][10];//用来接收输入数据的数组

int sd[82];//处理题目以及保存最终结果

int fix[82];//记录哪些位置是确定的,确定为1,否则为0

int possible[82][10];//记录所有未确定数字的可能性

int stack[82];//用来放置填入的数的栈

int t;

void clssd()//初始化函数,所有位置设为0

{

   int i,j,k;

   for(i=0;i<9;i++)

      for(j=0;j<9;j++)

         a[i][j]=0;

   for(k=1;k<=81;k++)

      sd[k]=0;

}

int line(int line,int value)//检验行

{

   int i;

   for(i=1;i<=9;i++)

   {

      if(a[line][i]==value) return 0;

   }

   return 1;

}

int row(int row,int value)//检验列

{

   int i;

   for(i=1;i<=9;i++)

   {

      if(a[i][row]==value) return 0;

   }

   return 1;

}

int square(int line,int row,int value)//检验3*3的九宫

{

   int L,R,i,j;

   L=(line%3!=0)+line/3;//L表示所在九宫的行数

   R=(row%3!=0)+row/3;//R表示所在九宫的列数

   for(i=(L-1)*3+1;i<=L*3;i++)

   {

      for(j=(R-1)*3+1;j<=R*3;j++)

         if(a[i][j]==value) return 0;

   }

   return 1;

}

   int transform_to_line(int i)

   {

      int line;

      line=i/9+(i%9!=0);

      return line;

   }

   int transform_to_row(int i)

   {

      int row;

      row=i%9+9*(i%9==0);

      return row;

   }

void transform_to_a(int i)

{

      int l,r;

      l=transform_to_line(i);

      r=transform_to_row(i);

      a[l][r]=sd[i];

}

void transform_to_sd()

{

   int line,row,i=1;

  

   for(line=1;line<=9;line++)

      for(row=1;row<=9;row++)

         do

         {

            sd[i]=a[line][row];

            i++;       

            break;

         }while(i<=81);

 

}

//输出函数

void printAll()

{

   int i;

   for(i=0;i<81;i++)

   { 

      if(i%9==0)

         printf(" ");

      printf("%4d",sd[i+1]);

  

   }

   printf(" ");

}

void input()//输入数据到a[10][10]

{

   system("cls");//清屏

   int b[3]={0,0,0},i,j;

   printf("请输入已知数据");

   printf(" 例如输入7 8 9,表示第8行 第9列的数值为7,以此类推。 ");

   do

   {       

      printf(" 输入数据:按照 值 / 行 / 列的顺序,以0结束");

      for(i=0;i<3;i++)

      {

         j=getch()-48;

         if(j==0&&i==0) break;//实现按0结束语句

         if(j>0&&j<10)

         {

            b[i]=j;

            printf("%3d",b[i]);

         }

         else i--;

      }

      a[b[1]][b[2]]=b[0];  

   }while(j); 

   transform_to_sd();

   printf(" 您输入的题目为: ");//打印输入的数独

   printAll();

}

//三个重要函数

bool beExist(int i,int j)//判断 sd 数组中位置为 i 的元素是否存在

{

   int l,r;

   l=transform_to_line(i);

   r=transform_to_row(i);

   //if(sd[i]!=0) return 1; 关键的错误所在!!! 

   if(line(l,j)*row(r,j)*square(l,r,j)==0) return 1;

   else return 0;

}

void setPb()//控制possible数组

{

  

   int i,j;

   for(i=1;i<=81;i++)

   {    

      for(j=1;j<=9;j++)

      {

         if(sd[i]!=0) possible[i][j]=-1;

         else if( beExist(i,j))

         {possible[i][j]=-1;}

         else

         {possible[i][j]=j;}

      }

   }

 

}

bool fixAll(int sd[],int fix[],int possible[82][10])//控制fix数组

{

   int i,j;

   int k[82]; 

   for(i=1;i<=81;i++)

   {

      if(sd[i]!=0)

      {fix[i]=1;}

      else

      {fix[i]=0;}

   } 

   for(i=1;i<=81;i++)

   {

      if(sd[i]!=0) continue;

      if(fix[i]==0)

      {

         for(j=1;j<=9;j++)

         if(possible[i][j]!=-1)

            k[i]++;

      }

      if(k[i]==1)//如果存在只有一种可能的位置则将fix[i]改为1

         fix[i]=1;

         sd[i]=possible[i][j];

   } 

   for(i=1;i<=81;i++)//判断是否存在只有一种可能的位置,若没有返回0

   {

      if(k[i]==1) return 1;

      else return 0;

   }

}

//判断是否完成

int isFull(int sd[])

{

   int i;

   for(i=1;i<=81;i++)

      if(sd[i]==0) return 0;  

   return 1;

}

void preDo()//预处理

{

   while(1)

   {

      setPb();

      if(!fixAll(sd,fix,possible)) //即不存在只有一种可能性的位置

         break;

      if(isFull(sd)) //若已经推出全部结果

         break;

   }

   if(isFull(sd))

      printAll();//打印结果

 

}

int calculate()//填数独 (关键算法!!!)

{

   preDo();//预处理,找出所有的位置的可能数值

   if(isFull(sd)) return 1;

   int top=0;

   //将所有为0的位置入栈

   for(int i=1;i<82;i++)

   { 

      if(fix[i]==0)

         stack[top++]=i;

   }

   int max=top;//记录最大数目加1

   top=0;//指向栈顶

   int temp;

   bool flag=true;//该标志位说明了当前是正常入栈

   while(1)

   {

      assert(top>=0);

      if(flag)

      {

         int j;

         for(j=1;j<=9;j++)

            if(possible[stack[top]][j]!=-1&&!beExist(stack[top],j))

            //若top所示的位置上,可以安插j这个数值,则

            {

                fix[stack[top]]=1;

                sd[stack[top]]=j;

                transform_to_a(stack[top]);

                ++top;

                if(top>=max) return 1;

                break;

            }

         if(j>9)//该空所有可能值都不可以,则退一格

         {

        

            --top;

            if(top<0) {printAll(); return 0;}

            flag=false;

         }

      }    

      else

      {

         if(sd[stack[top]]==9)

         {

            fix[stack[top]]=0;

            sd[stack[top]]=0;

            transform_to_a(stack[top]);

            --top;

            if(top<0) {printAll(); return 0;}

         }

         else

         {

            temp=sd[stack[top]];    

            temp++;

            while(possible[stack[top]][temp]==-1||beExist(stack[top],temp))

            {

                temp++;

                if(temp>9)

                break;

            }

            if(temp>9)//当前节点没有更换的可能性,继续退回

            {

                fix[stack[top]]=0;

                sd[stack[top]]=0;

                transform_to_a(stack[top]);

                --top;

                if(top<0) {printAll(); return 0;}

            }

            else

            {

                sd[stack[top]]=temp;

                transform_to_a(stack[top]);

                ++top;

                flag=true;

            }

         }

     

      }

   }

  

}

void solve_problem()//解题

{

   int p;

   system("cls"); //清屏

   clssd(); //赋初值为0

   input(); //输入数据

   transform_to_sd(); //转换为sd[i]数组

   p=calculate(); //计算数独

   if(p==0)

      printf(" 题目有误!!! ");

      printf(" 答案为: ");

   printAll();

void random()//从1-9中随机产生几个值输入sd[1]至sd[9]

   { 

      int i,j,r;

      int change=1;

      int b[10]={0,0,0,0,0,0,0,0,0,0};

       for(i=1;i<=9;)//从1-9中随机产生几个值

       { 

         change=1;

         j=1+rand()%9;

         for(r=1;r<=9;r++)

            if(b[r]==j) change=0;

         if(change==1)

         {b[i]=j;  i++;}

       }

       

       for(i=1;i<=9;i++)

       {

         sd[i]=b[i];

         transform_to_a(i);

       }

   }

void hide()

   {

      int i,f;

      printf("请输入难度: 1.Easy 2.Normal 3.Hard 4.So Hard 5.Terrible Hard ");

      do

      {

         f=getch()-48;

      }while(f>5||f<1);//一共5个级别

  

      for(i=1;i<=81;i++)

      { 

         if(rand()%6>=f)//利用随机数出现的概率出题

         printf("%4d",sd[i]);

         else

            printf("   0");

         if(i%9==0)

            printf(" ");

      }

   }

void make_problem()//出题函数

   system("cls");//初始化

   clssd();

   random();//填9个随机值

   calculate();//算出答案

   hide();

   printf(" 注意:题目中0代表待填数据    按空格键输出答案,其他键退出程序 ");

   int f;

   do

   {

      f=getch()-32;

      if(!f)

         printAll();

      else break;

   }while(f);

}

void quit()

{

int i;

for(i=0;i<100;i++)

{

 printf("%d ",i);

 if (i>2||i<1)

 {

  exit(1);

 }

}

}

void main()//主函数

{

   srand((unsigned)time(0));//设置时间种子为0

   system("cls");//清屏

   clssd();

   printf(" 数独游戏 1.你出题,电脑来解 2.电脑出题,你来解 3.退出游戏");

   int i;

   do

   {

      i=getch()-48;

      switch(i)

      {

      case 1:solve_problem();

         break;

      case 2:make_problem();

         break;

      case 3:quit();

         break;

      }

   }while(i>2||i<1);

}

六.调试报告

1.整个程序最麻烦的地方是二维数组a[10][10]与一 维数组sd[82]之间的转换。因为输入数据为了方便 和符合人类的思维采用了与数独相似的二维数组   进行输入。但是回溯算法要求转换成一维数组进行  操作。

2.调试过程中,用到了一个宏命令assert(top>=0)  top是栈顶元素的“指针”,用来操控栈中元素。正  常情况下top>=0的。如果出现异常情况,则assert 函数会弹出对话框报错。这表示calculate函数在 回溯的时候top值总是会回到栈底元素之前。事实  上,开始因为在beExist()函数中用了错误的判断 方 式,运行程序时总是会使得top<0。

3.上一条中的错误之所以会出现,是因为在设计程序 之初没有想好,不够完善。于是出现了,自己设计 的判断方法与计算数独矛盾的结果。得到的教训   是,动手之前应该先考虑清楚完整的架构和应该考 虑到的细节.

 

七.测试结果分析

下面为规定测试数据:

 图片粘贴不了

 

 

原文地址:https://www.cnblogs.com/wanghongcai/p/4839090.html