啟發(fā)式搜索算法求解數(shù)獨

最近在上《最優(yōu)化理論》這一門課,課后老師布置一道課后作業(yè)---“使用啟發(fā)式搜索算法求解9*9數(shù)獨問題”英上。采用“回溯算法”和“啟發(fā)式搜索算法”兩種算法進行求解并進行比較,回溯法用C++實現(xiàn)啤覆,啟發(fā)式方法用python實現(xiàn)苍日。

1數(shù)獨規(guī)則及題目

數(shù)獨的規(guī)則:

  • 填入空格(即下圖數(shù)獨中值為0的格子)中的數(shù)滿足使每一行、每一列窗声、每一個宮內的均含有數(shù)字1-9且不重復相恃。


    數(shù)獨題目

2使用回溯算法求解

(1) 思路:

對于給定的數(shù)獨按照從左到右從上到下的順序進行搜索求解空格的值,對每一個空格首先取出空格處的值(假設每一個空格處的初始值為0)賦給變量m笨觅,然后令m=m+1拦耐。判斷m是否大于9,若大于9則將空格處的值賦0然后退回到前一個空格见剩;若不大于9則繼續(xù)判斷m是否滿足數(shù)獨規(guī)則杀糯,若滿足規(guī)則將此時的值m添入空格并移動到下一個空格,若不滿足規(guī)則就令m=m+1,繼續(xù)判斷m是否大于9苍苞。直到填完最后一個空格則求解完成固翰。

(2)具體的流程圖如下:

回溯法流程圖

(3)代碼實現(xiàn)如下:

代碼結構:
初始化函數(shù)-InitiFun
判段當前值是否滿足數(shù)獨規(guī)則函數(shù)-DetermValue
求解函數(shù)-Reslove
顯示結果的函數(shù)-ShowResult

  • 函數(shù)的頭文件-headfile.h
#pragma once
#include<vector>
std::vector<std::pair<int, int>> InitiFun(int(*Sokudu)[9], int&NumofZero);//初始化函數(shù)
int DetermValue(int x, int y, int m, int(*Soduku)[9]);//判斷函數(shù)
int Reslove(int(*Sokudu)[9], std::vector<std::pair<int, int>>&Coord, int&NumofZero, int&Count);//求解函數(shù)
void ShowResult(int(*ResultSokudu)[9], int result);//顯示函數(shù)
  • 函數(shù)的源碼-source.cpp
#include"pch.h"
#include<iostream>
#include"headfile.h"
//判斷是否滿足數(shù)獨的規(guī)則
int DetermValue(int x, int y, int m, int(*Soduku)[9])
{
    int x0, y0;
    x0 = x / 3;
    y0 = y / 3;
    //橫向判斷是否滿足規(guī)則
    for (int i = 0; i < 9; i++)
    {
        if (m == Soduku[x][i])
            return 0;
    }
    //縱向判斷是否滿足規(guī)則
    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;
}
//初始化函數(shù)返回所有空格處的位置
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;
}
//求解數(shù)獨
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;
}
//顯示數(shù)獨的結果
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 << "數(shù)獨無解\n";

}
  • 主函數(shù)-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為需要填空的總數(shù),Count記錄總的走的步數(shù)
    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 << "迭代的次數(shù)為" << Count << "\n"
        <<"需要填寫的數(shù)字個數(shù)為"<<NumofZero<<"\n";
    ShowResult(Sokudu, result);
}

(4)求解結果

求解結果

3啟發(fā)式搜索算法求解

(1)思路

我們在手算數(shù)獨的時候一般先算出所有空格的候選數(shù),然后找到候選數(shù)個數(shù)最少的位置開始搜索羹呵,如果在開始時存在一個空格的候選數(shù)個數(shù)為一骂际,那么這個數(shù)一定為該空格處的值。所以我們在每一步中按照候選數(shù)的個數(shù)最少的路徑開始搜索担巩,這樣會在一定程度上減少搜索的次數(shù)方援。
具體步驟如下:

  • 判斷Sokudu是否有空格
  • 若有空格則求出所有空格處的候選數(shù)
    找到候選數(shù)個數(shù)最小的空格没炒,判斷最小的候選數(shù)個數(shù)是否為零涛癌,若最小的候選數(shù)個數(shù)不為零則取出該候選數(shù)的第一項賦給該空格,并將取出第一項后剩下的候選數(shù)以及該空格的位置分別記錄下來分別保存到PopList列表及PopCoord列表的尾部送火,然后繼續(xù)判斷Sokudu是否有空格拳话。若最小的候選數(shù)個數(shù)為零,則判斷PopList列表的最后一項是否為空种吸,若為空則刪除PopList列表PopCoord列表的最后一項弃衍,接著判斷PopList列表的最后一項是否為空。若不為空則取出Poplist列表最后一項的的第一項賦給PopCoord最后一項位置處對應的數(shù)獨的值坚俗,然后繼續(xù)判斷Sokudu是否有空格镜盯。
  • 若無空格則輸出結果

(2)具體流程圖

啟發(fā)式搜索算法流程圖

(3)代碼

代碼結構:
計算所有空格的位置及個數(shù)-Locat_Zero
計算所有空格的候補數(shù)列表-Compute_Cadida
計算最小候選數(shù)個數(shù)的位置-Find_Min
求解數(shù)獨函數(shù)-Solve_Sokudu
顯示結果函數(shù)-Show_Result

  • sokudu.py
import array
def Locat_Zero(Sokudu):
    #計算所有空格的位置及個數(shù)
    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):
    #計算所有空格的候補數(shù)列表
    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):
    #找到最小的候選數(shù)個數(shù)的位置
    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):
    #計算需要填空的坐標及個數(shù):坐標列表:Coord,個數(shù):Count
    Coord,Count=Locat_Zero(Sokudu)
    TotalCount=0
    #彈出的候選數(shù)列表
    PopCandidaList=[]
    #彈出的坐標列表
    PopCoordList=[]
    while Count:
        #計算需要填空的候補列表:Candidalist
        CandidaList=Compute_Cadida(Coord,Count,Sokudu)
        #計算最小的候選數(shù)個數(shù)的位置
        Min,MinIndex,MinCoord=Find_Min(CandidaList,Coord,Count)
        if Min==0:
            while len(PopCandidaList[-1])==0:
                #上一個空格的坐標
                x=PopCoordList[-1][0]
                y=PopCoordList[-1][1]
                #還原當前節(jié)點值
                Sokudu[x][y]=0
                Count=Count+1
                Coord.append(PopCoordList[-1])
                #刪除當前節(jié)點坐標及候選數(shù)列表
                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位置處的候選數(shù)刪除候選數(shù)的第一項后放到彈出來的候選數(shù)列表中
            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)結果

總的迭代步數(shù)為:444


計算結果
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末岸裙,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子速缆,更是在濱河造成了極大的恐慌降允,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件艺糜,死亡現(xiàn)場離奇詭異剧董,居然都是意外死亡,警方通過查閱死者的電腦和手機破停,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進店門翅楼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人真慢,你說我怎么就攤上這事毅臊。” “怎么了晤碘?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵褂微,是天一觀的道長。 經(jīng)常有香客問我园爷,道長宠蚂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任童社,我火速辦了婚禮求厕,結果婚禮上,老公的妹妹穿的比我還像新娘扰楼。我一直安慰自己呀癣,他們只是感情好,可當我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布弦赖。 她就那樣靜靜地躺著项栏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蹬竖。 梳的紋絲不亂的頭發(fā)上沼沈,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天,我揣著相機與錄音币厕,去河邊找鬼列另。 笑死,一個胖子當著我的面吹牛旦装,可吹牛的內容都是我干的页衙。 我是一名探鬼主播,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼店乐!你這毒婦竟也來了艰躺?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤眨八,失蹤者是張志新(化名)和其女友劉穎描滔,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體踪古,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡含长,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了伏穆。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拘泞。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖枕扫,靈堂內的尸體忽然破棺而出陪腌,到底是詐尸還是另有隱情,我是刑警寧澤烟瞧,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布诗鸭,位于F島的核電站,受9級特大地震影響参滴,放射性物質發(fā)生泄漏强岸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一砾赔、第九天 我趴在偏房一處隱蔽的房頂上張望蝌箍。 院中可真熱鬧,春花似錦暴心、人聲如沸妓盲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽悯衬。三九已至,卻和暖如春檀夹,著一層夾襖步出監(jiān)牢的瞬間筋粗,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工击胜, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留亏狰,地道東北人役纹。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓偶摔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親促脉。 傳聞我的和親對象是個殘疾皇子辰斋,可洞房花燭夜當晚...
    茶點故事閱讀 45,086評論 2 355

推薦閱讀更多精彩內容