队列应用之无冲突子集的划分

403 阅读3分钟

1、定义

      假设集合S有N个元素,N个元素之间存在一定的冲突关系,请问如何将N个元素划分成数量最小的子集且子集内元素间不存在冲突关系。

    数学模型:

  • 集合:S = { Mi | 0≤i<≤n };

  • 冲突关系的集合: C = { <ai, aj> | ai, aj ∈ S  };

  • 问题:如何将N个元素划分成子集数最少且子集内元素间不存在冲突关系。

2、解法---过筛法

  解决思路: 

  1. 从第一个元素考虑起,将与该元素不存在冲突的其他元素放入同一子集中;
  2. 再“过筛”出一批互不冲突的子集,依次类推,直到所有元素被划分;

   对应的数据结构与算法:

  • 队列Q: 存放等待过筛的元素的有序编号;
  • 变量prev: 存放队列Q前一次的队头元素的编号,初值为集合个数N;
  • n*n的二维数组M: 存放冲突关集合A的元素<ai, aj>,其中M[i][j] = 1,表示第i个元素与第j个元素间存在冲突,M[i][j] = 0,表示元素间无冲突关系;
  • 变量G:存放划分的子集;
  • 长度为N的数组clash:初值0,存放队头元素与当前子集元素的冲突关系;      

       1) clash[队头元素] = 0,表示队头元素与当前子集元素间不存在冲突,队头元素出队,加 入当前子集;当前子集每加入一个新元素m后,需将m在C中的冲突关系添加到clash数组中,换句话说,需将M中第i行与clash数组对应下标的元素进行累加;

       2)clash[队头元素] 0,表示队头元素与当前子集元素间存在冲突,队头元素出队,重  新加入队尾,等待下一个子集的筛分;

       3) 由于按照编号,队列里的元素是有序的,从出队后被放入队尾的元素依然是有序的,故当队头元素的编号< 队列前一次的队头元素的编号prev时,表明本轮筛选完成,此时应建立新的子集,并将clash初始化为0;

3 、典型应用

      一般问题:

       某运动会设立n个比赛项目,k个运动员参与项目组成的集合P,每个运动员可以参加qp个项目。试问:如何安排比赛日程可以使同一运动员参加的项目不安排在同一单位时间进行,又使得总的竞赛日程最短?

       建立模型:

  • 集合S:运动会项目;
  • 冲突关系:运动员参加的项目间存在冲突关系;

      假设n=9, m=1, n=3, k=7将项目进行依次编号,则S={ 0, 1, 2, 3, 4, 5, 6, 7, 8 }; 

      所有运动员参加的项目分别为:

     P ={ (1 ,4, 8)、 (1 ,7)、 (8,3)、 (1 ,0, 5)、 (3 ,4)、 (5, 6, 2)、 (6, 4) }, 根据P得到

     冲突关系:R = { (1, 4), (4, 8), (1, 8), (1, 7), (8, 3), (1, 0), (0, 5), (1, 5), (3, 4),(5,  6),                                 (5, 2), (6, 2), (6, 4) }, 如下表:

                        

代码实现:

// 队列Q: queue = [0, 1, 2, 3, 4, 5, 6, 7, 8]
const queue = new Array(9).fill(0).map((_, i) => i)

// 二维数组M
const p0 = [0, 1, 0, 0, 0, 1, 0, 0, 0]; // 项目0的冲突关系数组
const p1 = [1, 0, 0, 0, 1, 1, 0, 1, 1]; 
const p2 = [0, 0, 0, 0, 0, 1, 1, 0, 0];
const p3 = [0, 0, 0, 0, 1, 0, 0, 0, 1];
const p4 = [0, 1, 0, 1, 0, 0, 1, 0, 1];
const p5 = [1, 1, 1, 0, 0, 0, 1, 0, 0];
const p6 = [0, 0, 1, 0, 1, 1, 0, 0, 0];
const p7 = [0, 1, 0, 0, 0, 0, 0, 0, 0];
const p8 = [0, 1, 0, 1, 1, 0, 0, 0, 0];
const matrix = [p0, p1, p2, p3, p4, p5, p6, p7, p8];

function sieving(queue, matrix) {
    // 子集冲突数组clash   
   let clash = new Array(9).fill(0);   
   // 前一次对头元素 prev
   let prev = queue.length;
   // g:子集元素
   let g = [] 
   // 数组G,保存划分的子集
   const group = []
   const n = clash.length   
   while (queue.length) {        
      const head = queue.shift()        
      if (head <= prev) {            
          group.push(g)            
          g = []            
          clash = new Array(9).fill(0);        
      }        
      if (clash[head]) {            
          queue.push(head)        
      } else {            
          if (queue.length) {                
              g.push(head)                
              const c = matrix[head]               
              for (let i = 0; i < n; i++) {                    
                  clash[i] += c[i]                
              }            
          } else { // 当最后一个元素为一组                
              g.push(head)                
              group.push(g)            
          }        
      }       
       prev = head    
   }
    // slice(1): 排除第一次push的空数组    
   return group.slice(1)
}