1 课题需求描述
主要内容包括本课程设计(实践周)题目、需求描述。
1.1 常用集合的各种运算
问题描述:编制一个能演示执行集合的交、并和差等运算的程序。
基本要求:集合元素用小写英文字母,执行各种操作应以对话方式执行。
算法要点:利用set和map表示集合;理解好各种运算的含义。
2 总体功能与数据结构设计
主要叙述:本课题设计的总体功能结构、数据结构设计。
2.1 总体功能结构
给出总体功能结构图,并对每个功能进行简单的介绍
- 创建集合 (createSet): 通过 createSet 方法,用户可以创建一个新的集合,指定集合名和初始元素。
- 显示所有集合 (displayAllSets): 此功能打印所有存储在 SetManager 中的集合及其元素。
- 保存集合到文件 (saveSetsToFile): 此方法允许用户将所有集合保存到指定的文件中。
- 从文件加载集合 (loadSetsFromFile): 该方法可以从指定的文件中加载集合数据。
- 计算交集 (intersection): 计算并返回两个集合的交集。
- 计算并集 (unionSets): 计算并返回两个集合的并集。
- 计算差集 (difference): 计算并返回两个集合的差集。
- 检查子集 (isSubset): 检查一个集合是否是另一个集合的子集。
- 计算笛卡尔积 (cartesianProduct): 生成两个集合的笛卡尔积。
- 获取集合大小 (getSetSize): 返回指定集合的大小。
- 检查集合相等 (areSetsEqual): 检查两个集合是否相等。
- 打印集合 (printSet): 辅助方法,用于打印集合的内容。
- 计算补集 (complement): 计算一个集合相对于另一个集合的补集。
- 计算幂集 (powerSet): 生成并返回一个集合的所有子集(幂集)。
- 计算排列 (permutations): 生成并返回一个集合的所有排列。
- 计算组合 (combinations): 生成并返回一个集合的所有组合。
图1 总体功能结构图
2.2 数据结构设计
所选课题中主要涉及到两种数据结构:set 和 map,这两种结构都来自于 C++ 标准模板库(STL)。
1. set<T>
定义: set 是 STL 中的一个容器类,用于存储唯一元素,遵循特定顺序。
存储结构: set 通常是基于红黑树实现的。红黑树是一种自平衡二叉查找树,它确保树的高度大致保持对数级别,从而保证其操作(如插入、删除、查找)具有对数时间复杂度。
特点:
- 元素自动排序(默认为升序)。
- 不允许有重复的元素。
- 提供高效的查找、插入和删除操作。
2. map<string, set<T>>
定义: map 是 STL 中的一个关联容器,它存储的是键值对,其中每个键都是唯一的。
存储结构: map 通常也是基于红黑树实现的。在这种结构中,每个节点都包含一个键值对,树的排序基于键的排序。
特点:
- 提供基于键的快速查找。
- 每个键与一个值(在此课题中为 set<T>)相关联。
- 允许我们通过键名快速访问或修改值。
- 键不可重复,若尝试插入已存在的键,则旧值会被新值覆盖。
在所选课题中,SetManager 类使用了 map<string, set<T>> 来存储和管理多个集合。这里的 map 使得每个集合可以通过其名称(字符串键)快速访问。集合本身是以 set<T> 的形式存储,确保每个集合中的元素是唯一的且自动排序。
这种结构设计使得集合操作(如创建、查找、并集、交集、差集等)既高效又直观,非常适合集合管理系统的要求。
3 算法设计和程序设计
主要是各功能模块的算法设计(包括原理、算法及流程图)和程序设计。
3.1 算法原理的描述
1.数据存储:
系统使用 map 来存储不同的集合,其中每个集合以字符串为键(集合名称),以 set 为值(集合内容)。set 中的元素唯一且自动排序,利用红黑树实现,保证了基本操作的效率。
2.集合操作:
对于基本集合操作(如交集、并集、差集、子集检查等),代码利用 STL 提供的算法(如 set_intersection, set_union, set_difference, includes)来高效地处理这些操作,这些算法都是针对已排序的范围设计的,因此可以在对数时间内完成。
对于更复杂的操作(如幂集、排列、组合),代码实现了专门的算法。幂集的计算通过位掩码来枚举所有可能的子集,而排列和组合的计算则利用 STL 的 next_permutation 和布尔掩码技术来生成所有可能的排列和组合。
3.文件操作:
使用文件流(ifstream 和 ofstream)来实现集合的持久化。这包括将集合数据转换为可存储的格式(保存操作)和从文件中恢复集合数据(加载操作)。
4.用户界面:
交互式的命令行界面允许用户执行各种集合操作。这通过一个循环配合 switch 语句来处理用户的输入实现。
每个选项都对应一个集合操作,用户输入触发相应的功能,如创建新集合、显示所有集合、执行集合运算等。
3.2 算法流程描述
Step1: 创建集合 (createSet)
- 接收集合名称和元素作为输入。
- 将元素插入到 set 中。
- 将 set 存储到 map 中,键为集合名称。
Step2: 显示所有集合 (displayAllSets)
- 遍历 map 中的每个键值对。
- 打印每个键(集合名称)和对应的值(集合元素)。
Step3: 保存集合到文件 (saveSetsToFile)
- 打开指定的文件。
- 遍历 map,将每个集合的名称和元素转换为字符串并写入文件。
- 关闭文件。
Step4: 从文件加载集合 (loadSetsFromFile)
- 打开指定的文件。
- 读取文件内容,解析为集合名称和元素。
- 将解析后的集合存储到 map 中。
- 关闭文件。
Step5: 计算交集 (intersection)
- 查找并验证输入的两个集合是否存在。
- 使用 set_intersection 算法计算交集。
- 返回结果集合。
Step6: 计算并集 (unionSets)
- 查找并验证输入的两个集合是否存在。
- 使用 set_union 算法计算并集。
- 返回结果集合。
Step7: 计算差集 (difference)
- 查找并验证输入的两个集合是否存在。
- 使用 set_difference 算法计算差集。
- 返回结果集合。
Step8: 检查子集 (isSubset)
- 查找并验证输入的两个集合是否存在。
- 使用 includes 算法检查子集关系。
- 返回子集检查结果。
Step9: 计算笛卡尔积 (cartesianProduct)
- 查找并验证输入的两个集合是否存在。
- 使用双重循环创建元素对,并存储到新集合中。
- 返回结果集合。
Step10: 获取集合大小 (getSetSize)
- 查找并验证输入的集合是否存在。
- 返回集合的大小。
Step11: 检查集合相等 (areSetsEqual)
- 查找并验证输入的两个集合是否存在。
- 使用 == 运算符比较集合。
- 返回比较结果。
Step12: 计算补集 (complement)
- 查找并验证主集合和子集是否存在。
- 遍历主集合,将不在子集中的元素添加到结果集中。
- 返回结果集合。
Step13: 计算幂集 (powerSet)
- 查找并验证输入的集合是否存在。
- 使用位操作枚举所有可能的子集。
- 返回包含所有子集的集合。
Step14: 计算排列 (permutations)
- 查找并验证输入的集合是否存在。
- 使用 next_permutation 生成所有排列。
- 返回包含所有排列的向量。
Step15: 计算组合 (combinations)
- 查找并验证输入的集合是否存在。
- 使用布尔掩码和 prev_permutation 生成所有组合。
- 返回包含所有组合的向量。
Step16: 用户界面
- 显示菜单选项。
- 根据用户输入执行相应的集合操作。
- 输出操作结果或提示信息。
3.3 画出流程图
图2 流程图
4 测试与分析
4.1 测试
测试用例 1: 创建集合
输入: 创建集合 "A",元素为 {1, 2, 3}。
预期结果: 集合 "A" 被成功创建,元素为 {1, 2, 3}。
图3 创建集合
测试用例 2: 重复元素的处理
输入: 集合"A":{1, 1, 2, 2, 3, 3}。
预期结果: 输出显示集合 "A":{1, 2, 3}。
图4 重复元素的处理
测试用例 3: 显示所有集合
输入: 显示所有集合(假设目前只有集合 "A")。
预期结果: 输出显示集合 "A":{1, 2, 3}。
图5 显示所有集合
测试用例 4: 保存集合到文件
输入: 将当前的集合保存到 "sets.txt" 文件。
预期结果: 文件 "sets.txt" 被创建,内容包含集合 "A"。
图6 保存集合到文件
测试用例 5: 从文件加载集合
输入: 从 "sets.txt" 文件加载集合。
预期结果: 文件内容被正确加载,集合 "A" 被恢复。
图7 从文件加载集合
测试用例 6: 计算交集
输入: 创建集合 "B" 为 {2, 3, 4},计算 "A" 和 "B" 的交集。
预期结果: 交集为 {2, 3}。
图8 计算交集
测试用例 7: 计算并集
输入: 计算 "A" 和 "B" 的并集。
预期结果: 并集为 {1, 2, 3, 4}。
图9 计算并集
测试用例 8: 计算差集
输入: 计算 "A" 相对于 "B" 的差集。
预期结果: 差集为 {1}。
图10 计算差集
测试用例 9: 计算子集
输入: 计算 "A" 相对于 "B" 的子集。
预期结果: 'A' 不是 'B' 的子集。
图11 计算子集
测试用例 10: 计算笛卡尔积
输入: 计算 "A" 相对于 "B" 的笛卡尔积。
预期结果: 笛卡尔积为{ (1, 2) (1, 3) (1, 4) (2, 2) (2, 3) (2, 4) (3, 2) (3, 3) (3, 4) }。
图12 计算笛卡尔积
测试用例 11: 计算大小
输入: 计算 "A" 的大小。
预期结果: 集合 'A' 的大小为: 3。
图13 计算大小
测试用例 12: 计算补集
输入: 计算 "A" 和 "B" 的补集。
预期结果: 补集: { 1 }。
图14 计算补集
测试用例 13: 计算幂集
输入: 计算 "A" 的幂集。
预期结果:
幂集为
{ }
{ 1 }
{ 1 2 }
{ 1 2 3 }
{ 1 3 }
{ 2 }
{ 2 3 }
{ 3 }
图15 计算幂集
测试用例 14: 计算排列
输入: 计算 "A" 的排列。
预期结果:
排列为
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
图16 计算排列
测试用例 15: 计算组合
输入: 计算 "A" 的组合,输入组合大小 (k)为 2。
预期结果:
组合:
1 2
1 3
2 3
图17 计算组合
测试用例 16: 检查两个集合是否相等
输入: 计算 "A" 和 "B" 是否相等。
预期结果: 集合 'A' 和 'B' 不相等。
图18 检查集合相等
测试步骤:
- 对每个测试用例分别运行程序。
- 观察输出与预期结果是否一致。
- 如果结果不一致,检查输入数据的有效性、代码中的逻辑错误或潜在的边界条件问题。
- 根据发现的问题修改代码。
- 重新运行测试用例验证修正是否成功。
分析和修改:
- 如果测试结果与预期不符,可能的原因包括逻辑错误、错误的数据类型处理、文件读写问题等。
- 针对每个问题,逐一调试和修正代码,比如修正循环条件、修改数据类型、确保文件的正确打开和关闭等。
- 重新运行测试并观察输出是否符合预期。
4.2 算法分析
1. 时间复杂度分析
每个操作的时间复杂度取决于它所执行的特定算法和数据结构。
创建集合 (createSet): 时间复杂度为 O(NlogN),因为它涉及将 N 个元素插入到 set 中,每次插入操作的时间复杂度为 O(logN)。
显示所有集合 (displayAllSets): 时间复杂度为 O(MN),其中 M 是集合数量,N 是平均集合大小。
保存集合到文件 (saveSetsToFile) 和 从文件加载集合 (loadSetsFromFile): 时间复杂度取决于文件大小和集合数量,假设有 M 个集合,平均每个集合 N 个元素,复杂度为 O(MN)。
集合操作 (交集、并集、差集): 使用 STL 的集合操作算法,这些操作的时间复杂度通常为 O(N+M),其中 N 和 M 是两个集合的大小。
检查子集 (isSubset): 时间复杂度为 O(N+M)。
计算笛卡尔积 (cartesianProduct): 时间复杂度为 O(N*M),其中 N 和 M 是两个集合的大小。
获取集合大小 (getSetSize): 时间复杂度为 O(1)。
检查集合相等 (areSetsEqual): 时间复杂度为 O(N),N 是集合大小。
计算幂集 (powerSet): 时间复杂度为 O(2^N * N),因为每个元素都有存在和不存在两种可能。
计算排列 (permutations) 和 计算组合 (combinations): 这些操作的时间复杂度较高,特别是当集合大小增加时。
2. 空间复杂度分析
大多数操作的空间复杂度与集合大小成正比。例如,存储集合需要 O(N) 的空间,其中 N 是集合中的元素数量。
对于幂集和排列/组合等操作,空间复杂度可能会迅速增加,因为需要存储大量的可能结果。
3. 可能的优化策略
减少不必要的复制: 对于某些操作,如集合的交集、并集等,可以考虑就地修改而不是返回新的集合,以减少内存使用和提高效率。
延迟计算: 对于像幂集这样的大型数据集,可以考虑使用生成器或迭代器模式进行延迟计算,而不是一次性计算所有可能的结果。
多线程和并行计算: 对于计算密集型操作,如排列和组合的计算,可以考虑使用多线程或并行计算来提高效率。
5 算法设计技能训练总结
5.1 收获
通过分析和实践这个集合管理系统项目,主要收获在于深入理解了 C++ 中关键数据结构(如 set 和 map)的应用,熟悉了标准模板库(STL)中的高效算法,以及如何结构化和优化复杂的程序代码。
此外,还学习了编写全面测试用例的重要性,以及如何进行性能分析和调试,从而提高代码的可靠性和效率。这个练习不仅强化了具体的编程技能,还提升了解决实际问题的能力,对于提高编程效率和代码质量具有重要意义。
5.2 存在问题
在这个集合管理系统项目中,存在的主要问题包括对高级数据结构和算法的依赖可能导致对新手不够友好,特别是在理解和使用 C++ STL 中的高效算法时。
此外,复杂操作如幂集和排列组合的处理可能导致性能瓶颈,尤其是在处理大数据集时。代码中还缺乏对异常和错误处理的充分考虑,这可能在实际应用中引发问题。此外,用户界面相对基础,可能不足以处理更复杂的用户交互需求。最后,代码的模块化和注释可以进一步改进,以提高可读性和可维护性。
6 完整算法代码
#include <iostream>
#include <fstream> // 文件操作
#include <set> // 集合(set)
#include <map> // 映射(map)
#include <string>
#include <algorithm>
#include <vector> // 向量(vector)
using namespace std;
template <typename T>
class SetManager {
private:
map<string, set<T>> sets; // 使用map来存储集合,其中键是字符串,值是类型为T的集合
public:
// 创建一个集合,名称为setName,包含elements中的元素
void createSet(const string& setName, const set<T>& elements) {
sets[setName] = elements; // 在map中添加或更新具有给定名称和元素的集合
}
// 显示所有集合的内容
void displayAllSets() const { // auto关键字用于自动类型推断。当使用auto时,编译器会自动根据初始化表达式推断变量的类型
for (const auto& pair : sets) { // 遍历map中的所有键值对
cout << pair.first << ": "; // 打印集合的名称
printSet(pair.second); // 调用printSet函数显示集合的元素
cout << endl;
}
}
// 将所有集合保存到文件中
void saveSetsToFile(const string& filename) const {
ofstream file(filename); // 创建一个输出文件流,使用给定的文件名
if (file.is_open()) {
for (const auto& pair : sets) {
file << pair.first << ": "; // 将集合的名称写入文件
auto it = pair.second.begin(); // 获取集合开始的迭代器
auto end = pair.second.end(); // 获取集合结束的迭代器
while (it != end) {
file << *it; // 遍历直到集合的末尾
if (++it != end) { // 如果不是最后一个元素,则在元素后添加空格
file << " ";
}
}
file << "\n"; // 在每个集合后添加换行符
}
file.close();
cout << "所有集合已保存到文件 '" << filename << "'" << endl;
}
else {
cout << "无法打开文件 '" << filename << "' 进行保存。" << endl;
}
}
// 从文件中加载集合数据
void loadSetsFromFile(const string& filename) {
ifstream file(filename); // 创建一个输入文件流对象,用于读取文件
if (file.is_open()) {
string setName;
while (file >> setName) { // 从文件中读取集合名称
setName.pop_back(); // 移除集合名称末尾的冒号
int element; // 用于临时存储集合中的元素
set<int> newSet; // 创建一个新的整数集合
while (file >> element) {
newSet.insert(element);
if (file.peek() == '\n' || file.peek() == EOF)
break; // 如果遇到换行符或文件结束符,跳出循环
}
sets[setName] = newSet; // 将新集合添加到映射中
file.ignore(numeric_limits<streamsize>::max(), '\n'); // 忽略当前行的剩余部分
}
file.close();
cout << "从文件 '" << filename << "' 读取了集合。" << endl;
}
else {
cout << "无法打开文件 '" << filename << "' 进行读取。" << endl;
}
}
// 计算两个集合的交集 set<T>
set<T> intersection(const string& setName1, const string& setName2) const {
set<T> result; // 创建一个新集合用于存储交集结果
auto it1 = sets.find(setName1); // 在映射中查找第一个集合
auto it2 = sets.find(setName2); // 在映射中查找第二个集合
if (it1 != sets.end() && it2 != sets.end()) { // 检查两个集合是否都存在
set_intersection(it1->second.begin(), it1->second.end(),
it2->second.begin(), it2->second.end(),
inserter(result, result.begin())); // 计算两个集合的交集并存储在result中
}
else {
cout << "输入的集合名称无效。" << endl;
}
return result; // 返回交集结果
}
// 计算两个集合的并集 set<T>
set<T> unionSets(const string& setName1, const string& setName2) const {
set<T> result;
auto it1 = sets.find(setName1);
auto it2 = sets.find(setName2);
if (it1 != sets.end() && it2 != sets.end()) {
set_union(it1->second.begin(), it1->second.end(),
it2->second.begin(), it2->second.end(),
inserter(result, result.begin())); // 计算两个集合的并集并存储在result中
}
else {
cout << "输入的集合名称无效。" << endl;
}
return result; // 返回并集结果
}
// 计算两个集合的差集 set<T>
set<T> difference(const string& setName1, const string& setName2) const {
set<T> result;
auto it1 = sets.find(setName1);
auto it2 = sets.find(setName2);
if (it1 != sets.end() && it2 != sets.end()) {
set_difference(it1->second.begin(), it1->second.end(),
it2->second.begin(), it2->second.end(),
inserter(result, result.begin())); // 计算两个集合的差集并存储在result中
}
else {
cout << "输入的集合名称无效。" << endl;
}
return result; // 返回差集结果
}
// 检查一个集合是否是另一个集合的子集 set<T>
bool isSubset(const string& candidateSetName, const string& mainSetName) const {
auto it1 = sets.find(candidateSetName); // 在map中查找候选子集
auto it2 = sets.find(mainSetName); // 在map中查找主集合
if (it1 != sets.end() && it2 != sets.end()) { // 检查两个集合是否都存在
const set<T>& candidateSet = it1->second; // 获取候选子集
const set<T>& mainSet = it2->second; // 获取主集合
return includes(mainSet.begin(), mainSet.end(), //includes字符串匹配
candidateSet.begin(), candidateSet.end()); // 判断候选子集是否是主集合的子集
}
else {
cout << "输入的集合名称无效。" << endl;
return false;
}
}
// 计算两个集合的笛卡尔积 set<pair<T,T>>,用于存储元素对,其中每个元素对由两个集合的元素组成。
set<pair<T, T>> cartesianProduct(const string& setName1, const string& setName2) const {
set<pair<T, T>> result; // 创建一个新集合用于存储笛卡尔积结果
auto it1 = sets.find(setName1);
auto it2 = sets.find(setName2);
if (it1 != sets.end() && it2 != sets.end()) {
for (const T& elem1 : it1->second) { // 遍历第一个集合的每个元素
for (const T& elem2 : it2->second) { // 遍历第二个集合的每个元素
result.insert(make_pair(elem1, elem2)); // 插入元素对到结果集合
} //make_pair创建二元组的便利函数模板
}
}
else {
cout << "输入的集合名称无效。" << endl;
}
return result; // 返回笛卡尔积结果
}
// 获取特定集合的大小 set<T>
int getSetSize(const string& setName) const {
auto it = sets.find(setName);
if (it != sets.end()) {
return it->second.size(); // 返回集合的大小
}
else {
cout << "输入的集合名称无效。" << endl;
return -1;
}
}
// 检查两个集合是否相等 set<T>
bool areSetsEqual(const string& setName1, const string& setName2) const {
auto it1 = sets.find(setName1);
auto it2 = sets.find(setName2);
if (it1 != sets.end() && it2 != sets.end()) {
return it1->second == it2->second; // 比较两个集合是否相等
}
else {
cout << "输入的集合名称无效。" << endl;
return false;
}
}
// 打印集合内容
void printSet(const set<T>& s) const {
cout << "{ ";
for (const auto& elem : s) { // 遍历集合中的每个元素
cout << elem << " ";
}
cout << "}";
}
// 计算一个集合相对于另一个集合的补集 set<T>
set<T> complement(const string& mainSetName, const string& subSetName) {
auto itMain = sets.find(mainSetName); // 在map中查找主集合
auto itSub = sets.find(subSetName); // 在map中查找子集合
if (itMain != sets.end() && itSub != sets.end()) {
const set<T>& mainSet = itMain->second;
const set<T>& subSet = itSub->second;
set<T> result; // 创建一个新集合用于存储补集结果
for (const T& element : mainSet) { // 遍历主集合中的每个元素
if (subSet.find(element) == subSet.end()) { // 如果元素不在子集合中
result.insert(element); // 将元素添加到补集中
}
}
return result; // 返回补集结果
}
else {
cout << "输入的集合名称无效。" << endl;
return {};
}
}
// 计算一个集合的幂集 set<set<T>>,用于存储所有可能的子集合
set<set<T>> powerSet(const string& setName) {
auto it = sets.find(setName);
if (it != sets.end()) {
const set<T>& inputSet = it->second;
set<set<T>> result; // 创建一个新集合用于存储幂集结果
vector<T> elements(inputSet.begin(), inputSet.end()); // 将集合转换为向量
int n = elements.size(); // 获取集合元素数量
for (int i = 0; i < (1 << n); ++i) { // 对于每个可能的组合
set<T> subset; // 创建一个新集合用于存储子集
for (int j = 0; j < n; ++j) { // 遍历集合中的每个元素
if (i & (1 << j)) { // 检查位掩码 表示集合中元素的选取状态
//使用二进制数字的每一位来代表集合中某个位置的元素是否被选取 通过位运算可以高效地遍历所有可能的子集或组合
subset.insert(elements[j]); // 将元素添加到子集中
}
}
result.insert(subset); // 将子集添加到幂集中
}
return result; // 返回幂集结果
}
else {
cout << "输入的集合名称无效。" << endl;
return {};
}
}
// 计算一个集合的全排列 vector<vector<T>>,这是因为需要存储所有可能的排列,每个排列本身也是一个顺序列表
vector<vector<T>> permutations(const string& setName) {
auto it = sets.find(setName);
if (it != sets.end()) {
const set<T>& inputSet = it->second;
vector<T> elements(inputSet.begin(), inputSet.end());
vector<vector<T>> result; // 创建一个新向量用于存储全排列结果
do {
result.push_back(elements); // 将当前排列添加到结果中
} while (next_permutation(elements.begin(), elements.end())); // 生成下一个排列
//next_permutation是为给定的值数组查找下一个字典序更大的值
return result; // 返回全排列结果
}
else {
cout << "输入的集合名称无效。" << endl;
return {};
}
}
// 计算一个集合的组合 vector<vector<T>>,用于存储特定大小的所有组合
vector<vector<T>> combinations(const string& setName, int k) {
auto it = sets.find(setName);
if (it != sets.end()) {
const set<T>& inputSet = it->second;
vector<T> elements(inputSet.begin(), inputSet.end());
vector<vector<T>> result; // 创建一个新向量用于存储组合结果
int n = elements.size(); // 获取集合元素数量
vector<bool> bitmask(n, false); // 创建一个位掩码
fill(bitmask.begin(), bitmask.begin() + k, true); // 初始化位掩码 表示集合中元素的选取状态
do {
vector<T> combination; // 创建一个新向量用于存储当前组合
for (int i = 0; i < n; ++i) {
if (bitmask[i]) { // 如果位掩码为true
combination.push_back(elements[i]); // 将元素添加到当前组合中
}
}
result.push_back(combination); // 将当前组合添加到结果中
} while (prev_permutation(bitmask.begin(), bitmask.end())); // 生成下一个组合
//prev_permutation是为给定值数组查找先前的字典编排较小的值
return result; // 返回组合结果
}
else {
cout << "输入的集合名称无效。" << endl;
return {};
}
}
};
/*map用于将集合名称与其内容(set<T>)关联,而set用于存储不重复的元素。
对于需要排列和组合的操作,使用vector来存储临时结果,因为vector允许重复元素并且可以轻松地进行顺序访问和迭代。*/
int main() {
SetManager<int> setManager; // 创建一个整数类型的SetManager对象
while (true) {
cout << "---------------------------------------------------------" << endl;
cout << "| a. 创建新集合 |" << endl;
cout << "| b. 显示所有集合 |" << endl;
cout << "| c. 保存集合到文件 |" << endl;
cout << "| d. 从文件读取集合 |" << endl;
cout << "| e. 计算集合的交集 |" << endl;
cout << "| f. 计算集合的并集 |" << endl;
cout << "| g. 计算集合的差集 |" << endl;
cout << "| h. 检查集合的子集 |" << endl;
cout << "| i. 计算集合的笛卡尔积 |" << endl;
cout << "| j. 计算集合的大小 |" << endl;
cout << "| k. 计算集合的补集 |" << endl;
cout << "| l. 计算集合的幂集 |" << endl;
cout << "| m. 计算集合的排列 |" << endl;
cout << "| n. 计算集合的组合 |" << endl;
cout << "| o. 检查两个集合是否相等 |" << endl;
cout << "| p. 退出集合系统 |" << endl;
cout << "---------------------------------------------------------" << endl;
cout << "输入操作编号:";
char choice;
cin >> choice;
switch (choice) {
case 'a': {
string setName;
cout << "输入新集合的名称: ";
cin >> setName;
set<int> newSet;
int element;
cout << "输入新集合的元素(整数,以空格分隔): ";
while (cin >> element) {
newSet.insert(element);
if (cin.get() == '\n') break;
}
setManager.createSet(setName, newSet);
break;
}
case 'b':
setManager.displayAllSets();
break;
case 'c': {
string filename;
cout << "输入要保存的文件名: ";
cin >> filename;
setManager.saveSetsToFile(filename);
break;
}
case 'd': {
string filename;
cout << "输入要读取的文件名: ";
cin >> filename;
setManager.loadSetsFromFile(filename);
break;
}
case 'e': {
string setName1, setName2;
cout << "输入第一个集合的名称: ";
cin >> setName1;
cout << "输入第二个集合的名称: ";
cin >> setName2;
set<int> intersection = setManager.intersection(setName1, setName2);
cout << "交集: ";
setManager.printSet(intersection);
cout << endl;
break;
}
case 'f': {
string setName1, setName2;
cout << "输入第一个集合的名称: ";
cin >> setName1;
cout << "输入第二个集合的名称: ";
cin >> setName2;
set<int> unionSet = setManager.unionSets(setName1, setName2);
cout << "并集: ";
setManager.printSet(unionSet);
cout << endl;
break;
}
case 'g': {
string setName1, setName2;
cout << "输入第一个集合的名称: ";
cin >> setName1;
cout << "输入第二个集合的名称: ";
cin >> setName2;
set<int> difference = setManager.difference(setName1, setName2);
cout << "差集: ";
setManager.printSet(difference);
cout << endl;
break;
}
case 'h': {
string candidateSetName, mainSetName;
cout << "输入候选子集的名称: ";
cin >> candidateSetName;
cout << "输入父集的名称: ";
cin >> mainSetName;
if (setManager.isSubset(candidateSetName, mainSetName)) {
cout << "'" << candidateSetName << "' 是 '" << mainSetName << "' 的子集。" << endl;
}
else {
cout << "'" << candidateSetName << "' 不是 '" << mainSetName << "' 的子集。" << endl;
}
break;
}
case 'i': {
string setName1, setName2;
cout << "输入第一个集合的名称: ";
cin >> setName1;
cout << "输入第二个集合的名称: ";
cin >> setName2;
set<pair<int, int>> cartesian = setManager.cartesianProduct(setName1, setName2);
cout << "笛卡尔积: { ";
for (const auto& elem : cartesian) {
cout << "(" << elem.first << ", " << elem.second << ") ";
}
cout << "}" << endl;
break;
}
case 'j': {
string setName;
cout << "输入集合的名称: ";
cin >> setName;
int setSize = setManager.getSetSize(setName);
if (setSize != -1) {
cout << "集合 '" << setName << "' 的大小为: " << setSize << endl;
}
break;
}
case 'k': {
string setName1, setName2;
cout << "输入主集合的名称: ";
cin >> setName1;
cout << "输入子集合的名称: ";
cin >> setName2;
set<int> complementSet = setManager.complement(setName1, setName2);
cout << "补集: ";
setManager.printSet(complementSet);
cout << endl;
break;
}
case 'l': {
string setName;
cout << "输入集合的名称: ";
cin >> setName;
set<set<int>> powerSet = setManager.powerSet(setName);
cout << "幂集: " << endl;
for (const auto& subset : powerSet) {
setManager.printSet(subset);
cout << endl;
}
break;
}
case 'm': {
string setName;
cout << "输入集合的名称: ";
cin >> setName;
vector<vector<int>> perms = setManager.permutations(setName);
cout << "排列: " << endl;
for (const auto& perm : perms) {
for (const int& element : perm) {
cout << element << " ";
}
cout << endl;
}
break;
}
case 'n': {
string setName;
cout << "输入集合的名称: ";
cin >> setName;
int k;
cout << "输入组合大小 (k): ";
cin >> k;
vector<vector<int>> combos = setManager.combinations(setName, k);
cout << "组合: " << endl;
for (const auto& combo : combos) {
for (const int& element : combo) {
cout << element << " ";
}
cout << endl;
}
break;
}
case 'o': {
string setName1, setName2;
cout << "输入第一个集合的名称: ";
cin >> setName1;
cout << "输入第二个集合的名称: ";
cin >> setName2;
bool areEqual = setManager.areSetsEqual(setName1, setName2);
if (areEqual) {
cout << "集合 '" << setName1 << "' 和 '" << setName2 << "' 相等。" << endl;
}
else {
cout << "集合 '" << setName1 << "' 和 '" << setName2 << "' 不相等。" << endl;
}
break;
}
case 'p':
cout << "谢谢使用!" << endl;
return 0;
default:
cout << "无效的选择,请重新选择。" << endl;
}
}
return 0;
}