`
evasiu
  • 浏览: 171194 次
  • 性别: Icon_minigender_2
  • 来自: 广州
博客专栏
Fa47b089-e026-399c-b770-017349f619d5
TCP/IP详解卷一>阅读...
浏览量:12646
社区版块
存档分类
最新评论

解数独——dancing link X

阅读更多
折腾了一个星期,发现自己的大脑真的是短路了,智力水平下降到历史最低点,竟然折腾了那么久才理解了dancing link。所幸经过几天的反思,终于列出了接下来应该做的几件事:
1. 产生数独题:
1.1 实现解数独的算法dlx
1.2 从数独终盘中随机选择一个cell,判断该cell是否可以挖掉而不会造成解不唯一
2. 用pygame实现基本界面

今天完成了1.1的编码,借此总结一下。
老实说,我也不明白为什么dancing link X会花了我那么多时间去理解,尤其是当我最后理解了它并用三种方法把它实现了之后。也许是Donald 26页的论文[1]把我吓到了,也许是我真的变笨了,或者老了。。

在那篇26页的论文里,其实Donald主要讲了两件事(我关心的两件事,后面的我也没有仔细看):
1. 解决exact-cover问题的算法,也即Dancing link X里的那个算法X,Donald说他暂时没有想到一个更好的名字,所以管它叫algorithm X
2. 算法X的实现上的一些技巧,也即著名的dancing link,也就是说,dancing link并不是一种算法,它只是一种技巧而已~

Exact-Cover问题:
问题描述:给定一个0/1矩阵(一般是稀疏矩阵),选出一些行,使得由这些行构成的新矩阵中每一列有且仅有一个1。换个说法,把列看成域(universe)中的元素,把行看成域里的一个子集,exact-cover问题就是找出一些不相交的子集,它们的并集覆盖整个域。

问题举例:
有如下0/1矩阵:



矩阵的行(1,4,5)构成的新矩阵中每一行每一列都有且仅有一个1.

解决方案:
我们知道,当我们选择某一行时,其他与该行在某列上同时有一个1的行都将不能再入选。也就是说,这些行在下一次遴选前都将被删除,而相应的列也可以被删除,因为这些列已经有了1了。也就是说,我们可以通过删除矩阵的行、列来缩小问题的规模,直到最终矩阵为空,说明我们已经找到了一个合法的解。假如矩阵出现没有1的列,说明这个问题无解,因为它说明这个列没有办法得到一个1.下面是Donald总结的算法流程:

Exact_Cover(A):
	if A is empty:
		problem solve, return
	else pick a column c:
		choose a row, r, such that A[r,c]=1
		include r in the partial solution
		for each j such that A[r,j]=1:
			delete column j from matrix A
			for each i such that A[i,j]=1:
				delete row i from matrix A
		repeat this algorithm recursively on the reduced matrix A


上面的描述便是前面提到的algorithm X。它其实是一个深搜问题。在行4,算法选择一个非确定性的(nondeterministic)行r,这将形成搜索树上的不同分枝,例如上面的例子我们确定性地选择列1做为我们的初始入口,选择行2或选择行4将形成搜索树的两个分支,我们首先沿着行2这个分枝搜索下去,如果找不到解,将一步一步回溯,最后返回root,然后再从行4这个分枝搜索。在这个过程中,算法将花费很多时间在矩阵的操作(删除行/列)上。

Donald在文章的开篇就提出了一件很是让人感到aha~!的事情:
假设x指向双向链表的一个元素,x.left指向x的左元素,x.right指向x的右元素,当我们决定删除x时,可以做如下操作:
x.left.right = x.right
x.right.left = x.left

而下面这两句精练的对称的语句将重新将x放回链表上:
x.left.right = x
x.right.left = x

是不是很美妙呢?而这,便是dancing link的精华了。

让我们换个数据结构来表现一个稀疏的0/1矩阵,形象一点,就是下图这样的表示:



每个矩阵元素,我们称之为data object,有四个指针,分别指向其上、下、左、右元素,矩阵的每一行、每一列都是一个双向循环链表,每一列还连带一个list header,我们称之为column object,以方便我们进行列的删除操作。column object同样也是双向链表中的一部分。每个data object除了那四个指针外,还带有一个column object的引用,以方便对该列进行删除。而column object则还另外包括一个size元素,说明该列总共有多少个元素。还记得我们前面提到的算法不?我们每次选一个列c,该c拥有的每一个行都将成为一个分支。实际上我们做的事情是在众多未知数中先假设某个未知数,从而缩小搜索的空间,再在未知数中做新的假设,,直到最后所有假设都符合题目限制于是成功获得结果或者某个限制被打破从而搜索失败必须回溯。该列有多少个元素将决定了搜索树在该子根下将有多少个分支,那我们当然希望越在上层的分枝越少就越好,(因为随着树的深入,可能很多未知量都变成已知的),因此就有了这个size,以方便我们进行列的选择。由column object组成的双向横向链表还包含一个root,用来标志矩阵是否为空,当root.right==root时。上面的算法通过改用这个数据结构,可以重新描述如下:
search(k):
	if root.right == root:
		solution found, return true or print it~
	{ as we've mentioned before, choose one column that has least elements}
	choose a column c 
	cover column c	{remember c is a column object}
	for each r <- c.down, c.down.down, ..., while r!=c
		solution[k]=r
		for each j <- r.right, r.right.right, ..., while j!=r
			cover column j
		if search(k+1):
			return true
		{doing clean up jobs}
		for each j <- r.left, r.left.left, ..., while j!=r
			uncover column j	
	{if we get here, no solution is found}
	uncover column c
	return false


cover跟uncover的操作如下:
cover( column object c ):
	c.left.right = c.right
	c.right.left = c.left
	for each i <- c.down, c.down.down, ..., while i!=c:
		for each j <- i.right, i.right.right, ..., while j!=i:
			j.down.up = j.up
			j.up.down = j.down
			j.column.size--

uncover( column object c ):
	for each i <- c.up, c.up.up, ..., while i!=c:
		for each j <- i.left, i.left.left, ..., while j!=i:
			j.down.up = j
			j.up.down = j
			j.column.size++
	c.left.right = c
	c.right.left = c


是不是相当简洁呢?对于上面的那个例子,下图是cover了column A后的矩阵:



当我们选择了行2后,将cover column D跟G,下面是cover了D跟G后的矩阵:



Dancing link使矩阵的行/列删除变得相当快捷,有如弹弹手指般容易。实现起来也相当地容易。不过由于我对python还是很不熟,而当时又感觉自己没有弄懂dancing link的样子,于是我只好从自己比较熟悉的c++开始编码了,于是就有了下面两个版本的dlx:
C++封装做得不是很好,呵呵。
//ExtractCoverMatrix.h
#include<iostream>
using namespace std;

struct Node
{
	Node* left, *right, *up, *down;
	int col, row;
	Node(){
		left = NULL; right = NULL;
		up = NULL; down = NULL;
		col = 0; row = 0;
	}
	Node( int r, int c )
	{
		left = NULL; right = NULL;
		up = NULL; down  = NULL;
		col = c; row = r;
	}
};

struct ColunmHeader : public Node
{
	int size;
	ColunmHeader(){
		size = 0;
	}
};

class ExactCoverMatrix
{
	public:
		int ROWS, COLS;
		//这是存储结果的数组
		int* disjointSubet;
		//接收矩阵其及维度
		ExactCoverMatrix( int rows, int cols, int** matrix );
		//释放内存
		~ExactCoverMatrix();
		//在行r列c的位置上插入一个元素
		void insert( int r, int c );
		//即我们的cover和uncover操作了
		void cover( int c );
		void uncover( int c );
		//即我们的algorithm X的实现了
		int search( int k=0 );
	private:
		ColunmHeader* root;
		ColunmHeader* ColIndex;
		Node* RowIndex;
};


//ExactCoverMatrix.cpp
#include "ExtracCoverMatrix.h"

ExtracCoverMatrix::ExtracCoverMatrix( int rows, int cols, int** matrix )
{
	ROWS = rows;
	COLS = cols;
	disjointSubet = new int[rows+1];
	ColIndex = new ColunmHeader[cols+1];
	RowIndex = new Node[rows];
	root = &ColIndex[0];
	ColIndex[0].left = &ColIndex[COLS];
	ColIndex[0].right = &ColIndex[1];
	ColIndex[COLS].right = &ColIndex[0];
	ColIndex[COLS].left = &ColIndex[COLS-1];
	for( int i=1; i<cols; i++ )
	{
		ColIndex[i].left = &ColIndex[i-1];
		ColIndex[i].right = &ColIndex[i+1];
	}

	for ( int i=0; i<=cols; i++ )
	{
		ColIndex[i].up = &ColIndex[i];
		ColIndex[i].down = &ColIndex[i];
		ColIndex[i].col = i;
	}
	ColIndex[0].down = &RowIndex[0];
	
	for( int i=0; i<rows; i++ )
		for( int j=0; j<cols&&matrix[i][j]>0; j++ )
		{
			insert(  i, matrix[i][j] );
		}
}

ExtracCoverMatrix::~ExtracCoverMatrix()
{
	delete[] disjointSubet;
	for( int i=1; i<=COLS; i++ )
	{
		Node* cur = ColIndex[i].down;
		Node* del = cur->down;
		while( cur != &ColIndex[i] )
		{
			delete cur;
			cur = del;
			del = cur->down;
		}
	}
	delete[] RowIndex;
	delete[] ColIndex;
}

void ExtracCoverMatrix::insert( int r, int c )
{
	Node* cur = &ColIndex[c];
	ColIndex[c].size++;
	Node* newNode = new Node( r, c );
	while( cur->down != &ColIndex[c] && cur->down->row < r )
		cur = cur->down;
	newNode->down = cur->down;
	newNode->up = cur;
	cur->down->up = newNode;
	cur->down = newNode;
	if( RowIndex[r].right == NULL )
	{
		RowIndex[r].right = newNode;
		newNode->left = newNode;
		newNode->right = newNode;
	}
	else
	{
		Node* rowHead = RowIndex[r].right;
		cur = rowHead;
		while( cur->right != rowHead && cur->right->col < c )
			cur = cur->right;
		newNode->right = cur->right;
		newNode->left = cur;
		cur->right->left = newNode;
		cur->right = newNode;
	}
}

void ExtracCoverMatrix::cover( int c )
{
	ColunmHeader* col = &ColIndex[c];
	col->right->left = col->left;
	col->left->right = col->right;
	Node* curR, *curC;
	curC = col->down;
	while( curC != col )
	{
		Node* noteR = curC;
		curR = noteR->right;
		while( curR != noteR )
		{
			curR->down->up = curR->up;
			curR->up->down = curR->down;
			ColIndex[curR->col].size--;
			curR = curR->right;
		}
		curC = curC->down;
	}
}

void ExtracCoverMatrix::uncover( int c )
{
	Node* curR, *curC;
	ColunmHeader* col = &ColIndex[c];
	curC = col->up;
	while( curC != col )
	{
		Node* noteR = curC;
		curR = curC->left;
		while( curR != noteR )
		{
			ColIndex[curR->col].size++;
			curR->down->up = curR;
			curR->up->down = curR;
			curR = curR->left;
		}
		curC = curC->up;
	}
	col->right->left = col;
	col->left->right = col;
}

int ExtracCoverMatrix::search( int k )
{
	if( root->right == root )
		return k;
	ColunmHeader* choose = (ColunmHeader*)root->right, *cur=choose;
	while( cur != root )
	{
		if( choose->size > cur->size )
			choose = cur;
		cur = (ColunmHeader*)cur->right;
	}
	if( choose->size <= 0 )
		return -1;
		
	cover( choose->col );
	Node* curC = choose->down;
	while( curC != choose )
	{
		disjointSubet[k] = curC->row;
		Node* noteR = curC;
		Node* curR = curC->right;
		while( curR != noteR )
		{
			cover( curR->col );
			curR = curR->right;
		}
		int res=-1;
		if( (res = search( k+1 ))>0 )
			return res;
		curR = noteR->left;
		while( curR != noteR )
		{
			uncover( curR->col );
			curR = curR->left;
		}
		curC = curC->down;
	}
	uncover( choose->col );
	return -1;
}

下面是python的实现版本,显然比c++要简洁好多:
#这段代码基本上来自http://code.google.com/p/mycodeplayground/
#我更多的是理解和欣赏python程序的写法。。。
class Node(object):

	'''data object in dancing link
	Basic element for a sparse matrix
	doubly linked in both horizontal and vertical direction'''

	def __init__( self, left = None, right = None, up = None, down = None,
			column_header = None, row_header = None ):
		self.left = left or self
		self.right = right or self
		self.up = up or self
		self.down = down or self

		self.column_header = column_header
		self.row_header = row_header

	def nodes_in_direction( self, dir ):
		'''Generator for nodes in different direction

		:Parameters:
			dir: str
				dir can be 'up', 'down', 'left', 'right'
		'''
		node = getattr( self, dir )
		while node != self:
			#print node.column_header.size
			yield node
			node = getattr( node, dir )

class ColumnHeader(Node):
	'''list header, or column object in dancing link
	has an extra element of size recording the number of data object in this column'''
	def __init__( self, *args, **kargs ):
		Node.__init__( self, *args, **kargs )
		self.size = 0

class RowHeader(Node):
	'''RowHeader is an extra element for dancing link to record the row no. of a row
	so that we can trace down which rows are picked up'''

	def __init__( self, rowno, *args, **kargs ):
		Node.__init__( self, *args, **kargs )
		self.rowno = rowno
		self.row_header = self
	
class DLXSolver(object):

	def __init__( self ):
		pass

	def solve(self, matrix, num_columns):
		'''solve the exact cover problem of a given matrix

		:Parameters:
			matrix is in the form as:
				{ k:[x0, x1, ..., xn] }
			where k is the line number while xi(0<=xi<num_columns) is the column 
			of the matrix where there is a 1

			num_columns:
				number of columns in the matrix
		
		:Return:
			solution: [k]
			solution is a list of line number that is picked
		'''
		self._partial_answer = {}
		self._k = 0
		self._construct( matrix, num_columns )
		self._search(0)

		return [self._partial_answer[k] for k in xrange(self._k)]

	def _construct( self, matrix, num_columns ):
		'''construct a matrix into a sparse matrix using doubly link list
		
		:Parameter:
			parameters are same as solve
		'''

		self.root = Node()
		self.column_headers = []

		#constructing column_headers
		for i in xrange(num_columns):
			new_column_header = ColumnHeader(left=self.root.left,
					right=self.root)
			self.root.left.right = new_column_header
			self.root.left = new_column_header
			self.column_headers.append( new_column_header )
		


		#inserting nodes into the sparse matrix
		for k in matrix:
			if not matrix[k]:
				continue

			column_id_sorted = sorted(matrix[k])
			column_header = self.column_headers[column_id_sorted[0]]
			column_header.size += 1

			new_row_header = RowHeader(k, up=column_header.up, down=column_header,
					column_header=column_header)
			column_header.up.down = new_row_header
			column_header.up = new_row_header

			column_id_sorted.pop(0)

			#constructing remaining nodes
			for i in column_id_sorted:
				column_header = self.column_headers[i]
				column_header.size += 1
				new_node = Node( left=new_row_header.left, right=new_row_header,
						up = column_header.up, down = column_header, 
						column_header = column_header, row_header = new_row_header )
				column_header.up.down = new_node
				column_header.up = new_node
				new_row_header.left.right = new_node
				new_row_header.left = new_node

	def _cover( self, column_header ):
		'''cover a column of a matrix as what donald said'''
		
		column_header.left.right = column_header.right
		column_header.right.left = column_header.left

		for eachNode in column_header.nodes_in_direction("down"):
			for node in eachNode.nodes_in_direction("right"):
				node.column_header.size -= 1
				node.up.down = node.down
				node.down.up = node.up

	def _uncover( self, column_header ):
		'''uncover a column of a matrix in the reverse order as cover do'''

		for eachNode in column_header.nodes_in_direction("up"):
			for node in eachNode.nodes_in_direction("left"):
				node.column_header.size += 1
				node.down.up = node
				node.up.down = node

		column_header.left.right = column_header
		column_header.right.left = column_header

	def _search( self, k ):
		if self.root.right == self.root:
			self._k = k
			return True


		column_header=min([header for header in self.root.nodes_in_direction("right")],
				key=lambda h:h.size)

		self._cover( column_header )
		for eachNode in column_header.nodes_in_direction("down"):
			self._partial_answer[k] = eachNode.row_header.rowno

			for node in eachNode.nodes_in_direction("right"):
				self._cover(node.column_header)

			if self._search( k+1 ):
				return True

			for node in eachNode.nodes_in_direction("left"):
				self._uncover(node.column_header)

		self._uncover(column_header)
		return False

'''test case for dlxSolver'''
if __name__ == "__main__":
	matrix = { 1:[2, 4,5],
			2:[0,3,6],
			3:[1,2,5],
			4:[0,3],
			5:[1,6],
			6:[3,4,6]}
	testSolver = DLXSolver()
	print testSolver.solve( matrix, 7 )


从数独到Exact-Cover问题:
那么,数独跟Exact-Cover有什么关系呢?我们来看看数独的限制:
1. 每个格里只能填一个数(1-9),有且只有一个
2. 每个数(1-9)在每一行里只能出现一次,有且只有一次
3. 每个数在每一列里只能出现一次,有且只有一次
4. 每个数在每一个块(block)只能出现一次,有且只有一次
这么多个“有且只有一次”显然在向我们发出强烈的信号:这是一个exact-cover问题!于是便有了下面的转换:
我们知道,在没有任何限制下,数独的第i行,第j列可以填入数字k,其中0<i,j,k<=9,因此我们实际上是从9^3个(i,j,k)中选择81个,同时使其满足以上4个限制。对于每一个(i,j,k),格(i,j)只能填一个数字的限制将表现为列(ci,cj)从k=[1,9]为1,所以我们只能选其中一行,也就是,在k=[1,9]中,只有一个(i,j,k0)被选中。对于每一个(i,j,k),行i只能出现一次k的限制将表现为列(ci, ck)从j=[1,9]为1,我们只能选择其中一行。对于限制3,4也是同样的道理。总共将需要81*4列,9^3行。这里[2]把这个问题说得很清楚。

下面分别是C++和python的数独求解:
#include<iostream>
#include "ExactCoverMatrix.h"
using namespace std;

const int N_Cell = 3;
const int N = 9;
const int OFFSET = 9*9;
const int ROWS = N*N*N;
const int COLS = N*N*4;

void solve_sudoku( int (*grid)[N] )
{
	//tranform a sudoku to a exact cover problem
	int** sudoku_matrix = new int*[ROWS];
	for( int i=0; i<ROWS; i++ )
		sudoku_matrix[i] = new int[5];
	for( int i=0; i<N; i++ )
		for( int j=0; j<N; j++ )
		{
			int b = (i/N_Cell)*N_Cell + (j/N_Cell);
			int r = i*N*N + j*N;
			for( int k=1; k<=N; k++ )
			{
				sudoku_matrix[r][0]=i*N+j+1;
				sudoku_matrix[r][1]=OFFSET+(i*N+k);
				sudoku_matrix[r][2]=OFFSET*2+(j*N+k);
				sudoku_matrix[r][3]=OFFSET*3+(b*N+k);
				sudoku_matrix[r][4]=-1;
				r++;
			}
		}
	SparseMatrix sudoku( ROWS, COLS, sudoku_matrix );
	//cover those have been filled
	for( int i=0; i<N; i++ )
		for( int j=0; j<N; j++ )
		{
			int k;
			if( k = grid[i][j] )
			{
				/* corresponding to 
				insert( r, i*N+j+1 );
				insert( r, OFFSET+(i*N+k) );
				insert( r, OFFSET*2+(j*N+k) );
				insert( r, OFFSET*3+(b*N+k) );
				*/
				int b = (i/N_Cell)*N_Cell + j/N_Cell;
				sudoku.cover( i*N+j+1 );
				sudoku.cover( OFFSET+( i*N+k ) );
				sudoku.cover( OFFSET*2+(j*N+k) );
				sudoku.cover( OFFSET*3+(b*N+k) );
			}
		}
	int solved=0;
	if( (solved=sudoku.search())>0 )
	{
		int c, r, k;
		for( int i=0; i<solved; i++ )
		{
			r = sudoku.disjointSubet[i]/(N*N);
			c = (sudoku.disjointSubet[i]/N)%N;
			k = sudoku.disjointSubet[i]%N;
			grid[r][c]=k+1;
		}
		cout<<"here is the result: "<<endl;
		for( int i=0; i<N; i++ )
		{
			for( int j=0; j<N; j++ )
				cout<<grid[i][j]<<' ';
			cout<<endl;
		}
	}
	else
		cout<<"no solution for this sudoku"<<endl;
}

bool verify_puzzle( int (*grid)[N] )
{
	int line[N+1]={0};
	for( int i=0; i<N; i++ )
	{
		for( int j=0; j<=N; j++ )
			line[j] = 0;
		for( int j=0; j<N; j++ )
		{
			if( line[grid[i][j]] )
			{
				cout<<"row wrong: "<<i<<endl;
				return false;
			}
			else
				line[grid[i][j]] = 1;
		}
	}
	for( int i=0; i<N; i++ )
	{
		for( int j=0; j<N+1; j++ )
			line[j] = 0;
		for( int j=0; j<N; j++ )
		{
			if( line[grid[j][i]] )
			{
				cout<<"column wrong"<<endl;
				return false;
			}
			else
				line[grid[j][i]] = 1;
		}
	}
	for( int i=0; i<N_Cell; i++ )
		for( int j=0; j<N_Cell; j++ )
		{
			for( int x=0; x<=N; x++ )
				line[x] = 0;
			for( int k=0; k<N_Cell; k++ )
				for( int t=0; t<N_Cell; t++ )
				{
					int x = k+i*N_Cell;
					int y = t+j*N_Cell;
					if( line[grid[x][y]] )
					{
						cout<<"block wrong"<<endl;
						return false;
					}
					else
						line[grid[x][y]] = 1;
				}
		}
	return true;
}

int main()
{
	int puzzle[N][N]={
		{0, 0, 0, 0, 0, 3, 0, 8, 1},
		{2, 0, 0, 4, 0, 0, 0, 0, 0},
		{0, 5, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 2, 3, 0, 7, 0, 0},
		{0, 1, 0, 0, 0, 0, 0, 5, 0},
		{0, 0, 8, 6, 0, 0, 0, 0, 0},
		{7, 0, 0, 0, 0, 0, 4, 0, 0},
		{0, 9, 0, 0, 8, 0, 0, 0, 0},
		{0, 0, 0, 0, 5, 0, 2, 0, 0}};
	solve_sudoku( puzzle );
	if( !verify_puzzle( puzzle ) )
		cout<<"wrong answer!"<<endl;
		
}

from __future__ import division
from dlx_1 import DLXSolver

class SudokuSolver(object):

	digits = set(map(str, range(1,10)))

	GRIDOFFSET=0	#column 0~80 is for testing each cell has only one digit
	ROWOFFSET=81	#column 81~161 is for testing each line contain each number exactly once
	COLOFFSET=162	#column 162~242 is for testing each column contain each number exactly once
	BLKOFFSET=243	#colunm 243~323 is for testing each block contain each number exactly once
	COLUNMS = 324

	def __init__( self ):
		self.dlx_solver = DLXSolver()
	
	def solve( self, puzzle ):
		'''solve the given sudoku puzzle
		unfilled cell is represented with other symbols except 1~9
		'''
		dlx_matrix = self._construct(puzzle)
		result = self.dlx_solver.solve( dlx_matrix, self.COLUNMS )
		solved_sudoku=list(puzzle)
		for row, col, val in result:
			solved_sudoku[row*9+col]=val+1
		return solved_sudoku

	def _construct( self, puzzle ):
		'''construct a sudoku puzzle to a dlx_matrix'''
		linear_puzzle = ''.join(puzzle.split())
		if len(linear_puzzle) != 81:
			print 'invalid puzzle!'
			return

		dlx_matrix={}
		for i, c in enumerate(linear_puzzle):
			row, col = divmod(i,9)
			if c in self.digits:
				val = int(c)-1
				dlx_matrix[(row, col, val)]=self._get_ones(row, col, val)
			else:
				for val in range(9):
					dlx_matrix[(row, col, val)]=self._get_ones(row, col, val)
		return dlx_matrix

	def _get_ones(self, row, col, val):

		blk = (row//3)*3 + col//3
		ones = [row*9+col+self.GRIDOFFSET,
				row*9+val+self.ROWOFFSET,
				col*9+val+self.COLOFFSET,
				blk*9+val+self.BLKOFFSET]
		return ones

if __name__=="__main__":
	puzzle = "4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......"
	solver = SudokuSolver()
	res = solver.solve(puzzle)
	for i in range(9):
		print res[i*9: i*9+9]

最后最后,这里[3]提供了一个30行的dlx的python代码,非常的精致,建议大家都去看一看~!

__________________________________________
[1]Dancing Links, Donald E. Knuth(附件下载)
[2]http://cgi.cse.unsw.edu.au/~xche635/dlx_sodoku/
[3]http://www.cs.mcgill.ca/~aassaf9/python/algorithm_x.html
  • 大小: 11.4 KB
  • 大小: 73.2 KB
  • 大小: 75.9 KB
  • 大小: 76.7 KB
0
0
分享到:
评论

相关推荐

    MATLAB实现参数化重采样时频变换(PRTF)用于振动与声音信号故障诊断

    内容概要:本文详细介绍了参数化重采样时频变换(PRTF)在振动与声音信号故障诊断中的应用。首先通过仿真信号展示PRTF的基本原理,即通过非线性时间轴映射提高时频分辨率,特别是在处理非平稳信号时的优势。接着讨论了PRTF的具体实现步骤,包括重采样、时频分析、坐标系转换等关键技术点。文中还提供了多个实际案例,如齿轮箱故障诊断、压缩机阀片断裂检测、蝙蝠回声定位信号处理等,展示了PRTF在不同应用场景中的灵活性和有效性。此外,文章分享了一些实用经验和技巧,如窗函数选择、抗混叠滤波、多尺度融合等,帮助读者更好地理解和应用PRTF。 适合人群:具备一定MATLAB编程基础和技术背景的信号处理工程师、研究人员。 使用场景及目标:适用于处理非平稳信号,尤其是振动和声音信号的故障诊断。目标是提高时频分辨率,清晰呈现故障特征,从而快速准确定位故障源。同时,也为研究者提供了一种新的信号处理工具,拓展了传统时频分析方法的应用范围。 其他说明:PRTF虽然强大,但在某些情况下并非最佳选择,如处理稳态信号或需要极高频率分辨率的任务。因此,使用者应根据具体情况选择合适的工具。此外,由于PRTF计算量较大,实时性要求较高的场景需考虑硬件加速或其他优化手段。

    毕设课设-基于MATLAB的汽车出入库识别系统.zip

    基于MATLAB的汽车出入库识别系统是一份适用于毕业设计或课程设计的项目,它主要围绕车辆进出仓库的自动识别技术开发。该系统充分利用MATLAB这一强大的数学计算和图形处理软件,实现了汽车识别的核心功能。 项目主要包括以下几个关键部分: 1. **图像采集与预处理**:通过摄像头或传感器捕捉汽车的实时图像,对图像进行预处理,如灰度化、边缘检测或特征提取,提高后续识别的精度。 2. **目标检测与识别**:利用MATLAB的机器视觉工具箱,可能采用了模板匹配、特征点匹配(如SIFT、SURF或HOG)、或者现代的深度学习技术(如卷积神经网络CNN),来识别出汽车的特征。 3. **车牌识别**:针对汽车的车牌进行识别,这通常涉及到字符分割、识别和验证,可能结合了OCR(Optical Character Recognition)技术。 4. **数据分析与管理系统**:收集并分析出入库数据,用于优化仓库管理策略,如实时流量监控、车辆调度等。 5. **文档与代码完整性**:项目不仅提供了完整的工作流程和算法实现,还包含了详尽的README.md文档,以便使用者了解项目的结构和使用方法,以及注意事项。 这个系统的优势在于将理论知识应用到实际场景中,既锻炼了学生的编程能力,也展示了MATLAB在计算机视觉领域的实用性。通过下载和交流,有助于参与者提升自己的技术能力,并推动自动化仓储系统的研发和优化。

    (源码)基于51单片机的密码锁控制器.zip

    # 基于51单片机的密码锁控制器 ## 项目简介 本项目是一个基于51单片机的密码锁控制器,通过结合LCD显示器和键盘,实现了一个简单的密码输入与验证系统。该系统可以用于需要密码保护的应用场景,如门禁系统、保险箱等。用户可以通过键盘输入密码,系统会根据输入的密码进行验证,并通过LED灯显示验证结果。 ## 项目的主要特性和功能 1. LCD显示功能使用LCD显示器实时显示密码输入的相关信息。 2. 密码设置与修改用户可以设置和修改一个4位数字(09)的密码。 3. 超级用户密码系统内置一个超级用户密码“1234”,用于特殊权限操作。 4. 密码验证反馈密码输入正确时,系统会亮绿灯密码输入错误时,系统会亮红灯。 ## 安装使用步骤 ### 前提条件 假设用户已经下载了本项目的源码文件,并具备基本的单片机开发环境(如Keil等)。 ### 步骤 1. 解压源码文件将下载的源码文件解压到本地目录。

    (源码)基于Python和强化学习算法的智能体训练系统.zip

    # 基于Python和强化学习算法的智能体训练系统 ## 项目简介 本项目是一个基于Python和强化学习算法的智能体训练系统,旨在通过深度学习和策略优化技术,训练智能体在复杂环境中进行决策和行动。项目结合了多种强化学习算法,如TRPO(Trust Region Policy Optimization),并使用了如Pommerman这样的复杂环境进行训练和评估。 ## 项目的主要特性和功能 强化学习算法包括TRPO在内的多种强化学习算法,适用于连续动作空间的强化学习任务。 环境模拟使用Pommerman环境进行智能体的训练和评估,环境包含复杂的棋盘布局和动态变化的炸弹、火焰等元素。 预训练与微调支持预训练模型的加载和微调,加速训练过程。 多模型评估支持多个模型的同时评估,比较不同模型在相同环境下的表现。 状态抽象与特征提取通过状态抽象和特征提取,优化智能体的决策过程。

    制造业2022年制造业上市公司高质量发展:城市群与主要城市百强企业分布分析

    内容概要:本文档展示了2022年中国制造业上市公司百强企业在不同城市群和城市的分布情况。从城市群角度看,百强企业主要集中在长三角(19家)、粤港澳(16家)和京津冀(11家)三大国家级城市群,这些地区凭借强大的发展基础、完善的产业链和优越的营商环境成为制造业高质量发展的领头羊。从具体城市分布来看,深圳和北京各有10家企业上榜,上海有9家。其中,深圳以比亚迪、中兴等大企业为代表,在营收规模上位居全国第一;北京依托科技和人才优势支持企业发展;上海则在高端制造业特别是集成电路领域处于领先地位。 适合人群:对中国经济地理、制造业发展趋势感兴趣的读者,以及从事相关行业研究的专业人士。 使用场景及目标:①了解中国制造业区域布局和发展趋势;②为政策制定者提供参考依据;③为企业投资决策提供数据支持。 阅读建议:建议重点关注各城市群和城市的具体数据,结合当地产业特色和发展优势进行分析,以便更好地理解中国制造业的空间分布规律及其背后的原因。

    房地产营销策划 -湖南涟源博盛生态园年度营销方案.pptx

    房地产营销策划 -湖南涟源博盛生态园年度营销方案.pptx

    基于粒子群算法PSO的宽带消色差超透镜Matlab设计与FDTD仿真

    内容概要:本文详细介绍了利用粒子群算法(PSO)在Matlab中设计宽带消色差超透镜的方法及其FDTD仿真验证。首先,通过定义合理的初始参数范围和适应度函数,将超透镜的纳米结构参数(如纳米柱的直径、高度、周期)作为粒子的位置,采用PSO进行优化。适应度函数结合了预存的相位延迟查找表和实时FDTD仿真结果,确保优化过程中能够高效评估不同结构参数的效果。文中还讨论了惯性权重的动态调整、震荡因子的引入以及适应度函数中物理约束的添加,以提高优化效果并防止陷入局部最优。最终,通过FDTD仿真验证优化结果,展示了在可见光波段内的聚焦效率和焦斑尺寸的改进。 适合人群:从事光学设计、超材料研究、电磁仿真领域的科研人员和技术开发者。 使用场景及目标:适用于需要设计高性能宽带消色差超透镜的研究项目,旨在通过粒子群算法优化超透镜结构参数,减少色差并提高聚焦效率。 其他说明:文中提供了详细的Matlab代码片段和FDTD仿真设置示例,帮助读者更好地理解和实施该方法。此外,强调了在实际应用中需要注意的参数选择和物理约束,以确保设计方案的可行性和有效性。

    FLAC 3D中深基坑支护结构(冠梁+钢支撑+钻孔灌注桩)的数值模拟及优化技巧

    内容概要:本文详细介绍了利用FLAC 3D软件进行深基坑支护结构的数值模拟方法,特别是针对冠梁、钢支撑和钻孔灌注桩的组合支护结构。文章首先解释了钻孔灌注桩的建模要点,强调了桩土接触面参数设置的重要性。接着讨论了钢支撑的激活时机及其对支护系统的影响,指出合理的开挖步控制可以更好地模拟实际情况。对于冠梁,则着重于其与桩顶的正确耦合方式以及弯矩分布的监测。此外,还分享了一些实用的经验教训和技术细节,如避免常见的建模错误、优化参数选择等。 适合人群:从事岩土工程、地下结构设计的专业人士,尤其是有一定FLAC 3D使用经验的研究人员和工程师。 使用场景及目标:适用于需要精确模拟深基坑开挖过程中支护结构行为的工程项目,旨在提高数值模拟的准确性,为实际施工提供科学依据和支持。 其他说明:文中提供了大量具体的FLAC 3D命令示例和实践经验,有助于读者快速掌握相关技能并在实践中灵活运用。同时提醒读者关注模型验证的重要性,确保模拟结果能够真实反映工程实际状况。

    前端铺子开发者 前端杂货铺 小程序在线课堂+工具组件小程序uniapp移动端.zip

    前端铺子开发者 前端杂货铺 小程序在线课堂+工具组件小程序uniapp移动端

    Delphi 12.3控件之geniso(CD iso Generator)可启动光盘文件制作器可执行文件.zip

    Delphi 12.3控件之geniso(CD iso Generator)可启动光盘文件制作器可执行文件.zip

    (源码)基于Arduino的传感器应用项目.zip

    # 基于Arduino的传感器应用项目 ## 项目简介 这是一个基于Arduino开发的项目集合,主要聚焦于传感器应用及相关开发。通过此项目,您将能够了解并实践如何使用Arduino进行硬件编程,以实现对各种传感器的读取和控制。 ## 项目的主要特性和功能 ### 1. 传感器读取 此项目包含多个示例,可以读取不同类型的传感器数据,如温度、湿度、光线、压力等。 ### 2. 实时数据反馈 通过Arduino,项目能够实现实时读取传感器的数据并在某些媒介(如LED灯、LCD显示屏等)上进行反馈。 ### 3. 自动化控制 根据项目需求,可以实现基于传感器数据的自动化控制,例如自动开关灯光、调节风扇速度等。 ## 安装使用步骤 ### 1. 下载源码文件 ### 2. 安装Arduino IDE 确保您的计算机上安装了Arduino IDE,这是编写和上传代码到Arduino设备所必需的。 ### 3. 导入项目文件

    房地产活动策划 -2025商业地产脆皮打工人春日养生局(万物回春主题)活动策划方案.pptx

    房地产活动策划 -2025商业地产脆皮打工人春日养生局(万物回春主题)活动策划方案.pptx

    h5py-3.1.0-cp37-cp37m-manylinux1_x86_64.whl

    该资源为h5py-3.1.0-cp37-cp37m-manylinux1_x86_64.whl,欢迎下载使用哦!

    基于Comsol仿真的远场涡流检测技术及其在工业探伤中的应用研究

    内容概要:本文详细介绍了利用Comsol软件进行远场涡流检测仿真的方法和技术要点。首先构建了一个二维轴对称模型,模拟了线圈和含缺陷铁磁管道之间的相互作用。文中强调了空气域大小、材料参数设置以及频率选择对检测效果的重要影响。通过调整不同的仿真参数如频率、线圈位置等,探讨了它们对磁场强度和相位变化的影响规律。此外,还分享了一些提高仿真效率的经验,例如合理的网格划分策略和参数化扫描的应用。最后指出远场涡流检测在工业探伤领域的潜在价值,特别是在检测埋地管道内部缺陷方面的优势。 适合人群:从事无损检测、电磁场仿真等相关工作的科研人员和技术工程师。 使用场景及目标:适用于希望深入了解远场涡流检测原理并掌握具体实施步骤的研究者;旨在为实际工程项目提供理论支持和技术指导。 其他说明:文中提供了大量实用的操作技巧和注意事项,有助于读者快速上手并在实践中优化自己的仿真流程。

    (源码)基于STM32F10x微控制器的综合驱动库.zip

    # 基于STM32F10x微控制器的综合驱动库 ## 项目简介 本项目是一个基于STM32F10x系列微控制器的综合驱动库,旨在为开发者提供一套全面、易于使用的API,用于快速搭建和配置硬件资源,实现高效、稳定的系统功能。项目包含了STM32F10x系列微控制器的基本驱动和常用外设(如GPIO、SPI、Timer、RTC、ADC、CAN、DMA等)的驱动程序。 ## 项目的主要特性和功能 1. 丰富的外设驱动支持支持GPIO、SPI、Timer、RTC、ADC、CAN、DMA等外设的初始化、配置、读写操作和中断处理。 2. 易于使用的API接口提供统一的API接口,简化外设操作和配置,使开发者能够专注于应用程序逻辑开发。 3. 全面的时钟管理功能支持系统时钟、AHB时钟、APB时钟的生成和配置,以及时钟源的选择和配置。 4. 电源管理功能支持低功耗模式、电源检测和备份寄存器访问,帮助实现节能和延长电池寿命。

    MACHIN3tools-1.0.1

    MACHIN3tools_1.0.1

    丰田功率分流混合动力系统Simulink建模及经济动力性仿真分析

    内容概要:本文详细介绍了丰田功率分流混合动力系统(如普锐斯)的Simulink分析模型及其经济性和动力性仿真的全过程。首先解析了该系统独特的双电机加发动机构型以及行星排耦合机制,接着阐述了Simulink模型的具体构建步骤,包括初始化参数设定、各模块的选择与配置。文中提供了多个代码示例,展示如何模拟不同工况下的动力输出和能耗情况,并强调了模型的高精度和实用性。此外,还探讨了模型的可扩展性和版本兼容性,以及一些关键的技术细节,如行星齿轮参数设定、能量管理模式、能耗计算方法等。 适合人群:从事混合动力技术研发的工程师和技术爱好者,尤其是对丰田THS系统感兴趣的读者。 使用场景及目标:①用于研究和开发新型混合动力系统;②为现有混合动力系统的改进提供参考;③作为教学工具,帮助学生理解和掌握混合动力系统的工作原理和仿真技术。 其他说明:该模型基于MATLAB 2021a版本构建,具有良好的版本兼容性和模块化设计,便于参数调整和功能扩展。同时,模型经过严格的验证,确保仿真结果与实际情况高度一致。

    (源码)基于Vue 3和Element Plus框架的Vite前端快速开发工程.zip

    # 基于Vue 3和Element Plus框架的Vite前端快速开发工程 ## 项目简介 本项目是一个基于Vue 3和Element Plus框架的项目模板,运用Vite作为前端开发工具,集成了Vue Composition API、Vue Router等常用技术栈。其目的在于简化前端开发流程,提升开发效率,提供了路由系统、组件自动化加载、状态管理、布局系统、CSS引擎等丰富功能,同时支持国际化、API自动加载,还集成了单元测试、端到端测试以及可视化调试与预览等功能。 ## 项目的主要特性和功能 1. 基于Vue 3和Element Plus构建,具备丰富组件库和UI样式。 2. 采用Vite开发工具,支持快速开发迭代。 3. 基于文件的路由系统,便于页面管理。 4. 组件自动化加载,简化开发流程。 5. 使用Pinia进行状态管理,方便应用状态维护。 6. 布局系统,方便页面布局管理。 7. 支持UnoCSS高性能即时原子化CSS引擎。

    upload2025.04.17-2.zip

    upload2025.04.17-2.zip

Global site tag (gtag.js) - Google Analytics