运动会排程问题(划分子集问题)
(注:这是同济大学通信专业数据结构课程作业报告,在此记录数据结构的学习)
1.问题描述
在安排运动会比赛日程时,需要考虑如何安排比赛项目,才能使同一运动员参加的不同项目不在同一日进行,同时又使比赛总的日程最短。设运动会共有9个项目,每名运动员最多参加3个项目。
2.问题分析
这是典型的划分子集问题,可采用循环筛选算法。问题抽象为:给定一个集合A和集合间的冲突关系R={(ai,aj)|ai,aj∈A, i≠j},要求将A划分为互不相交的子集,使每个子集内元素不能冲突,且子集数尽可能少。
方法:先将集合A所有元素入队,以第一个元素开始,凡与第一个元素无冲突的元素划归一组,再将剩下的元素重新找出互不冲突的划归第二组,以此类推,直到所有的元素都进组。
3.输入、输出样例
输入:
A={1, 2, 3, 4, 5, 6, 7, 8, 9}
R={(2, 8), (9,4) , (2, 9) , (2, 1) , (2, 5) , (6, 2) , (5, 9) , (5, 6) , (5, 4) , (7, 5) , (7, 6) , (3, 7) , (6, 3)}
输出:
A1={1, 3, 4, 8} A2={2, 7} A3={5} A4={6, 9}
4.设计思路
首先要将集合中元素的冲突关系设置一个冲突矩阵,由一个二维数组R[1:n, 1:n]表示,若第i与第j 元素有冲突,则R[i, j]=1,否则R[i, j]=0。
循环队列cq[0:n-1],存放集合A的元素;数组Result[1:n]用以存放每个元素的分组号;newr[1:n]为工作数组。
第一步,集合A元素放入cq中,Result及newr置零,设置组号group=1。
第二步:元素逐个出队。将R中该元素对应行中的“1”拷入newr向量对应位置(表示与该元素冲突的元素)。继续出队,若出队的元素与当前组中元素冲突,则将其重新入队尾;若不冲突,则设置Result中该元素对应位置为1。
第三步:当队内元素依次出队后,建立新组,group增1,newr清零。重复上述操作,直至cq中front=rear,队空,运算结束。
5.数据结构(所有变量说明)
主要:
cq[1:n+1]:最大容量为n的循环队列(因循环队列中有一项为空)
R[1:n][1:n]:冲突矩阵
Result[1:n]:存放每个元素的分组号
newr[1:n]:工作数组
其他:
I:当前出队元素
Pre:上一个出队的元素
front/rear:排头/队尾指示器
6.算法描述(伪码)
PROCEDURE DIVISION (R, n, cq, newr, Result);
FOR k=0 TO n-1 cq[k]←k+1; //n个元素存入循环队列cq//
front←n-1; rear←n-1; //头尾指针赋初值//
FOR K=1 TO n newr[k]←0 //newr向量置初值//
group←1; pre←0; //group为当前组号,pre为前一个出队元素编号,初值为零//(pre←9);
fi=.t.
WHILE rear≠front.or.fi=.t. DO //队列非空//
Fi=.f.
front←front+1; IF front=n+1 THEN front←1;
I←cq[front]; //I为当前出队元素//
IF I<=pre THEN //重新开辟新组//
group←group+1; Result[I]←group; //记录组号//
FOR k=1 TO n newr[k]←R[I, k]
ELSE
IF newr[I]≠0 THEN //发生冲突元素,重新入队//
rear=rear+1; IF rear=n+1 THEN rear=1;cq[rear]←I
ELSE //可以分在当前组//
Result[I]←group;
FOR k=1 TO n
Newr[k]←newr[k]+R[I, k];
pre←I
END(WHILE);
7.测试用例和结果说明
8.设计及测试过程
一、问题抽象化;
二、构思并描述算法;
三、伪码描述;
四、编写真码;
五、调试运行、改bug;
六、代码修正。
9.评价和改进
算法优点:能够准确给出其中一种子集划分,对同类型问题(划分子集问题)都是行之有效的。
算法缺点:当有多种划分方法时,只能找到其中一种。且找到的解决方案未必是实际应用中的最优方案。
附:源程序
#include <iostream>
#define N 9
using namespace std;
class Que {
public:
Que(int n) {
data = new int[n];
M = n;
front = rear = 0;
for (int i = 0; i < M; i++)
data[i] = 0;
}
int pop();
void push(int);
bool empty() {
return rear == front;
}
~Que() {
delete[]data;
}
private:
int* data;
int front;
int rear;
int M;
};
int main()
{
int R[N][N] = { {0} };
int num, I, PJ[3], newr[N], result[N];
Que cq(N + 1);
cout << "请输入运动员人数:" << endl;
cin >> num;
cout << "下面请输入每名运动员参加的项目编号(每人最多参加3个项目);" << endl;
for (int i = 0; i < num; i++) {
cout << "请输入第" << i + 1 << "位运动员参加的项目编号(1~9),若不足3项请输入0结束:" << endl;
int j = 0;
for (j = 0; j < 3; j++) {
cin >> PJ[j];
if (PJ[j] == 0)break;
}
if (j == 2) {//参加了两个项目
R[PJ[0] - 1][PJ[1] - 1] = R[PJ[1] - 1][PJ[0] - 1] = 1;
}
if (j == 3) {//参加了三个项目
R[PJ[0] - 1][PJ[1] - 1] = R[PJ[1] - 1][PJ[0] - 1] = 1;
R[PJ[0] - 1][PJ[2] - 1] = R[PJ[2] - 1][PJ[0] - 1] = 1;
R[PJ[1] - 1][PJ[2] - 1] = R[PJ[2] - 1][PJ[1] - 1] = 1;
}
}
int group = 1, pre = 0, subI; //group:组号,pre:前一个出队的元素编号,subI:出队元素的下标
for (int i = 1; i <= N; i++) {
cq.push(i);
newr[i - 1] = 0;
}
while (!cq.empty()) {
I = cq.pop();
subI = I - 1;
if (I <= pre) { //重新开辟新组
group++;
result[subI] = group; //记录该项目组号
for (int k = 0; k < N; k++)
newr[k] = R[subI][k];
}
else {
if (newr[subI] != 0) {
cq.push(I); //元素冲突,重新入队
}
else {
result[subI] = group;
for (int k = 0; k < N; k++)
if (R[subI][k] == 1)newr[k] = 1;
}
}
pre = I; //记录上一个出队元素
}
for (int i = 1; i <= group; i++) {
cout << "第" << i << "天的项目安排为: ";
for (int j = 0; j < N; j++) {
if (result[j] == i)
cout << j + 1 << ' ';
}
cout << endl;
}
return 0;
}
int Que::pop()
{
if (front == rear) {
cout << "队空" << endl;
return 0;
}
else {
front = (front + 1) % M;
return data[front];
}
}
void Que::push(int a)
{
int origin = rear;
rear = (rear + 1) % M;
if (front == rear) {
cout << "队满" << endl;
rear = origin;
}
else data[rear] = a;
return;
}
参考资料:《计算机软件技术基础》 清华大学出版社 第三版