数独を解くための効率的なアルゴリズム


タイトル:効率的なアルゴリズムの数だけでは解決するために
2019年12月26日午後五時55分16秒:DATE
タグ:データ構造とアルゴリズムの
カテゴリ:データ構造とアルゴリズムを


背景

  Pythonのクラスの前に、実験は数独を解くためになるとすると、複雑さが低くなっている時間を要し、この目的のために教師は、数独のための効率的なアルゴリズムを説明するために、私は非常に興味深いアルゴリズムを感じ、それについてブログを書きます。


説明

数独のルールを知るために、2つの最初の必要性:

  1. 位置の値が決定された場合、その後、この位置で、同じ行の、同じカラム、3×3のグリッドに、この値を満たすことができない、例えば、正方形(1,1)の位置は、次に、2であります最初の行、最初の列、3×3のグリッドは、2で埋めることができません。
  2. 他の位置がそれを埋めることができないので、もし行または列、またはフィラーの唯一の可能な位置であり、3×3の一方は(それが1である場合)、一つは、この位置で充填されなければならない;,


解決手順

  1. 、単独で記録された位置81 9×9の数「可能な値の配列」と呼ばれると仮定すると、三次元アレイを作成、それぞれの可能な位置のためのフィル値は、初期条件の下で、各位置の可能な値は、1~9であります各位置は、任意の番号1-9に充填する可能性を表します。

  2. 数独は、ロケーションが値を有する場合、この場所の可能な値は、例えば、正方形、(1,1)の値は2であると決定された、この値に更新され、それが三次元になり、各場所を横断し配列内の可能な値[1-9]から(1,1)の位置更新[2]に、すべての位置まで更新。

  3. 上記の規則を使用して1はプルーニングされています。

    (1):現在位置(i、j)に横断場合正方形を横切る第一の位置から、値が既に知られており、i行目、j列目、及びそれに対応する、配列の値を更新することが可能です3×3([I / 3×3、J / 3×3]、これは、3×3の最初の点である)すべての位置において、その値の値(i、j)の位置を除去することができます。

    (2):もし位置バイステップの可能な値のセット上の位置の値、例えば、初期位置(1,1)が1であることが、後に剪定唯一の可能な値、および後図9に示すように、上記のステップの除去、わずか3、および(1,1)の位置が3であるの値で完了しなければならないことにより、工程を経。その後、我々はルール再び1、即ち、第1行、最初の列、及びその3×3の対応を使用することができ、値は、グリッド3の全てを有していなくてもよいです。

    (3)最後のグリッドまで、最初のプルーニングは完了され、上記の規則1を使用して、各位置を通って向けます。

  4. 上記の規則を使用して2は、剪定されます。

    (1):A場合、各列の各行の統計、及び3×3のそれぞれ、回数、そのような統計値としてそれぞれの出現の回数は、1~9回ずつ出現を参照、各グリッドの最初の行であってもよいです値は、一度だけその位置が表示さの値を発生することがあり、この値を埋めるためには、例えば、最初の行、3この図では、唯一の(1,1)は、この位置を埋め、及び(1,1)が充填されてもよい3ある他の場所3を含んでいない可能性があるの値が3を満たすことができないからです。

    (2):前のステップによれば、このときの位置の値を決定するために、我々は、(1,1)3を充填することを決定するために、例えば、前のステップをルール1を使用することができ、その後、最初の行、最初の列、だけでなく、最初の3×3のグリッドの残りの部分のように、3を書き込むことができない、我々は、値の配列から、これらの位置に指定できる値は、この数3を削除してあります。

    (3)この時点で、それは、上記のステップ3の場合(3)表示されてもよいです。

  5. 2つのプルーニングルールが完了した後、数独は解決されていない完了し、あなたが最終的な解決策が見つかるまで、我々は唯一の、各位置の値が、1トライずつを列挙することができます:

    (1):我々は、各位置、初期状況に可能な値を格納するための3次元配列を作成するために、初めにあり、各位置について可能な値は1-9であるが、上記の二つのプルーニングルールの後、されてい多くのに加えて、

    (2):私たちは、DFSの深さ優先探索を使用し、この時点では、すべての位置の値を記入してみてください。上記剪定後、各位置の可能な値の数はいけない同じ、およびDFS探索の数を減らすために、我々は、ロケーション検索の最小可能値で開始すべきです。

    (3):9トラバースグリッドが完了された値を見つけるために、その位置の最小の可能な値(複数の第一のものを見つけることがあってもよい)、この位置での可能な値を満たすために彼の最初の試み、コールは、数2のように、アドレスとしないルールがある場合、現在の方向パッドを決定し、再び1およびルール2を支配した後、剪定、剪定が完了し、同じ行に現れます。いかなる不正な場所がない場合はそうである場合には、再び(3)、それは別のものを選択することができる場所はこの位置の値からこの値を満たすことができないことを示しています。

    (4):必ず、ステップ(3)を使用するすべての位置が決定されるまで、そして成功し、特定の位置は、それはすべての可能な値を埋めるために起こっている場合は、数独は、最終的な結果を得ることができない解くこと数独解決策はありません。

  6. 上記の手順の後、迅速に単独での解の数は、主にルールが大幅列挙、改善された効率の数を減らす、1,2-によってプルーニングされるからです。


必要な計算

  1. 既知の位置(i、j)は、最初のポイントである3×3の位置は、(I / 3×3、J / 3×3)/ 3×3の他に、の最初の分割を表します小数部、及びその後3によって、3の倍数です。
  2. 3行に* 3 3のそれぞれ、および3、(I / 3×3 + J / 3)であること、いくつかの最初に所属する3×3の位置を計算するための方法を既知の位置(i、j)は、I / 3この位置でI / 3×3 + J / 3が得られるように、3の最初のいくつかの、三列の3位に3×3のそれぞれを与えるために最初の数行3、J / 3の位置を与えます3×3の一部。


コード

pythonので、実験を完了するために、そのコードはPythonで書かれています:

# 此类用来表示搜索时,需要搜索的一个位置
# x,y为此位置的坐标,size为此位置的可能值的数量
class Node:
    def __init__(self, x, y, size):
        self.x = x
        self.y = y
        self.size = size

# 读取方式2,读取top95
def read_way2():
    # 从文件中读取初始数独
    value = [[0] * 10 for i in range(10)]
    # 读取文件2,3
    s = infile.readline();
    if s == '':
        return
    for i in range(9):
        for j in range(9):
            value[i][j] = int(s[i * 9 + j])
    return value

# 初始化函数
def init():
    value = read_way2()   # 读取top95
    # 初始化possibleValue,若当前位置有值,则其可能值就是这个值本身
    # 若没有值,则初始的可能值就是1-9
    possibleValue = [[[] for j in range(10)] for i in range(10)]
    for i in range(9):
        for j in range(9):
            if value[i][j] != 0:
                possibleValue[i][j] = [value[i][j]]
            else:
                possibleValue[i][j] = [1, 2, 3, 4, 5, 6, 7, 8, 9]

    return possibleValue

#####################################################################################################################

# 根据规则1进行剪枝
# 遍历所有的位置,找到已经确定的位置进行剪枝
def pruningByRule1(possibleValue):
    for i in range(9):
        for j in range(9):
            if len(possibleValue[i][j]) == 1:
                removeValueByRule1(i, j, possibleValue)    # 以当前位置为起点,移除重复的可能值


# 在规则1剪枝中,将同一区域中,已经确定的数移除
# 以(i,j)位置为起点,移除重复的可能值
def removeValueByRule1(i, j, possibleValue):
    # 与当前值在同一行或同一列的位置,可能值减去当前位置的值
    for k in range(9):
        # 从第i行中的可能值列表中,移除当前值
        confirmOneValueInRule1(i, k, possibleValue[i][j][0], possibleValue)
        # 从第i列中的可能值列表中,移除当前值
        confirmOneValueInRule1(k, j, possibleValue[i][j][0], possibleValue)

    # 与当前值在同3*3的位置,可能值减去当前位置的值
    for k in range(int(i / 3) * 3, int(i / 3) * 3 + 3):
        for l in range(int(j / 3) * 3, int(j / 3)* 3 + 3):
            confirmOneValueInRule1(k, l, possibleValue[i][j][0], possibleValue)

# 移除某个位置的可能值,并在移除后判断能否得到确定值
def confirmOneValueInRule1(i, j, num, possibleValue):
    if len(possibleValue[i][j]) == 1:
        return
    # 从当前位置的可能值中,移除已经确定的数
    if num in possibleValue[i][j]:
        possibleValue[i][j].remove(num)
    # 判断移除后,当前位置能否确定
    if len(possibleValue[i][j]) == 1:
        # 若当前位置确定,则以当前位置为基准进行移除操作
        removeValueByRule1(i, j, possibleValue)


###########################################################################################

# 根据规则2剪枝,判断同一个区域每个值可能出现的次数
# 若某个值可能出现的位置只有一个,表示这个值就在此位置
def pruningByRule2(possibleValue):
    # 统计第i行,数字j可能值出现了几次
    countX = [[0] * 10 for i in range(12)]
    # 统计第i列,数字j可能值出现了几次
    countY = [[0] * 10 for i in range(12)]
    # 统计第i个3*3,数字j可能值出现了几次
    countZ = [[0] * 10 for i in range(12)]

    # 统计各个区域可能值出现的次数
    for i in range(9):
        for j in range(9):
            for num in possibleValue[i][j]:
                countX[i][num] += 1
                countY[j][num] += 1
                countZ[i // 3 * 3 + j // 3][num] += 1

    # 判断哪些数字只出现了一次, 若只出现了一次的数字
    # 表示这个数字就是那个位置的答案
    for i in range(9):
        for j in range(1,10):
            # 若第i行数字j只出现了一次
            if countX[i][j] == 1:
                for k in range(9):  # 遍历第i行的每一列,判断这个唯一值出现在哪
                    confirmValueInRule2(i, k, j, possibleValue)

            # 若第i列数字j只出现了一次
            if countY[i][j] == 1:
                for k in range(9):  # 遍历第i列的每一列,判断这个唯一值出现在哪
                    confirmValueInRule2(k, i, j, possibleValue)

            # 若第i个3 * 3中,数字j的可能值只有一个
            if countZ[i][j] == 1:
                # 遍历第i个3*3的所有位置,判断这个唯一值出现在哪
                for k in range(i//3*3, i//3*3+3):
                    for l in range(i%3*3, i%3*3+3):
                        confirmValueInRule2(k, l, j, possibleValue)


# 判断当前位置是否包含某个数,包含则为此位置的答案
def confirmValueInRule2(i, j, singleNum, possibleValue):
    # 若当前位置已经确定值了, 直接返回
    if len(possibleValue[i][j]) ==1:
        return
    # 若当前位置包含唯一可能值,则这个位置的确定值就是它
    if singleNum in possibleValue[i][j]:
            possibleValue[i][j] = [singleNum]
            # 重新调用规则1
            removeValueByRule1(i, j, possibleValue)

###########################################################################################

# 递归搜索
def searchForPruning(node, possibleValue):
    # 若没有需要填值的点了,表示搜索结束,答案已出
    if node is None:
        return possibleValue

    # 获取当前位置的x,y坐标
    x = node[0]
    y = node[1]
    for num in possibleValue[x][y]:
        # 复制一份当前状态
        tempPossibleValue = copy.deepcopy(possibleValue)
        # 更新数据
        tempPossibleValue[x][y] = [num]
        # 调用规则1,2
        removeValueByRule1(x, y, tempPossibleValue)
        pruningByRule2(tempPossibleValue)

        # 调用规则1,2后,判断当前结果是否合法,若合法,则进行递归下一层
        if judge_result(tempPossibleValue):
            # 递归求解
            tempPossibleValue = searchForPruning(get_lowest_node(tempPossibleValue), tempPossibleValue)
            # 判断递归结果,若结果有返回值,则表示求解成功
            if tempPossibleValue is not None:
                return tempPossibleValue

# 获取当前可能值最小的位置
def get_lowest_node(possibleValue):
    minn = 100
    node = None
    for i in range(9):
        for j in range(9):
            # 若当前位置没有确定值,并且可能值的数量更少,则更新记录,
            if 1 < len(possibleValue[i][j]) < minn:
                minn = len(possibleValue[i][j])
                node = (i, j)
    return node


# 判断某个位置是否可以放某个值
def judge_result(possibleValue):
    # 标记某个数字是否出现
    countX = [[False] * 10 for i in range(12)]
    countY = [[False] * 10 for i in range(12)]
    countZ = [[False] * 10 for i in range(12)]

    # 统计各个区域可能值出现的次数
    for i in range(9):
        for j in range(9):
            if len(possibleValue[i][j]) == 1:
                # 若当前状态不合法,返回false
                if countX[i][possibleValue[i][j][0]] or countY[j][possibleValue[i][j][0]] or countZ[i // 3 * 3 + j // 3][possibleValue[i][j][0]]:
                    return False
                # 若合法,则标记已经确定的数字
                countX[i][possibleValue[i][j][0]] = True
                countY[j][possibleValue[i][j][0]] = True
                countZ[i // 3 * 3 + j // 3][possibleValue[i][j][0]] = True
    return True


# 判断某个位置是否可以放某个值
def judge_now_number(possibleValue, i, j, num):
    # 判断num在这一行和这一列是否被使用
    for k in range(9):
        if len(possibleValue[i][k]) == 1 and possibleValue[i][k][0] == num:
            return False
        if len(possibleValue[k][j]) == 1 and possibleValue[k][j][0] == num:
            return False
    # 判断num在这个3*3是否被使用
    for k in range(int(i / 3) * 3, int(i / 3) * 3 + 3):
        for l in range(int(j / 3) * 3, int(j / 3) * 3 + 3):
            if len(possibleValue[k][l]) == 1 and possibleValue[k][l][0] == num:
                return False
    return True

###########################################################################################


# 输出展示可能值列表
def display(possibleValue):
    for i in range(9):
        for j in range(9):
            print(possibleValue[i][j], end="---")
        print()
    print()

###########################################################################################

# 主函数
def main():
    start = time.time()
    c = 0
    # 主逻辑
    while True:
        # 调用初始化函数
        possibleValue = init()
        # 调用规则1剪枝
        pruningByRule1(possibleValue)
        # 调用规则2剪枝
        pruningByRule2(possibleValue)
        # display(possibleValue)

        possibleValue = searchForPruning(get_lowest_node(possibleValue), possibleValue)
        # 判断是否有解
        if possibleValue is not None:
            display(possibleValue)
        else:
            print("无解")

        if not judge_result(possibleValue):
            print("结果异常")

        c += 1
        if c >= 90:
            break
    end = time.time()
    print(end - start)


# 读取本地存储文件
infile = open("D:/top95.txt")
main()


スプレッド

  数独は、アルゴリズムを解決する、これは上記の最速はありませんが、既知ありダンスのチェーン(ダンスリンク)アルゴリズム、より効率的には、我々が興味を持っていることを知ることができます。

おすすめ

転載: www.cnblogs.com/tuyang1129/p/12103726.html