DLX算法及应用(一)DLX模板+解数独
来源:互联网 发布:淘宝购物车自动用卷么 编辑:程序博客网 时间:2024/05/16 07:12
DLX算法
原理:网上太多了,我就不写了。。
用途:解决精确覆盖问题
下面的代码是严格按照算法写的,其实对于这种没有数据域的链表,是可以用数组进行模拟的(见DLX算法及应用(二)Matlab解数独)。
代码中全部都用的是vector,更通用一些~
后半部分给出了一个解数独的实例,如何将数独转换为精确覆盖问题的文章也是网上一搜一大把。。。。我就不写了。。
2014-2-15 修改:还是写下如何转换吧
精确覆盖问题的矩阵表示:
给出这样一个矩阵,
100100110010000001101001011001100110100001找出该矩阵一组行的集合,使得该组集合中每列有且只有一个1
例如第2,4,6行
那么我们现在将数独问题转换成一个324列的精确覆盖问题:
数独有4个限制条件,将其分别转换为81列
每个格子都要填入数字---1到81列,表示数独中9*9=81个格子是否填入了数字。如果是,则选取的01行在该01列上为1每一行都要有1~9填入---81+1到81*2列,每9列就代表数独中的一行,如果该行有某个数字,则其对应的列上为1每一列都要有1~9填入---81*2+1到81*3列,每9列就代表数独中的一列每一宫都要有1~9填入---81*3+1到81*4列,每9列就代表数独中的一宫
对于已给出数字的格子,例如第 3 行第 5 列为7
那么就插入一行,其中,第23,106,205,259列为1,其他为0,分别代表:
23 = (3-1)*9 + 5 第 3 行第 5 列填入了数字106= 81 + (3-1)*9 + 7 第 3 行填入了7205= 81*2 + (5-1)*9 + 7 第 5 列填入了7259= 81*3 + (2-1)*9 + 7 第 2 宫填入了7
如果没有给出数字,那这个格子就有9种可能性,插入9行,其中如下列为1:
第一行:23,100,199,253第二行:23,101,200,254........第九行:23,108,207,261这样,构造的01矩阵,每行都有4个1。
在最多81*9行的01矩阵中,寻找一组精确覆盖(81行),就可以求解一个数独
代码如下
#include <time.h>#include <iostream>#include <limits.h>#include <vector>#include <fstream>using namespace std;struct Node{ Node *up, *down, *left, *right, *colRoot, *rowRoot;//上下左右四个指针以及指向行列对象的指针 int Num;//行对象特有,记录行数 int Size;//列对象特有,记录该列元素数 Node(int i = -1 ): Num(i),Size(0) {};//构造函数};class DLX{public: DLX(vector<vector<int> > &matrix, int m, int n); ~DLX() { delete Head;};//析构有点难写 void init(); void link(vector<vector<int> > &matrix); void cover(Node *cRoot); void recover(Node *cRoot); bool Search(int k = 0); vector<int> getResult() const { return result;} int getUpdates() const { return _updates;}private: Node *Head; vector<int> result;//结果存放在这里 int _row, _col, _updates;//记录行列数,更新次数};DLX::DLX(vector<vector<int> > &matrix, int m, int n) :_row(m),_col(n),_updates(0){ Head = new Node; Head->up = Head; Head->down = Head; Head->right = Head; Head->left = Head; init(); link(matrix);}void DLX::init(){ Node *newNode; for (int ix = 0; ix < _col; ++ix)//表头位置向后插入,构造列对象 { newNode = new Node; newNode->up = newNode; newNode->down = newNode; newNode->right = Head->right; newNode->left = Head; newNode->right->left = newNode; Head->right = newNode; } for (int ix = 0; ix < _row; ++ix)//表头位置向下插入,构造行对象 { newNode = new Node(_row-ix);//注意序号是_row-ix newNode->down = Head->down; newNode->up = Head; newNode->down->up = newNode; Head->down = newNode; }}void DLX::link(vector<vector<int> > &matrix){ Node *current_row, *current_col, *newNode, *current;//当前行对象,当前列对象,新节点,当前节点 current_row = Head; for (int row = 0; row < _row; ++row) { current_row = current_row->down; current_col = Head; for (int col = 0; col < _col; ++col) { current_col = current_col->right; if (matrix[row][col] == 0)//矩阵上为0的位置不设置节点 continue; newNode = new Node; newNode->colRoot = current_col; newNode->rowRoot = current_row;//设置当前节点对应的行列对象 newNode->down = current_col; newNode->up = current_col->up; newNode->up->down = newNode; current_col->up = newNode;//链接当前节点到列双向链尾端 if (current_row->Size == 0)//行双向链不应该把行对象包含进来 { current_row->right = newNode; newNode->left = newNode; newNode->right = newNode; current_row->Size++; } current = current_row->right;//设置当前节点(即行对象右的节点) newNode->left = current->left; newNode->right = current; newNode->left->right = newNode; current->left = newNode;//链接当前节点到行双向链尾端 current_col->Size++; } }}void DLX::cover(Node *cRoot)//覆盖列{ ++_updates; cRoot->left->right = cRoot->right; cRoot->right->left = cRoot->left;//删除该列对象 Node *i, *j; i = cRoot->down; while (i != cRoot) { j = i->right; while (j != i) { j->down->up = j->up; j->up->down = j->down; j->colRoot->Size--; j = j->right; } i = i->down; }}void DLX::recover(Node *cRoot)//整个算法的精髓!!{ Node *i, *j; i = cRoot->up; while (i != cRoot) { j = i->left; while (j != i) { j->colRoot->Size++; j->down->up = j; j->up->down = j; j = j->left; } i = i->up; } cRoot->right->left = cRoot; cRoot->left->right = cRoot;}bool DLX::Search(int k){ if (Head->right == Head)//表空,则成功找到一组行的集合 return true; Node *cRoot, *c; int minSize = INT_MAX; for(c = Head->right; c != Head; c = c->right)//根据启发条件选择列对象 { if (c->Size < minSize) { minSize = c->Size; cRoot = c; if (minSize == 1) break; if (minSize == 0)//有一列为空,失败 return false; } } cover(cRoot); Node *current_row,*current; for (current_row = cRoot->down; current_row != cRoot; current_row = current_row->down) { result.push_back(current_row->rowRoot->Num);//将该行加入result中 for (current = current_row->right; current != current_row; current = current->right) { cover(current->colRoot); } if (Search(k+1)) return true; for (current = current_row->left; current != current_row; current = current->left) recover(current->colRoot); result.pop_back();//发现该行不符合要求,还原result } recover(cRoot); return false;}vector<vector<int> > sudoku2matrix(string &problem)//将数独转换为01矩阵{ vector<vector<int> > matrix; for (int ix = 0; ix < 81; ++ix) { int val = problem[ix] - '0'; vector<int> current_row(324,0); if (val != 0) { current_row[ix] = 1; current_row[81 + ix/9*9 + val -1] = 1; current_row[162 + ix%9*9 +val -1] = 1; current_row[243 + (ix/9/3*3+ix%9/3)*9 +val -1] = 1; matrix.push_back(current_row); continue; } for (int jx = 0; jx < 9; ++jx) { vector<int> current_row2(324,0); current_row2[ix] = 1; current_row2[81 + ix/9*9 + jx] = 1; current_row2[162 + ix%9*9 +jx] = 1; current_row2[243 + (ix/9/3*3+ix%9/3)*9 +jx] = 1; matrix.push_back(current_row2); } } return matrix;}vector<int> matrix2sudoku(vector<vector<int> > &matrix, vector<int> result)//将01矩阵翻译为数独{ vector<int> solution(81); for (int ix = 0; ix < 81; ++ix) { vector<int> current = matrix[result[ix]-1]; int pos = 0, val = 0; for (int jx = 0; jx < 81; ++jx) { if (current[jx] == 1) break; ++pos; } for (int kx = 81; kx < 162; ++kx) { if (current[kx] == 1) break; ++val; } solution[pos] = val%9 + 1; } return solution;}void solve_sudoku(string &problem, ostream &os = cout){ clock_t start_1 = clock(); vector<vector<int> > matrix = sudoku2matrix(problem); clock_t end_1 = clock(); float time_1=(float)(end_1-start_1)/CLOCKS_PER_SEC; clock_t start_2 = clock(); DLX sudoku(matrix,matrix.size(),324); clock_t end_2 = clock(); float time_2=(float)(end_2-start_2)/CLOCKS_PER_SEC; clock_t start_3 = clock(); if (!sudoku.Search()) { os << "该数独无解!\n\n"; return; } clock_t end_3 = clock(); float time_3=(float)(end_3-start_3)/CLOCKS_PER_SEC; clock_t start_4 = clock(); vector<int> solution = matrix2sudoku(matrix, sudoku.getResult()); clock_t end_4 = clock(); float time_4=(float)(end_4-start_4)/CLOCKS_PER_SEC; for (int ix = 0; ix < 81; ++ix) os << solution[ix] << ((ix+1)%9 ? '\0' : '\n'); os << "构造01矩阵用时: " << time_1 << "s\n" << "构造链表用时: " << time_2 << "s\n" << "Dancing用时: " << time_3 << "s\n" << "Dancing更新次数: " << sudoku.getUpdates() << "次\n" << "翻译结果用时: " << time_4 << "s\n" << endl;}int main(){ string problem; ofstream outfile("solution.txt"); ifstream infile("problem.txt"); while (infile >> problem) { outfile << problem << endl; if (problem.size() != 81) { outfile << "数独不合法\n\n"; continue; } solve_sudoku(problem, outfile); }}
示例:
problem.txt文件内容:027380010010006735000000029305692080000000000060174503640000000951800070080065340000000520080400000030009000501000600200700000000300000600010000000000704000000030800000000003600000070090200050007000000045700000100030001000068008500010090000400运行后生成的solution.txt内容0273800100100067350000000293056920800000000000601745036400000009518000700800653405 2 7 3 8 9 4 1 68 1 9 4 2 6 7 3 54 3 6 7 5 1 8 2 93 7 5 6 9 2 1 8 41 9 4 5 3 8 2 6 72 6 8 1 7 4 5 9 36 4 3 2 1 7 9 5 89 5 1 8 4 3 6 7 27 8 2 9 6 5 3 4 1构造01矩阵用时: 0.002s构造链表用时: 0.002sDancing用时: 0sDancing更新次数: 324次翻译结果用时: 0s0000005200804000000300090005010006002007000000003000006000100000000007040000000304 1 6 8 3 7 5 2 99 8 2 4 6 5 3 7 17 3 5 1 2 9 4 6 85 7 1 2 9 8 6 4 32 9 3 7 4 6 1 8 58 6 4 3 5 1 2 9 76 4 7 9 1 3 8 5 23 5 9 6 8 2 7 1 41 2 8 5 7 4 9 3 6构造01矩阵用时: 0.002s构造链表用时: 0.002sDancing用时: 0sDancing更新次数: 419次翻译结果用时: 0s8000000000036000000700902000500070000000457000001000300010000680085000100900004008 1 2 7 5 3 6 4 99 4 3 6 8 2 1 7 56 7 5 4 9 1 2 8 31 5 4 2 3 7 8 9 63 6 9 8 4 5 7 2 12 8 7 1 6 9 5 3 45 2 1 9 7 4 3 6 84 3 8 5 2 6 9 1 77 9 6 3 1 8 4 5 2构造01矩阵用时: 0.002s构造链表用时: 0.002sDancing用时: 0.001sDancing更新次数: 8321次翻译结果用时: 0s
最后一个数独就是传说中芬兰数学家设计的最难数独,可以从更新次数看出,确实挺难。。
整个算法时间都浪费在了将数独转换成01矩阵和构造链表上,真正的DLX用时还是很短的!
(DLX算法中一次覆盖列的操作称为一次更新)
0 0
- DLX算法及应用(一)DLX模板+解数独
- DLX算法及应用(二)Matlab解数独-数组模拟链表
- DLX算法解数独游戏 Java版
- DLX算法及应用(三)杀手数独
- DLX精确覆盖解数独问题
- poj 3074 Sudoku dlx解数独
- poj 3076 Sudoku dlx解数独
- POJ 3076 Sudoku (DLX解数独)
- POJ 2676 Sudoku (DLX解数独)
- DLX 模板
- DLX模板
- DLX模板
- 模板--DLX
- DLX模板
- dlx模板
- poj 2676 dfs/DLX(数独)
- DLX算法
- DLX
- 常见Java面试题 – 第二部分:equals与==
- Career plan
- C# 操作 Word 修改word的高级属性中的自定义属性
- Java开发日期工具类,分享一下
- 利用java程序给图片打水印
- DLX算法及应用(一)DLX模板+解数独
- 关于GPS坐标转换(转)
- 基于MTK65xx平台lcm和背光驱动的编写
- 如何大幅提高DBCC CHECKDB/DBCC CHECKTABLE的性能
- VaadinTouchKit笔记——使用NavigatonManager
- 处理SQL Server 因修改过计算机名无法登陆的问题
- 超长显示省略号...,兼容各浏览器,适用于多行
- html5论坛苹果三星再次谈判
- Makefile模板之 sub.c main.c