算法分析与设计大作业--解数独

自己学校的作业,删了觉得太可惜了, 不如发出来一起学习

算法大作业

解数独

数独的定义

数独(shù dú)是源自18世纪瑞士的一种数学游戏。是一种运用纸、笔进行演算的逻辑游戏。玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个粗线宫(3*3)内的数字均含1-9,不重复 [1] 。

数独盘面是个九宫,每一宫又分为九个小格。在这八十一格中给出一定的已知数字和解题条件,利用逻辑和推理,在其他的空格上填入1-9的数字。使1-9每个数字在每一行、每一列和每一宫中都只出现一次,所以又称“九宫格”。

图形展示:

算法分析与设计大作业--解数独_第1张图片

算法的核心设计思路:

首先, 这是一道搜索类的题目, 采用 dfs 搜索会比较方便的写出代码

但是如果只有 dfs 来搜索所有的状态, 进而来判断答案是肯定不行的, 因为每一位都可以填写 1~9 ,即9个状态, 因而最坏的情况则是 9 * 9 * 9 * … * 9 O ( 9 n ) O(9^{n}) O(9n)的复杂度, 为指数级别

那么要优化这个情况, 则必须要进行剪枝, dfs + 剪枝优化 也称为回溯法. 虽然算法的最坏的时间复杂度依然不变, 但是通过优化, 可以使得算法很难达到得最坏的情况, 可以近似的看成一个可接受的复杂度.

那么本题用到的剪枝优化则是

  1. 位运算

    由于每放置一位均需要 check() 一次, 如果 check() 为真则可以放置当前的数字当前的位置

    因此需要对 check() 做一个优化:

    1. 设置一个 line[9], col[9]ceil[3][3] 数组若第 i 行可以放置数字 j, 则 line[i] >> j & 1 == 1 (即line[]的第i个元素的第j位为1), col[], ceil[][] 同理. ceil[i][j]表示第i行,第j列个单元格可以放置的数字

    2. 初始化 : 将line,col, ceil的每一个元素的前9位均设置为1

    3. 运算: 若第i行, j列存在了数字 k, 则line[i] -= 1 << k

      line[i] -= 1 << k;						// 第i行的第k位变为0
      col[j] -= 1 << k;							// 第j列的第k位变为0
      ceil[i / 3][j / 3] -= 1 << k;	// i, j所属的单元格的第k位变为0
      
  2. 排除等效冗余

    任意一个状态下,我们只需要找一个位置填数即可,而不是找所有的位置和可填的数字

    因此我们在dfs的时候若查找到了, 则返回true, 否则返回false

    bool Compute::dfs(int cnt)

    在递归时则是:

    if (dfs(cnt - 1)) return true;

  3. 优化搜索循序

    很明显,我们肯定是从当前能填合法数字最少的位置开始填数字, 即第i行,j列的合法状态的二进制中1的数量最少的一个

    当前的合法的状态为第i行的合法状态和第j列的合法状态和i,j所属单元格的合法状态的并集:
    line[i] & col[j] & ceil[i / 3][j / 3];

    然后选取其中的1所在的位置, 并获取它代表的数字

    • 小细节: 获取最小的1所在的位置有一个运算叫做lowbit运算, 用它就可以在 O ( 1 ) O(1) O(1)的时间以内获取当前最小的1所在的位,而不是用 O ( n ) O(n) O(n)的复杂度来遍历这个数字的每一位

    然后计算1的个数, 并取得个数最小的点, 然后遍历该点

代码实现:

compute.h

#pragma once
#include 

static class Compute {
public:
	Compute(const char*);
	Compute(std::string);
	
	operator const char* ();
	bool has_answer = false;
protected:
	int get(int, int);
	void draw(int, int, int, bool);
	void init();
	int lowbit(int);
	bool dfs(int);
private:
	const static int N = 9, M = 1 << N;
	
	int ones[M];
	int ceil[3][3], map[M];
	int line[N], col[N];
	char str[N * N + 1];
	int cnt = 0;

};

compute.cpp

#include "compute.h"

Compute::Compute(std::string temp) : Compute((const char *)temp.c_str()) {
	;
}

Compute::Compute(const char* temp) {
    for (int i = 0; i <= N * N; i++) str[i] = temp[i];
    for (int i = 0; i < N; i++) map[1 << i] = i;
    for (int i = 0; i < M; i++) {
        for (int j = 0; j < N; j++) {
            ones[i] += i >> j & 1;
        }
    }

    init();

    for (int i = 0, k = 0; i < N; i++)
        for (int j = 0; j < N; j++, k++) {
            if (str[k] != '.') {
                int t = str[k] - '1';
                draw(i, j, t, true);
            }
            else {
                cnt++;
            }

        }
    dfs(cnt);
}

Compute::operator const char* () {
    return str;
}



int Compute::get(int x, int y) {
    return line[x] & col[y] & ceil[x / 3][y / 3];
}

void Compute::draw(int x, int y, int t, bool is_set) {
    if (is_set) str[x * N + y] = t + '1';
    else str[x * N + y] = '.';

    int v = 1 << t;
    if (!is_set) v = -v;

    line[x] -= v;
    col[y] -= v;
    ceil[x / 3][y / 3] -= v;
}

void Compute::init() {
    cnt = 0;
    for (int i = 0; i < N; i++) {
        line[i] = (1 << N) - 1;
        col[i] = (1 << N) - 1;
    }

    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 3; j++) {
            ceil[i][j] = (1 << N) - 1;
        }
}

int Compute::lowbit(int x) {
    return x & -x;
}

bool Compute::dfs(int cnt) {
    if (cnt == 0) {
        has_answer = true;
        return true;
    }

    int minv = 10;
    int a = -1, b = -1;
    for (int i = 0; i < N; i++)
        for (int j = 0; j < N; j++) {
            if (str[i * N + j] == '.') {
                int state = get(i, j);
                if (ones[state] < minv) {
                    minv = ones[state];
                    a = i, b = j;
                }
            }

        }
    if (a == -1 || b == -1) return false;

    int state = get(a, b);
    if (state < 0 || state >((1 << N) - 1)) return false;
    for (int i = state; i; i -= lowbit(i)) {
        int t = map[lowbit(i)];
        draw(a, b, t, true);
        if (dfs(cnt - 1)) return true;
        draw(a, b, t, false);
    }
    return false;
}

数独.cpp

#include 
#include 
#include "Compute.h"
#include 
using namespace std;

void show(const char* temp) {
	printf("=======================================\n");
	for (int i = 0; i < 9; i++) {
		for (int j = 0; j < 9; j++) {
			char a = temp[i * 9 + j];
			if (a == '.') a = ' ';
			if ((j + 1) % 3 == 0)
				printf(" %2c |", a);
			else
				printf(" %2c ", a);

		}
		printf("\n");
		if ((i + 1) % 3 == 0)
			printf("=======================================\n");
	}
}


int main() {
	while (1) {
		char str[] = "4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......";
		cout << "请输入棋盘(若为空, 则输入 . )\n示例:";
		cout << str << endl;
		cin >> str;
		show(str);
		time_t start = clock();
		Compute ans = str;
		time_t ed = clock();
		if (ans.has_answer) {
			cout << "存在一组解为:" << endl;
			show(ans);
		}
		else {
			cout << "不存在一组解" << endl;
		}
		cout << "运算时间为: " << ed - start << " ms" << endl;
	}
	return 0;
}

程序运行图

算法分析与设计大作业--解数独_第2张图片

算法分析与设计大作业--解数独_第3张图片
算法分析与设计大作业--解数独_第4张图片

可以看到,运算时间还是很可观的

你可能感兴趣的:(算法学习,笔记,算法,深度优先)