启发式搜索算法求解数独

最近在上《最优化理论》这一门课,课后老师布置一道课后作业---“使用启发式搜索算法求解9*9数独问题”。采用“回溯算法”和“启发式搜索算法”两种算法进行求解并进行比较,回溯法用C++实现,启发式方法用python实现。

1数独规则及题目

数独的规则:

  • 填入空格(即下图数独中值为0的格子)中的数满足使每一行、每一列、每一个宫内的均含有数字1-9且不重复。


    数独题目

2使用回溯算法求解

(1) 思路:

对于给定的数独按照从左到右从上到下的顺序进行搜索求解空格的值,对每一个空格首先取出空格处的值(假设每一个空格处的初始值为0)赋给变量m,然后令m=m+1。判断m是否大于9,若大于9则将空格处的值赋0然后退回到前一个空格;若不大于9则继续判断m是否满足数独规则,若满足规则将此时的值m添入空格并移动到下一个空格,若不满足规则就令m=m+1,继续判断m是否大于9。直到填完最后一个空格则求解完成。

(2)具体的流程图如下:

回溯法流程图

(3)代码实现如下:

代码结构:
初始化函数-InitiFun
判段当前值是否满足数独规则函数-DetermValue
求解函数-Reslove
显示结果的函数-ShowResult

  • 函数的头文件-headfile.h
#pragma once
#include<vector>
std::vector<std::pair<int, int>> InitiFun(int(*Sokudu)[9], int&NumofZero);//初始化函数
int DetermValue(int x, int y, int m, int(*Soduku)[9]);//判断函数
int Reslove(int(*Sokudu)[9], std::vector<std::pair<int, int>>&Coord, int&NumofZero, int&Count);//求解函数
void ShowResult(int(*ResultSokudu)[9], int result);//显示函数
  • 函数的源码-source.cpp
#include"pch.h"
#include<iostream>
#include"headfile.h"
//判断是否满足数独的规则
int DetermValue(int x, int y, int m, int(*Soduku)[9])
{
    int x0, y0;
    x0 = x / 3;
    y0 = y / 3;
    //横向判断是否满足规则
    for (int i = 0; i < 9; i++)
    {
        if (m == Soduku[x][i])
            return 0;
    }
    //纵向判断是否满足规则
    for (int i = 0; i < 9; i++)
    {
        if (m == Soduku[i][y])
            return 0;
    }
    //宫内判断是否满足条件
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            if (m == Soduku[x0 * 3 + i][y0 * 3 + j])
                return 0;
        }
    }
    return 1;
}
//初始化函数返回所有空格处的位置
std::vector<std::pair<int, int>> InitiFun(int(*Sokudu)[9], int&NumofZero)
{
    //定义局部的vector故不能返回vector的引用
    std::vector<std::pair<int, int>>Coord;
    for (int i = 0; i < 9; i++)
    {
        for (int j = 0; j < 9; j++)
        {
            if (Sokudu[i][j] == 0)
            {
                Coord.push_back(std::make_pair(i, j));
                NumofZero++;
            }
        }
    }
    return Coord;
}
//求解数独
int Reslove(int(*Sokudu)[9], std::vector<std::pair<int, int>>&Coord, int&NumofZero, int&Count)
{
    int i = 0;
    int X, Y, M;
    while (i < NumofZero&&i >= 0)
    {
        X = Coord[i].first;
        Y = Coord[i].second;
        M = Sokudu[X][Y];
        while (1)
        {
            M++;
            if (M > 9)
            {
                Sokudu[X][Y] = 0;
                i--;
                Count++;
                break;
            }
            else
            {
                if (DetermValue(X, Y, M, Sokudu))
                {
                    Sokudu[X][Y] = M;
                    i++;
                    Count++;
                    break;
                }
            }
        }
    }
    return i;
}
//显示数独的结果
void ShowResult(int(*ResultSokudu)[9], int result)
{
    if (result != -1)
    {
        for (int i = 0; i < 9; i++)
        {
            for (int j = 0; j < 9; j++)
            {
                std::cout << ResultSokudu[i][j] << "\t";
            }
            std::cout << "\n";
        }
    }
    else
        std::cout << "数独无解\n";

}
  • 主函数-sodukumain.cpp
#include "pch.h"
#include <iostream>
#include"headfile.h"
int main()
{
    int Sokudu[9][9] =
    {
    {0,0,5,3,0,0,0,0,0},
    {8,0,0,0,0,0,0,2,0},
    {0,7,0,0,1,0,5,0,0},
    {4,0,0,0,0,5,3,0,0},
    {0,1,0,0,7,0,0,0,6},
    {0,0,3,2,0,0,0,8,0},
    {0,6,0,5,0,0,0,0,9},
    {0,0,4,0,0,0,0,3,0},
    {0,0,0,0,0,9,7,0,0}
    };
    //NumofZero为需要填空的总数,Count记录总的走的步数
    int i=0, NumofZero=0, Count=0;
    //进行初始化
    std::vector<std::pair<int,int>>Coord = InitiFun(Sokudu, NumofZero);
    //进行计算
    int result = Reslove(Sokudu, Coord, NumofZero, Count);
    std::cout << "迭代的次数为" << Count << "\n"
        <<"需要填写的数字个数为"<<NumofZero<<"\n";
    ShowResult(Sokudu, result);
}

(4)求解结果

求解结果

3启发式搜索算法求解

(1)思路

我们在手算数独的时候一般先算出所有空格的候选数,然后找到候选数个数最少的位置开始搜索,如果在开始时存在一个空格的候选数个数为一,那么这个数一定为该空格处的值。所以我们在每一步中按照候选数的个数最少的路径开始搜索,这样会在一定程度上减少搜索的次数。
具体步骤如下:

  • 判断Sokudu是否有空格
  • 若有空格则求出所有空格处的候选数
    找到候选数个数最小的空格,判断最小的候选数个数是否为零,若最小的候选数个数不为零则取出该候选数的第一项赋给该空格,并将取出第一项后剩下的候选数以及该空格的位置分别记录下来分别保存到PopList列表及PopCoord列表的尾部,然后继续判断Sokudu是否有空格。若最小的候选数个数为零,则判断PopList列表的最后一项是否为空,若为空则删除PopList列表PopCoord列表的最后一项,接着判断PopList列表的最后一项是否为空。若不为空则取出Poplist列表最后一项的的第一项赋给PopCoord最后一项位置处对应的数独的值,然后继续判断Sokudu是否有空格。
  • 若无空格则输出结果

(2)具体流程图

启发式搜索算法流程图

(3)代码

代码结构:
计算所有空格的位置及个数-Locat_Zero
计算所有空格的候补数列表-Compute_Cadida
计算最小候选数个数的位置-Find_Min
求解数独函数-Solve_Sokudu
显示结果函数-Show_Result

  • sokudu.py
import array
def Locat_Zero(Sokudu):
    #计算所有空格的位置及个数
    Count=0
    Coord=[]
    for X in range(9):
        for Y in range(9):
            if Sokudu[X][Y]==0:
                CoordXY=[]
                CoordXY.append(X)
                CoordXY.append(Y)
                Coord.append(CoordXY)
                Count=Count+1
    return Coord,Count
def Compute_Cadida(Coord,Count,Sokudu):
    #计算所有空格的候补数列表
    CandidaList=[]
    for i in range(Count):
        Num=[1,2,3,4,5,6,7,8,9]
        x=Coord[i][0]
        y=Coord[i][1]
        x0=int(x/3)
        y0=int(y/3)
        for j in range(9):
            if Sokudu[x][j] in Num:
                Num.remove(Sokudu[x][j])
        for k in range(9):
            if Sokudu[k][y] in Num:
                Num.remove(Sokudu[k][y])
        for m in range(3):
            for n in range(3):
                if Sokudu[x0*3+m][y0*3+n] in Num:
                    Num.remove(Sokudu[x0*3+m][y0*3+n])
        CandidaList.append(Num)
    return CandidaList
def Find_Min(CandidaList,Coord,Count):
    #找到最小的候选数个数的位置
    NumofCandida=[]
    for i in range(Count):
        temp=len(CandidaList[i])
        NumofCandida.append(temp)
    Min=min(NumofCandida)
    MinIndex=NumofCandida.index(Min)
    return Min,MinIndex,Coord[MinIndex]
def Solve_Sokudu(Sokudu):
    #计算需要填空的坐标及个数:坐标列表:Coord,个数:Count
    Coord,Count=Locat_Zero(Sokudu)
    TotalCount=0
    #弹出的候选数列表
    PopCandidaList=[]
    #弹出的坐标列表
    PopCoordList=[]
    while Count:
        #计算需要填空的候补列表:Candidalist
        CandidaList=Compute_Cadida(Coord,Count,Sokudu)
        #计算最小的候选数个数的位置
        Min,MinIndex,MinCoord=Find_Min(CandidaList,Coord,Count)
        if Min==0:
            while len(PopCandidaList[-1])==0:
                #上一个空格的坐标
                x=PopCoordList[-1][0]
                y=PopCoordList[-1][1]
                #还原当前节点值
                Sokudu[x][y]=0
                Count=Count+1
                Coord.append(PopCoordList[-1])
                #删除当前节点坐标及候选数列表
                del PopCoordList[-1]
                del PopCandidaList[-1]
            #更改上一个空格位置处的值
            x=PopCoordList[-1][0]
            y=PopCoordList[-1][1]
            Sokudu[x][y]=PopCandidaList[-1][0]
            del PopCandidaList[-1][0]
            TotalCount=TotalCount+1
        else:
            #更改MinCoord位置处空格的值
            x=MinCoord[0]
            y=MinCoord[1]
            Sokudu[x][y]=CandidaList[MinIndex][0]
            #取出MinCoord位置处的候选数删除候选数的第一项后放到弹出来的候选数列表中
            PopCandida=CandidaList.pop(MinIndex)
            del PopCandida[0]
            PopCandidaList.append(PopCandida)
            #取出MinCoord的位置放到弹出来的坐标列表中
            PopCoord=Coord.pop(MinIndex)
            PopCoordList.append(PopCoord)
            Count=Count-1
            TotalCount=TotalCount+1
    return Sokudu,TotalCount
def Show_Result(Sokudu):
    for i in range(9):
        print(Sokudu[i])
  • sukudumain.py
from numpy import*
from soduku import*
Sokudu=[
         [0,0,5,3,0,0,0,0,0],
         [8,0,0,0,0,0,0,2,0],
         [0,7,0,0,1,0,5,0,0],
         [4,0,0,0,0,5,3,0,0],
         [0,1,0,0,7,0,0,0,6],
         [0,0,3,2,0,0,0,8,0],
         [0,6,0,5,0,0,0,0,9],
         [0,0,4,0,0,0,0,3,0],
         [0,0,0,0,0,9,7,0,0]]
Solve,TotalCount=Solve_Sokudu(Sokudu)
print("solve:")
Show_Result(Solve)
print(TotalCount)

(4)结果

总的迭代步数为:444


计算结果
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,542评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,822评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,912评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,449评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,500评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,370评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,193评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,074评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,505评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,722评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,841评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,569评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,168评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,783评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,918评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,962评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,781评论 2 354

推荐阅读更多精彩内容