【C++】划分子集问题

运动会排程问题(划分子集问题)

(注:这是同济大学通信专业数据结构课程作业报告,在此记录数据结构的学习)

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;
}

参考资料:《计算机软件技术基础》 清华大学出版社 第三版

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值