更智能的解数独算法,让效率提升5倍!

《穷举法解数独》中,利用穷举每一个空格的位置,到最后能终解出数独,虽然感觉上也是一瞬间的事,不过也可以通过更加智能的方式更高效的实现。

当然基本思路还是离不开穷举,只不过将穷举的次序进行改进:建立一个矩阵,记录下数独中每个空位可能填写的数字的个数,每次只对数独中可能性个数最少的空位操作。

另外,程序中会使用之前写的泛型栈,可参考《C语言实现一个泛型栈

数据和函数声明:

#include <stdio.h>
#include "stack.h"

#define BLANK 0
#define TOPIC 1
#define TRY 2

static int sudo[9][9];
static int sudo2[9][9];
static int sudo3[9][9];

typedef struct
{
        int v;
        int x;
        int y;
}recorder;

static void sudo2Min(int *y, int *x);
static int sudo_2_Init();
static int sudo1_3Init();
static int sudoInit(int arr[9][9]);
static int sudo2ElemUpdate(int y, int x);
static int sudo2RowUpdate(int y, int x);
static int sudo2ColUpdate(int y, int x);
static int sudo2SquUpdate(int y,int x);
static int sudo2Update(int y, int x);
static int verSquare(int y, int x);
static int verRow(int y, int x);
static int verCol(int y, int x);
static int verify(int y, int x);
static int verifyAll();
static void next(int *y, int *x);
void sudoPrt();
int sudoGo(int arr[9][9]);

数据说明:

  • sudo[]9[9] 记录数独每个位置所填写的数字, sudo2[9][9] 记录每个空位能够填写的数字个数, sudo3[9][9] 记录每个位置的状态
  • 宏定义BLANK、TOPIC、TRY分别代表空位,题目给出所占用的位,已尝试填写的位
  • 结构recorder记录坐标和填写的数字,用于压栈和弹栈。

函数说明:

static int verCol(int y, int x) 检查坐标所在的列所填的数字能否通过

static int verCol(int y, int x)
{
        int n;
        for(n = 0; n < 9; n++){
                if(n == y)continue;
                if(sudo[n][x] == sudo[y][x])
                        return 0;
        }

        return 1;
}

static int verRow(int y, int x) 检查坐标所在的行所填的数字能否通过

static int verRow(int y, int x)
{
        int n;
        for(n = 0; n < 9; n++){
                if(n == x)continue; 
                if(sudo[y][n] == sudo[y][x])
                        return 0;
        }
        return 1;
}
static int verSquare(int y, int x)  给出一个坐标,检查这个坐标所填的数字在的9格方块中能否通过。

static int verSquare(int y, int x)
{
        int dx,dy;
        int begx = (x / 3) * 3;   // 起始坐标
        int begy = (y / 3) * 3;   // 起始坐标
        for(dy = 0; dy < 3; dy++){
                for(dx = 0; dx < 3; dx++){
                        if((begx + dx == x) && (begy + dy == y))continue;
                        if(sudo[begy + dy][begx + dx] == sudo[y][x])
                                return 0;
                }
        }

        return 1;
}

static int verify(inty, int x)  调用以上3个函数,验证一个坐标所填的数字能否通过

static int verify(int y, int x)
{
        if(sudo[y][x] != 0){
                if(verSquare(y,x) == 0 || verCol(y,x)== 0 || verRow(y,x) == 0 ){
                        return 0;
                }
        }

        return 1;
}

static int verifyAll() 遍历整个数独,如果全部通过即找到解答

static inline int verifyAll()
{
        int x,y;
        for(y = 0; y < 9; y++)
                for(x = 0; x < 9; x++){
                        if(sudo[y][x] == 0)
                                return 0;
                }
        for(y = 0; y < 9; y++)
                for(x = 0; x < 9; x++){
                        if(verify(y,x) == 0)
                                return 0;
                }
        return 1;
}
void sudoPrt() 在屏幕上打印出数独

void sudoPrt()
{
        int y,x;
        for(y = 0; y < 9; y++){
                for(x = 0; x < 9; x++){
                        printf("%d  ",sudo[y][x]);
                }
                printf("\n");
        }
}

static void next(int *y, int *x) 选出下一个空位
static void next(int *y, int *x)
{
        *x += 1;
        *y += *x / 9;
        *x = *x % 9;
        if(sudo3[*y][*x] != BLANK && *y < 10)
                next(y,x);
}

static void sudo2Min(int *y, int *x) 在sudo2中查找可能的数字最少的坐标
static void sudo2Min(int *y, int *x)
{
        int x2,y2;
        *x = -1;
        *y = 0;
        x2 = -1;
        y2 = 0;
        next(y,x);

        while(y2 < 9){
                next(&y2,&x2);
                if(sudo2[y2][x2] < sudo2[*y][*x]){
                        *y = y2;
                        *x = x2;
                }
        }
}
static int sudo2ElemUpdate(int y, int x)  给出一个坐标,从sudo2里更新这个位置能够填写数字的个数:
static int sudo2ElemUpdate(int y, int x)
{
        int i, v, tmpSudo, ableNum;
        ableNum = 0;
        tmpSudo = sudo[y][x];               // 先记下这个位置的数字
        for(v = 1; v < 10; v++){              // 然后从 1到9 进行遍历
        sudo[y][x] = v;                            // 如果验证通过ableNum++
                if(verify(y,x))
                        ableNum++;
        }
        sudo[y][x] = tmpSudo;              // 还原这个位置的数字
        if(ableNum == 0 && sudo3[y][x] == BLANK){
                return 0;                  // 如果存在一个空位置,并且不能填写任何数字,那么返回0
        }
        sudo2[y][x] = ableNum;
        return 1;
}
static int sudo2RowUpdate(int y, int x)  在sudo2中,更新坐标所在行的每个空位的可填数字个数
static int sudo2RowUpdate(int y, int x)
{
        int i,rowX, rowY;
        rowX = x;                    // 行起始坐标
        rowY = 0;
        for(i = 0; i < 9; i++){
                if(sudo3[rowY + i][rowX] != BLANK)continue;
                if(sudo2ElemUpdate(rowY + i,rowX) == 0)
                        return 0;
        }
        return 1;
}

static int sudo2ColUpdate(int y,int x)  在sudo2中,更新坐标所在列的每个空位的可填数字个数
static int sudo2ColUpdate(int y,int x)
{
        int i,colX, colY;
        colX = 0;            //  列起始坐标
        colY = y;
        for(i = 0; i < 9; i++){
                if(sudo3[colY][colX + i] != BLANK)continue;
                if(sudo2ElemUpdate(colY,colX + i) == 0)
                        return 0;
        }
        return 1;
}
static int sudo2SquUpdate(int y, int x)  在sudo2中,更新坐标所在9格方块的每个空位的可填数字个数
static int sudo2SquUpdate(int y, int x)
{
        int i,squX, squY;
        squX = (x / 3) * 3;         // 9格方块起始坐标
        squY = (y / 3) * 3;
        for(i = 0; i < 9; i++){
                if(sudo3[squY + i / 3][squX + i % 3] != BLANK)continue;
                if(sudo2ElemUpdate(squY + i / 3,squX + i % 3) == 0)
                        return 0;
        }
        return 1;
}

static int sudo2Update(int y,int x)  在sudo2中,利用以上3个函数对坐标所影响到的位置更新
static int sudo2Update(int y,int x)
{
        if(sudo2RowUpdate(y,x) && sudo2ColUpdate(y,x) && sudo2SquUpdate(y,x))
                return 1;
        return 0;
}

static int sudo1_3Init(int arr[9][9])  对sudo和sudo3进行初始化
static int sudo1_3Init(int arr[9][9])
{
        int x, y;
        for(y = 0; y < 9; y++)
                for(x = 0;x < 9; x++){
                        sudo[y][x] = arr[y][x];
                        if(verify(y,x) == 0)
                                return 0;
                        if(sudo[y][x] != 0)
                                sudo3[y][x] = TOPIC;
                else
                                sudo3[y][x] = BLANK;
                }
        return 1;
}
static int sudo_2_Init() 对sudo2进行初始化
static int sudo_2_Init()
{
        int x,y;
        x = -1;
        y = 0;
        while(y < 9){
                next(&y, &x);
                if(sudo2ElemUpdate(y, x) == 0)
                        return 0;
        }
        return 1;
}
static int sudoInit(int arr[9][9])  利用以上两个函数做初始化
static int sudoInit(int arr[9][9])
{
        if(sudo1_3Init(arr) && sudo_2_Init())
                return 1;
        return 0;
}
int sudoGo(int arr[9][9])  驱动以上函数,寻找解答,Go
int sudoGo(int arr[9][9])
{
        int y,x,val;
        stack s;
        recorder re;
        val = 1;
        if(sudoInit(arr) == 0)
                return -1;                           // 首先进行初始化,失败说明给出的题目有问题,返回-1
        newStack(&s,sizeof(recorder));  // 初始化栈
        sudo2Min(&y,&x);                 // 首先在sudo2中找出可填数字最少的坐标

        do{    //  while (!isEmpty(&s));
                while(val < 10){        // 从1到10开始尝试
                        sudo[y][x] = val;   
                        if(verify(y,x)){      // 如果通过测试
                                if(verifyAll()){    // 如果整个数独都通过测试
                                        disposeStack(&s,NULL);   // 销毁栈,找出了解答,返回1
                                        return 1;
                                }
                                if(sudo2Update(y, x) == 0){ // 在sudo2中更新所影响到的位置sudo2可填写数字个数
                                        val++;          // 如果导致某个位置既是空位,又不能填写任何数字
                                        continue;     // 从下个数字尝试
                                }

                                re.x = x;             // 记录下这个坐标和数字
                                re.y = y;
                                re.v = val;
                                sudo[y][x] = val;     
                                sudo3[y][x] = TRY;  // 修改状态
                                push(&s,&re);    // 压栈

                                val = 1;        //  新位置从数字1开始重新遍历
                                sudo2Min(&y,&x);  找出下一个可填数字最少的坐标

                        }else{ val++; }  // 坐标验证没通过,那么从下一个数字开始
                }

                sudo[y][x] = 0;          //  数字遍历到9了,如果没通过验证
                sudo3[y][x] = BLANK;   // 当前状态和数字全部清0
                pop(&s,&re);   //  弹栈,返回到上一次的地方
                val = re.v + 1;       // 从下一个数字开始
                x = re.x;
                y = re.y;

        }while(!isEmpty(&s));

        disposeStack(&s, NULL);
        return 0;
 }



下面是测试:

#include <stdio.h>
#include <time.h>

extern int sudoGo(int arr[9][9]);
extern void sudoPrt();

int main(int argc, const char *argv[])
{
        int c,result;
        time_t t1,t2;

        int sudo[9][9] = {{9,0,0,0,6,0,8,2,0},
                          {0,0,0,2,0,8,0,0,5},
                          {0,0,0,4,0,9,0,6,0},
                          {2,9,0,1,0,0,6,0,3},
                          {0,6,0,0,0,0,0,7,0},
                          {7,0,4,0,0,6,0,5,2},
                          {0,7,0,8,0,3,0,0,0},
                          {5,0,0,6,0,7,0,0,0},
                          {0,2,6,0,4,0,0,0,8}};

        t1 = time(NULL);
        for(c=0; c < 1000; c++)
                result = sudoGo(sudo);
        if(result == 1)               
                sudoPrt(0);
        else if(result == 0)
                printf("\nNo result\n");
        else if(result == -1)
                printf("\nError input");
        t2 = time(NULL);  
      
        printf("\nBegin is %s\n",ctime(&t1));   
        printf("End is %s\n",ctime(&t2));   
        printf("Executive spend %f sec.\n", difftime(t2,t1));  
      
        return 0;  
    } 

循环1000次的运行结果

9  4  3  7  6  5  8  2  1  
6  1  7  2  3  8  9  4  5  
8  5  2  4  1  9  3  6  7  
2  9  5  1  7  4  6  8  3  
1  6  8  3  5  2  4  7  9  
7  3  4  9  8  6  1  5  2  
4  7  9  8  2  3  5  1  6  
5  8  1  6  9  7  2  3  4  
3  2  6  5  4  1  7  9  8  

Begin is Mon Nov 12 13:13:27 2012

End is Mon Nov 12 13:13:30 2012

Executive spend 3.000000 sec.

之前的穷举法循环1000次需要花费15秒,而用这个算法1000次只花费了3秒,效率提高了5倍,当然对于不同难度的数独测试结果也不同,我是虚拟机上装linux测试的,如果在本地环境下应该会更快。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
机器学习是一种人工智能(AI)的子领域,致力于研究如何利用数据和算法让计算机系统具备学习能力,从而能够自动地完成特定任务或者改进自身性能。机器学习的核心思想是让计算机系统通过学习数据中的模式和规律来实现目标,而不需要显式地编程。 机器学习应用非常广泛,包括但不限于以下领域: 图像识别和计算机视觉: 机器学习在图像识别、目标检测、人脸识别、图像分割等方面有着广泛的应用。例如,通过深度学习技术,可以训练神经网络来识别图像中的对象、人脸或者场景,用于智能监控、自动驾驶、医学影像分析等领域。 自然语言处理: 机器学习在自然语言处理领域有着重要的应用,包括文本分类、情感分析、机器翻译、语音识别等。例如,通过深度学习模型,可以训练神经网络来理和生成自然语言,用于智能客服、智能助手、机器翻译等场景。 推荐系统: 推荐系统利用机器学习算法分析用户的行为和偏好,为用户推荐个性化的产品或服务。例如,电商网站可以利用机器学习算法分析用户的购买历史和浏览行为,向用户推荐感兴趣的商品。 预测和预测分析: 机器学习可以用于预测未来事件的发生概率或者趋势。例如,金融领域可以利用机器学习算法进行股票价格预测、信用评分、欺诈检测等。 医疗诊断和生物信息学: 机器学习在医疗诊断、药物研发、基因组学等领域有着重要的应用。例如,可以利用机器学习算法分析医学影像数据进行疾病诊断,或者利用机器学习算法分析基因数据进行疾病风险预测。 智能交通和物联网: 机器学习可以应用于智能交通系统、智能城市管理和物联网等领域。例如,可以利用机器学习算法分析交通数据优化交通流量,或者利用机器学习算法分析传感器数据监测设备状态。 以上仅是机器学习应用的一部分,随着机器学习技术的不断发展和应用场景的不断拓展,机器学习在各个领域都有着重要的应用价值,并且正在改变我们的生活和工作方式。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值