问题背景:某运动会设立N个比赛项目,每个运动员可以参加1-3个项目。试问如何安排比赛日程,既可以使同一运动员参加的项目不安排在同一单位时间进行,又使总的竞赛日程最短。
问题抽象:若将此问题抽象成数学模型,则归属于“划分子集”问题。N个比赛项目构成一个大小为n的集合,有同一运动员参加的项目则抽象为“冲突”关系。
解决问题:假设某运动会设有9个项目,A = {0, 1, 2, 3, 4, 5, 6, 7, 8},7名运动员报名参加的项目分别为:(1, 4, 8)、(1, 7)、(8, 3)、(1, 0, 5)、(3, 4)、(5, 6, 2)和(6, 4),则构成一个冲突关系的集合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)}(一对括号中的两个项目不能安排在同一单位时间)。“划分子集”问题即将集合A划分成k个互不相交的子集A1, A2, ... , Ak(k <= n),使同一子集中的元素均无冲突关系。
解决方法:利用队列先进先出的特点,将待划分的集合A中的所有元素放入一个队列中,然后依次取出元素放入一个待分配的组中,若当前元素与改组中已经入选的元素无冲突,则出栈,如果产生冲突则继续放在队列的尾部;当遍历考察一轮队列中的所有元素后,产生一个无冲突的子集,如此循环直到所有元素都被分配完成时结束分配。
源文件:main.cpp(重点在于子函数DividSubset的实现) SqQueue.h SqQueue.cpp
main.cpp文件:
#include <iostream>
#include "SqQueue.h"
#define ITEM 9 //ITEM宏表示待处理集合中元素的项数
using namespace std;
int R[ITEM][ITEM] = { //该矩阵用来存储待划分集合中元素之间的冲突关系,0代表不冲突,1代表冲突
{0, 1, 0, 0, 0, 1, 0, 0, 0},
{1, 0, 0, 0, 1, 1, 0, 1, 1},
{0, 0, 0, 0, 0, 1, 1, 0, 0},
{0, 0, 0, 0, 1, 0, 0, 0, 1},
{0, 1, 0, 1, 0, 0, 1, 0, 1},
{1, 1, 1, 0, 0, 0, 1, 0, 0},
{0, 0, 1, 0, 1, 1, 0, 0, 0},
{0, 1, 0, 0, 0, 0, 0, 0, 0},
{0, 1, 0, 1, 1, 0, 0, 0, 0}};
int result[ITEM]; //该数组用来存放分组后的结果
/***********************************************************
*功能:划分给定集合的无冲突子集
*输入:集合中元素关系集,集合中基本元素的个数,存储结果的数组
*时间:2016年10月17日
***********************************************************/
void DivideSubset(int R[][ITEM], int n, int result[])
{
int PreIndex = n, GroupIndex = 0; //PreIndex表示前一次出队列的元素序号,GroupIndex表示当前分配的组的编号
SqQueue SQ;
InitQueue(SQ); //初始化一个队列,长度与被划分集合的基本元素个数相同,本例中取9
for(int i = 0 ; i < n ; ++i) //该循环用来给分配的队列附上初值,该例中为(0-8)
{
EnQueue(SQ, i);
}
int currVal; //该变量用来表示当前待考察的队列中的一个元素
int clash[ITEM]; //该数组用来表示当前分配组的已经添加的元素与其它元素的关系,即是否产生冲突
while(!QueueEmpty(SQ)) //该循环用来处理队列中的每一个元素,知道所有元素都分配完成时结束
{
DeQueue(SQ, currVal); //取出一个元素进行处理
if(currVal <= PreIndex) //如果当前元素小于前一个,则表示队列已经循环遍历所有的元素,应该新建另一个组
{
++GroupIndex;
for(int i = 0 ; i < n ; ++i)
clash[i] = 0;
}
if(clash[currVal] == 0) //查询当前分配组的clash数组的值,当值为0时表示该元素没有与当前组中已经添加的元素产生冲突
{
result[currVal] = GroupIndex; //将当前元素编入该组
for(int i = 0 ; i < n ; ++i) //添加与被添加元素冲突的信息
clash[i] += R[currVal][i];
}
else
{
EnQueue(SQ, currVal); //如果该元素与当前组中的所有元素都冲突,将该元素继续入栈
}
PreIndex = currVal;
}
}
int main()
{
DivideSubset(R, ITEM, result);
for(const auto &e : result)
{
cout << e << " ";
}
cout << endl;
return 0;
}
SqQueue.h文件:
#ifndef SQQUEUE_H
#define SQQUEUE_H
#define MAXQSIZE 10 //队列的最大长度
typedef int QElemType;
typedef struct
{
QElemType *base; //初始化的动态分配存储空间
int front; //头指针,若队列不空,指向队列头元素
int rear; //尾指针,若队列不空,指向队列尾元素的下一个位置
}SqQueue;
bool InitQueue(SqQueue &Q); //按指定大小构造一个顺序空队列Q
int QueueLength(SqQueue &Q); //返回Q的元素的个数,即队列长度
bool EnQueue(SqQueue &Q, QElemType e); //插入元素e为Q的新的队尾元素
bool DeQueue(SqQueue &Q, QElemType &e); //对头元素出队列
void PrintSqQueue(SqQueue &Q); //打印顺序队列
bool QueueEmpty(SqQueue &Q); //判断给定的队列是否为空队列
#endif /* SqQueue.h */
SqQueue.cpp文件:
#include "SqQueue.h"
#include <stdlib.h>
#include <iostream>
using namespace std;
bool InitQueue(SqQueue &Q)
{
Q.base = (QElemType*)malloc(MAXQSIZE*sizeof(QElemType));
if(Q.base == nullptr)
return false;
Q.front = Q.rear = 0;
return true;
}
int QueueLength(SqQueue &Q)
{
return (Q.rear - Q.front + MAXQSIZE) % MAXQSIZE;
}
bool EnQueue(SqQueue &Q, QElemType e)
{
if(((Q.rear + 1) % MAXQSIZE) == Q.front)
return false;
Q.base[Q.rear] = e;
Q.rear = (Q.rear + 1) % MAXQSIZE;
return true;
}
bool DeQueue(SqQueue &Q, QElemType &e)
{
if(Q.rear == Q.front)
return false;
e = Q.base[Q.front];
Q.front = (Q.front + 1) % MAXQSIZE;
return true;
}
void PrintSqQueue(SqQueue &Q)
{
int i = Q.front;
while(i != Q.rear)
{
cout << Q.base[i] << " ";
i = (i + 1) % MAXQSIZE;
}
cout << endl;
}
bool QueueEmpty(SqQueue &Q)
{
return (Q.front == Q.rear) ? true : false;
}