精确覆盖问题学习笔记(四)——算法优化

一、可优化的地方

   上一节实现的代码从运行效率上看,有两个重大缺陷:

1、每次递归调用前,需要将当前的状态矩阵拷贝一份,然后删除和当前行相交的所有行和列,得到新的矩阵,当矩阵非常大时,拷贝操作所需的时间和空间都很大。

2、在实际情况中,矩阵M一般是稀疏矩阵,0的个数远远多于1的个数,如果我们能只处理含1的单元格的话,将大大提高运行的时空效率。

二、优化所用到的数据结构

     以下优化算法是Knuth提出来的,其主要思想是用十字链表存储矩阵中的所有1.具体叙述如下

1、首先定义一个通用的十字链表节点类

struct CNode
{
	CNode* Left;      //左节点指针
	CNode* Right;     //右节点指针

	CNode* Up;        //上节点指针,对列节点,则为本列最后一个元素的指针
	CNode* Down;      //下节点指针,对列节点,则为本列第一个元素的指针
	
	int name;	         //对普通节点,为所在子集的编号,即行号;
			//对列节点,为列编号
};


2、然后从上面的节点类派生出一个列标题类,这个列标题多了一个size数据域,用于存储本列元素的个数,即

 struct CColumnHead:public CNode
{
int size;   //本列元素的个数
};

3、数据节点类,存储原矩阵里的每个1,

struct CGrid:public CNode
{
	CColumnHead* columnHead;   //列头节点的指针

};

4、建立一个头节点,指向列标题双向链表,以及存放可行解的变量。

set solution;      //存放可行解
CColumnHead* master;   //列头链表的头节点


 

三、核心函数

算法的核心是两个函数。

1、cover函数

cover函数,输入是一个列号c,函数流程的伪代码为

     (1)将列c整个的从列头链表中摘除

    (2)从列c的第一个数据节点开始向下遍历列c的所有数据节点r,都每个r都执行如下操作:

      a、从r 的右侧开始向右遍历r 所在行的其他节点cell,对每个cell执行如下操作

      b、设cell的所在列号为n,将cell从其所在列n中摘除,列标题n的size域的值减1

总体来说cover函数的目的是将和这个列相关的所有节点从矩阵中删除。

2、cover的执行实例

下面看一个实例,假设

矩阵的当前状态为,括号中的数字表示该列的含1个数,h为列头链表的头节点,开始指向第一列

h->  1(2) 2(2) 3(2) 4(3) 5(2) 6(2) 7(4) 
A    1    0    0    1    0    0    1    
B    1    0    0    1    0    0    0    
C    0    0    0    1    1    0    1    
D    0    0    1    0    1    1    0    
E    0    1    1    0    0    1    1    
F    0    1    0    0    0    0    1 

矩阵1 ——原始矩阵

现在要将列1及其相关节点从矩阵中摘除,则具体过程是

(1)将列1从矩阵中整体摘除,此时h指向列2,矩阵的状态为

h->  2(2) 3(2) 4(3) 5(2) 6(2) 7(4) 
A    0    0    1    0    0    1    
B    0    0    1    0    0    0    
C    0    0    1    1    0    1    
D    0    1    0    1    1    0    
E    1    1    0    0    1    1    
F    1    0    0    0    0    1 

矩阵2——摘除列1之后的矩阵
注:列1虽然从矩阵中整体摘除,但它的自身信息并未发生变化,列1的原始信息仍然为

     1(2)
A    1   
B    1   
C    0   
D    0   
E    0   
F    0   

可见,和列1相关的行有A和B

下一步的任务就是将行A和B的其他节点从矩阵2中删除,

(2) 首先在矩阵2中遍历行A的所有数据节点(从第一列的右侧向右开始遍历),显然,A和列4、列7中有交点,于是删除这两个节点,第4列和第7列的计数器值减1,

这样整个行A就被从矩阵2中移走,现在得到的矩阵为

h->  2(2) 3(2) 4(2) 5(2) 6(2) 7(3) 
B    0    0    1    0    0    0    
C    0    0    1    1    0    1    
D    0    1    0    1    1    0    
E    1    1    0    0    1    1    
F    1    0    0    0    0    1  

类似的,将行B的所有数据节点(只有第4列)从矩阵中摘除,各数据节点所在列的计数器值减1。此时矩阵M的新状态为

h->2(2) 3(2) 4(1) 5(2) 6(2) 7(3) 
C    0    0    1    1    0    1    
D    0    1    0    1    1    0    
E    1    1    0    0    1    1    
F    1    0    0    0    0    1   

矩阵4 - 移走行B之后的状态

从上面的过程可知,cover并未切断被删除元素与其他元素的横向和纵向联系,这样就被恢复矩阵的状态打下了基础。


3、uncpver函数

uncover 函数的输入也是一个列号c,它的功能与cover相反,是将列c插入到矩阵中,也就是将矩阵恢复到执行

cover(c)以前的状态。

uncover的处理流程也正好和cover相反。

函数流程的伪代码为

      (1)从列c的最后一个数据节点开始,向上遍历列c的所有数据节点r,都每个r都执行如下操作:

      a、从r的左侧开始向左遍历其 所在行的其他节点cell,对每个cell执行如下操作

      b、设cell的所在列号为n,将cell加入到列n中,列标题n的size域的值加1。

     (2)将列c整个加入到从列头链表中

4、uncover的执行实例

设现在的矩阵状态为

     2(2) 3(2) 4(1) 5(2) 6(2) 7(3) 
C    0    0    1    1    0    1    
D    0    1    0    1    1    0    
E    1    1    0    0    1    1    
F    1    0    0    0    0    1

要插入的列号为1,且列1的信息为

     1(2)
A    1   
B    1   
C    0   
D    0   
E    0   
F    0   

行A和B的信息为

       2    3    4    5   6     7
A    0    0    1    0    0    1   
B    0    0    1    0    0    0   

那么uncover的具体执行过程是

(1)首先将列4的最后一行B恢复到矩阵中,即将B的节点4恢复到矩阵中,且列4的计数器加1,矩阵状态为

h->  2(2) 3(2) 4(2) 5(2) 6(2) 7(3) 
B    0    0    1    0    0    0    
C    0    0    1    1    0    1    
D    0    1    0    1    1    0    
E    1    1    0    0    1    1    
F    1    0    0    0    0    1  

(2)然后将行A恢复到矩阵中,A的最后一个节点是7,所以先恢复列7的状态,矩阵为

h->  2(2) 3(2) 4(2) 5(2) 6(2) 7(4) 
A    0    0    0    0    0    1    
B    0    0    1    0    0    0    
C    0    0    1    1    0    1    
D    0    1    0    1    1    0    
E    1    1    0    0    1    1    
F    1    0    0    0    0    1 

然后将节点4恢复到矩阵中,矩阵的状态为

h->  2(2) 3(2) 4(3) 5(2) 6(2) 7(4) 
A    0    0    1    0    0    1    
B    0    0    1    0    0    0    
C    0    0    1    1    0    1    
D    0    1    0    1    1    0    
E    1    1    0    0    1    1    
F    1    0    0    0    0    1 

(3)最后将列1插入到矩阵中,矩阵为

     1(2) 2(2) 3(2) 4(3) 5(2) 6(2) 7(4) 
A    1    0    0    1    0    0    1    
B    1    0    0    1    0    0    0    
C    0    0    0    1    1    0    1    
D    0    0    1    0    1    1    0    
E    0    1    1    0    0    1    1    
F    0    1    0    0    0    0    1   

至此,矩阵已完全恢复到cover(1)之前的状态。
 

四、优化算法的基本步骤

算法的函数为search

1、如果矩阵为空,即 master->Right==master,则函数返回true

2、选择含1数目最少的列,设其列标题为c

3、执行cover(c)

4、 遍历列c的每一个数据节点r ,对每个r都执行(a)到(d)中的操作

      (a) 从r的右侧开始向右遍历r所在行的所有其他节点cell,设cell所在里的列头节点为n,执行操作cover(n)

      (b)  递归调用函数search,结果存到变量flag中

      (c)  如果上一步的结果为真,将行r加入到解集中,然后返回true

      (d)  如果步骤(b)的结果为false,则向从r的左侧开始向左遍历r所在行的所有其他节点cell,设cell所在里的列头节点为n

 ,执行操作uncover(n)

5、执行uncover(c)

6、函数返回false

你可能感兴趣的:(算法,C/CPP)