zoukankan      html  css  js  c++  java
  • DacningLinks实现

    本文简单分析DancingLinks实现中的数据结构设计,给出了精确覆盖问题及其扩展问题的代码。并应用于数独问题。




    先简单描写叙述一下精确覆盖问题:
    给定一个N*M的01矩阵,从中选中若干行,这些行向量相加后每一个分量的值都是1。这种行向量集合称为对列的一个精确覆盖。问题可能是找到一个解。或者找到解使得行向量的个数最少。



    假设这些行向量相加后每一个分量的值都大于或等于1。我们称这个行向量集为列的一个覆盖。问题是怎样找到最小覆盖。


    更准确的请參考:
    http://zh.wikipedia.org/wiki/%E8%88%9E%E8%B9%88%E9%93%BE
    http://zh.wikipedia.org/wiki/%E7%B2%BE%E7%A1%AE%E8%A6%86%E7%9B%96%E9%97%AE%E9%A2%98

    数据结构定义:

    const int maxn = 505;
    struct Cell
    {
    	int u, d, l, r;
    	int x, y;
    };
    
    
    Cell cell[maxn*(maxn+6)];
    int top;
    int cols[maxn], rows[maxn];
    int header;

    当中Cell表示一个值为1的元素。成员x,y表示了所在的行和列,而其他成员表明了在其上下右右的结点id。

    由于是静态分配的cell,所以仅仅须要一个整数表明相应元素在cell中的id就可以。这种表示和直接用Cell* u;之类的表示没有本质差别。



    top表示cell中下一个可用的结点的id,每次须要一个新的结点时,仅仅须要return top++;因为是静态分配的cell数组,所以不会考虑结点的回收。

    cols[i]和rowss[j]分别指出了第一列和每一行的头结点。而header指出了整个矩阵的头结点。这样做的优点是我们知道行号或列号的时候,能够随机訪问相应的行,列。


    Cell中的x和y成员以及上述描写叙述的行列表的头结点,不是都须要的,在一些富有技巧的实现中,使得不用这些存储。




    再来看初始化:
    初始化提供两个函数init和add_element。init表示初始化一个n*m的矩阵。而add_element表示加入一个1元素。要求加入顺序是先按x从小到大。x同样的情况下y从小到大。

    由外界保证不反复加入。

    void init(int n, int m)
    {
    	top = 0;
    	header = top++;
    	memset(cell, 0, sizeof (cell[0]));
    	for (int i = 0; i < m; ++i)
    	{
    		const int me = top++;
    		int last = cell[header].l; cols[i] = me;
    		cell[last].r = me, cell[me].l = last;
    		cell[me].r = header, cell[header].l = me;
    		cell[me].y = i;cell[me].u = cell[me].d = me;
    	}
    	for (int i = 0; i < n; ++i)
    	{
    		const int me = top++; rows[i] = me;
    		int last = cell[header].u;
    		cell[last].d = me, cell[me].u = last;
    		cell[me].d = header, cell[header].u = me;
    		cell[me].x = i;cell[me].l = cell[me].r = me;
    	}
    }

    当中先为整个表分配了头结点,cell[header]和全部的列的头结点组成双向链表,其链接通过l和r成员来表示。

    相同的,cell[header]和全部的行的头结点组成双向链表,其链接通过u和d成员来表示。


    当中的两个for循环分配了列的头结点和行的头结点。以列的头结点初始化为例。在初始情况下cell[header].l = cell[header].r = header; 构成一个仅仅有一个结点的双向链表。我们有cell[header].l是该链表的最后一个结点的不变量。每当加入一个结点的时候,通过cell[header].l找到最后一个元素。然后新加结点和这个最后元素,以及用header标明的最開始的元素建立链接。

    须要注意的是每一个列的头结点的u和d成员须要初始化。

    考虑cols[i-1]是上一个分配的元素,也能够不利用cell[header].h。



    对行的头结点而言。算法是一样的。

    void add_element(int x, int y)
    {
    	const int me = top++;
    	cell[me].x = x, cell[me].y = y;
    	{
    		const int row_header = rows[x];
    		const int last = cell[row_header].l;
    		cell[last].r = me, cell[me].l = last;
    		cell[me].r = row_header, cell[row_header].l = me;
    	}
    	{
    		const int col_header = cols[y];
    		const int last = cell[col_header].u;
    		cell[last].d = me, cell[me].u = last;
    		cell[me].d = col_header, cell[col_header].u = me;
    	}
    }
    


    在add_element调用前,我们也有一个不变量,rows[x].h表示x行最后一个结点。cols[y].u表示y列最后一个结点。所以在加入时,仅仅须要分配一结点。和相应的行的头结点。行的最后一个元素,列的头结点,列的最后一个元素建立链接关系。


    紧接着看在精确覆盖问题中怎样选择一行:
    void disable_row(int id, int y)
    {
    	for (int i = cell[rows[id]].r; i != rows[id]; i = cell[i].r)
    	{
    		if (cell[i].y != y)
    		{
    			cell[cell[i].u].d = cell[i].d;
    			cell[cell[i].d].u = cell[i].u;
    		}
    	}
    }
    
    
    void select_row(int id)
    {
    	int p = rows[id];
    	for (int q = cell[p].r; q != p; q = cell[q].r)
    	{
    		const int col = cols[cell[q].y];
    		cell[cell[col].l].r = cell[col].r;
    		cell[cell[col].r].l = cell[col].l;
    		for (int i = cell[col].d; i != col; i = cell[i].d)
    		{
    			disable_row(cell[i].x, cell[q].y);
    		}
    	}
    }

    入口是select_row,id表示相应的行。


    当一行被选择的时候,这一行覆盖的全部列从须要覆盖的列的双向链表中删除。最好还是设当前删除了第j列。可是可能存在其他行x。也覆盖了第j列,这种x不应该再次被select。

    所以,对于这种j考虑全部可能的x,x行可能覆盖的全部列k。x行的元素从k列中删除(k=j的时候不必再删除,由于j列已经被覆盖了)。能够看出在select过程中。id行构成的双向链表没有发生变化。



    对于recover方法。事实上现刚好和select相反。



    另外再看一下一般覆盖中怎样选择一行:

    void multi_select_row(int id)
    {
    	int p = rows[id];
    	for (int q = cell[p].r; q != p; q = cell[q].r)
    	{
    		const int col = cols[cell[q].y];
    		cell[cell[col].l].r = cell[col].r;
    		cell[cell[col].r].l = cell[col].l;
    		for (int i = cell[col].d; i != col; i = cell[i].d)
    		if (i != q)
    		{
    			cell[cell[i].l].r = cell[i].r;
    			cell[cell[i].r].l = cell[i].l;
    		}
    	}
    }

    相同的。选择id行的时候。将其覆盖的列吕须要覆盖的列从双向链接中删除。对于被删除的列j。能够找到相应的能够覆盖该列的全部行。假设行不是当前正在覆盖j列的行id,那么把第j列从该行所能覆盖的列中删除。下次选择这个行的时候,就不会出现其覆盖了某列。可是该列已经被覆盖过的情况。

    在此基础上就得到精确覆盖(找到一个解)。一般(多重)覆盖的算法:
    int answers[maxn];
    bool exact_cover(int now)
    {
    	int who = cell[header].r;
    	if (who == header)
    	{
    		// 找到一个解
    		return false; // 须要遍历全部解时返回false,仅仅须要一个解时返回true
    	}
    	for (int i = cell[who].d; i != who; i = cell[i].d)
    	{
    		select_row(cell[i].x);
    		answers[now] = cell[i].x;
    		if (exact_cover(now+1))
    		{
    			return true;
    		}
    		recover_row(cell[i].x);
    	}
    	return false;
    }
    
    
    bool multi_cover(int now)
    {
    	int who = cell[header].r;
    	if (who == header)
    	{
    		// 找到一个解
    		return false; // 须要遍历全部解时返回false,仅仅须要一个解时返回true
    	}
    	for (int i = cell[who].d; i != who; i = cell[i].d)
    	{
    		multi_select_row(cell[i].x);
    		answers[now] = cell[i].x;
    		if (multi_cover(now+1))
    		{
    			return true;
    		}
    		multi_recover_row(cell[i].x);
    	}
    	return false;
    }


    最后通过解数独来看看精确覆盖的应用:

    一共同拥有81*4列:
    第一个81列中每一列表示数独的每个位置,该列被覆盖的含义是该位置上填了一个元素。
    第二个81列被分为9部分,每一部分9列。每一部分表示一行,一个部分中的9个分量表示这一行须要填的9个数。
    第三个81列和第二个81列类似,表示9列。
    第四个81列和第二个81列也类似。表示9个3*3的块。



    矩阵一共同拥有81*9行:
    第i行j列填上数字val后(val从0到8),相应于选择了第(i*9+j)*9+val行。



    在建立模型后还须要对精确覆盖算法进行优化:
    用av[y]表示当前能够覆盖第y列的行的个数。在覆盖时,假设存在某列仅仅有一个覆盖行,则优先选择使得这列先被覆盖。假设某列不存在覆盖行,则能够直接返回无解。

    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <algorithm>
    #include <numeric>
    #include <iostream>
    using namespace std;
    
    
    const int maxn = 1005;
    struct Cell
    {
    	int u, d, l, r;
    	int x, y;
    };
    Cell cell[maxn*(maxn+6)];
    int top;
    int cols[maxn], rows[maxn];
    int header;
    int av[maxn];
    
    
    static inline void init(int n, int m)
    {
    	top = 0;
    	header = top++;
    	memset(cell, 0, sizeof (cell[0]));
    	fill(av, av+m, 0);
    	for (int i = 0; i < m; ++i)
    	{
    		const int me = top++;
    		int last = cell[header].l; cols[i] = me;
    		cell[last].r = me, cell[me].l = last;
    		cell[me].r = header, cell[header].l = me;
    		cell[me].y = i;cell[me].u = cell[me].d = me;
    	}
    	for (int i = 0; i < n; ++i)
    	{
    		const int me = top++; rows[i] = me;
    		int last = cell[header].u;
    		cell[last].d = me, cell[me].u = last;
    		cell[me].d = header, cell[header].u = me;
    		cell[me].x = i;cell[me].l = cell[me].r = me;
    	}
    }
    
    
    static inline void add_element(int x, int y)
    {
    	const int me = top++;
    	cell[me].x = x, cell[me].y = y;
    	{
    		const int row_header = rows[x];
    		const int last = cell[row_header].l;
    		cell[last].r = me, cell[me].l = last;
    		cell[me].r = row_header, cell[row_header].l = me;
    	}
    	{
    		const int col_header = cols[y];
    		const int last = cell[col_header].u;
    		cell[last].d = me, cell[me].u = last;
    		cell[me].d = col_header, cell[col_header].u = me;
    	}
    	++av[y];
    }
    
    
    static inline void disable_row(int id, int y)
    {
    	const int row_header = rows[id];
    	for (int i = cell[row_header].r; i != row_header; i = cell[i].r)
    	if (cell[i].y != y)
    	{
    		cell[cell[i].u].d = cell[i].d;
    		cell[cell[i].d].u = cell[i].u;
    		--av[cell[i].y];
    	}
    }
    
    
    static inline void enable_row(int id, int y)
    {
    	const int row_header = rows[id];
    	for (int i = cell[row_header].l; i != row_header; i = cell[i].l)
    	if (cell[i].y != y)
    	{
    		cell[cell[i].u].d = i;
    		cell[cell[i].d].u = i;
    		++av[cell[i].y];
    	}
    }
    
    
    static inline void select_row(int id)
    {
    	const int p = rows[id];
    	for (int q = cell[p].r; q != p; q = cell[q].r)
    	{
    		const int col = cols[cell[q].y];
    		cell[cell[col].l].r = cell[col].r;
    		cell[cell[col].r].l = cell[col].l;
    		--av[cell[q].y];
    		for (int i = cell[col].d; i != col; i = cell[i].d)
    		{
    			disable_row(cell[i].x, cell[q].y);
    		}
    	}
    }
    
    
    static inline void recover_row(int id)
    {
    	const int p = rows[id];
    	for (int q = cell[p].l; q != p; q = cell[q].l)
    	{
    		const int col = cols[cell[q].y];
    		cell[cell[col].l].r = col;
    		cell[cell[col].r].l = col;
    		++av[cell[q].y];
    		for (int i = cell[col].u; i != col; i = cell[i].u)
    		{
    			enable_row(cell[i].x, cell[q].y);
    		}
    	}
    }
    
    
    int answers[maxn];
    int orz;
    bool exact_cover(int now)
    {
    	int who = cell[header].r;
    	if (who == header)
    	{
    		orz = now;
    		return true;
    	}
    	int best = 1000000;
    	for (int i = cell[header].r; i != header; i = cell[i].r)
    	if (av[cell[i].y] < best)
    	{
    		who = i;
    		best = av[cell[i].y];
    		if (best <= 1) break;
    	}
    	if (best == 0) return false;
    	for (int i = cell[who].d; i != who; i = cell[i].d)
    	{
    		select_row(cell[i].x);
    		answers[now] = cell[i].x;
    		if (exact_cover(now+1))
    		{
    			return true;
    		}
    		recover_row(cell[i].x);
    	}
    	return false;
    }
    
    
    // puzzle为输入。其元素是0到9,0表示该位置元素待定
    int puzzle[9][9];
    void solve()
    {
    	init(81*9, 81*4);
    
    
    	for (int i = 0; i < 9; ++i)
    	for (int j = 0; j < 9; ++j)
    	for (int val = 0; val < 9; ++val)
    	if (puzzle[i][j] == 0 || puzzle[i][j] == val + 1)
    	{
    		const int id = (i*9+j)*9+val;
    		const int t = i / 3 * 3 + j / 3;
    		add_element(id, i*9+j);
    		add_element(id, 81+i*9+val);
    		add_element(id, 81+81+j*9+val);
    		add_element(id, 81+81+81+t*9+val);
    	}
    
    
    	exact_cover(0);
    	// 假定exact_cover返回true
    	for (int i = 0; i < orz; ++i)
    	{
    		int id = answers[i] / 9;
    		int row = id / 9;
    		int col = id % 9;
    		int val = answers[i] % 9;
    		puzzle[row][col] = val+1;
    	}
    }

    数独问题的一个实例:
    http://cstest.scu.edu.cn/soj/problem.action?id=2982

    多重覆盖问题的一个实例:
    http://cstest.scu.edu.cn/soj/problem.action?

    id=3778

  • 相关阅读:
    网站数据库
    提笔不知道说啥
    预祝大家新年快乐
    又..
    明日出发
    吸气呼气
    网吧
    光阴似箭
    <转>生活中的各种食品安全问题
    老了吗?
  • 原文地址:https://www.cnblogs.com/gccbuaa/p/7202482.html
Copyright © 2011-2022 走看看