Part One: Debugging Practice
QT的调试,推荐b站找个课来看比较好
Part Two: Matchmaker
Milestone One: Find Perfect Matchings
你有一群人都在上同一门课。这门课让人们在作业上结对工作,而人们都想找人一起工作。每个人都在相互交谈,他们各自列出了一个他们愿意与之合作的人的名单。然后问题出现了——鉴于每个人的偏好,是否有可能将每个人配对,使他们与他们想一起工作的人结成伙伴?
我们可以通过为每个人画一个圆圈,然后画线连接那些愿意彼此合作的人,来形象地说明这个问题。下面是一个例子,说明这可能是什么样子:"A": { "B" }, "B": { "A", "C", "E", "G" }, "C": { "B", "D", "G" }, "D": { "C", "G", "H" }, "E": { "B", "F" }, "F": { "E", "G" }, "G": { "B", "C", "D", "F", "H" }, "H": { "D", "G" }
你的第一个任务是写一个函数
bool hasPerfectMatching(const Map<string, Set<string>>& possibleLinks,
Set<Pair>& matching);
它将一组人和他们之间可能存在的联系作为输入,然后确定是否存在完美匹配。如果存在,则该函数应该返回true并设置匹配的输出参数以保持一个这样的完美匹配。如果没有,则该函数应该返回false,匹配的内容可以是您想要的任何内容。
最开始我的想法就是和题目描述的贪心算法一样,我认为让可能合作的人(possibleLinks对应的value.size())最少的那个人先选伙伴,然后让倒数第二少再选,依次类推,也就是先对possibleLinks进行排序,按照value.size()从低到高排序,然后用firstKey()方法取出元素来让他选人,后来发现这个方法不行,因为它只尝试了一种情况,并没有对所有情况都试一遍。
仔细阅读题目的提示
As a hint for this problem, at each point in time, you’ll find either that everyone is paired off, or that there’s someone who isn’t paired. In the first case, great! You’re done, and you have a perfect matching. In the second case, pick some person who isn’t yet paired off, then try all possible ways of pairing them off with an unpaired person.
在每个时间点,你会发现要么每个人都是成对的,要么有人没有成对。在第一种情况下,很好!完成了,你找到了完美的配对对象。在第二种情况下,选择一个还没有配对的人,然后尝试所有可能的方法把他们和一个没有配对的人配对。
代码逻辑在注释写得很详细,这个思路应该叫做递归回溯/深度优先遍历,也就是一条路走到黑,走不通换一条,直到走完所有路
bool hasPerfectMatching(const Map<string, Set<string>>& possibleLinks, Set<Pair>& matching) {
if(possibleLinks.isEmpty()){
return true;
}
else{
string people=possibleLinks.firstKey();
for (string partner:possibleLinks[people]){
bool flag = 0;
//判断partner是否存在于matching中,如果存在,换下一个人来当partner
for(Pair pair:matching){
if (pair.first()==partner || pair.second()==partner){
flag=1;
break;
}
}
//如果flag==1,则不会执行下面if语句,会执行下一次for循环
/*如果flag==0,说明partner不在matching中,此时不能把这对partner,people添加进matching,只有当确定这条路是完美匹配才可以,
然后删除possibleLinks中people,partner的键,并且遍历剩下的possibleLinks,如果possibleLinks
剩余的键对应的值(集合)中含有people,partner,则删掉,然后递归调用本函数,把剩下的possibleLinks
和增加过的matching传入,如果函数返回true说明这条线路成功,否则程序结束本次循环,进行下次循环换下
一个人来当partner如果所有循环结束都没有返回true,则说明没有完美匹配,返回false*/
if(flag==0){
Pair pair={people,partner};
Map<string, Set<string>> remaining=possibleLinks;//这里不用原来的possibleLinks一是因为下次循环要使用原本的possibleLinks,二是它的声明用了const
remaining.remove(people);
remaining.remove(partner);
//这里与上文判断partner是否存在于matching中的代码作用重复了,两者保留一个就行
for (string key:remaining){
if(remaining[key].contains(people)){
remaining[key]-=people;
}
if(remaining[key].contains(partner)){
remaining[key]-=partner;
}
}
//如果hasPerfectMatching(remaining,matching)返回ture说明这条路就是完美皮皮额,返回false说明这条路不通,进行下一个for循环换一条路,直到所有路走完
if (hasPerfectMatching(remaining,matching)){
matching+=pair;
return true;
}
}
}
//进行到这里说明所有路都走完了且没有完美匹配
return false;
}
}
Milestone Two: Find Maximum-Weight Matchings
编写函数:
Set<Pair> maximumWeightMatching(const Map<string, Map<string, int>>& possibleLinks)
输入带有权重的可能链接,返回最大权重的组合
思路仍然是和递归回溯/深度优先遍历有关,笔者写的代码迭代了很多遍,并且使用了全局变量,所以着重介绍思路
本题思路仍然是把所有路线走一遍,计算每条路线的权重,与设置的全局变量->最大权重比较,当某条路线的权重大于最大权重,就返回这条路线的配对
对于某个节点(人),笔者认为有两种情况,一种是让他与可能合作的人逐一配对,配对一次算一条路线,一种是他全程不参与配对,这算一条路线,从而比较所有路线的权重
测试结果表明思路是没有问题的,代码部分可供参考(因为比较乱,所以主要借鉴思路)
Set<Pair> matching={};//已经配对的人
Set<Pair> res={};//最终的结果
int maxValue=0,nowValue=0;//最大权重和当前权重
Set<Pair> maximumWeightMatching(const Map<string, Map<string, int>>& possibleLinks) {
if(possibleLinks.isEmpty()){
return {};
}
else{
//第一种情况让people分别与它可能匹配的人配对,第二种情况让people不参与配对
string people=possibleLinks.firstKey();
for (string partner:possibleLinks[people]){
Map<string, Map<string, int>> remaining=possibleLinks;
nowValue+=possibleLinks[people][partner];
remaining.remove(people);
remaining.remove(partner);
for (string key : remaining) {
vector<string> elementsToRemove; // 用于存储需要移除的元素
for (string x : remaining[key]) {
if (x == people || x==partner) {
elementsToRemove.push_back(x);
}
}
// 移除需要移除的元素
for (string x : elementsToRemove) {
remaining[key].remove(x);
}
}
Pair pair={people,partner};
matching+=pair;
Set<Pair> ans=maximumWeightMatching(remaining);
matching+=ans;
if(nowValue>maxValue){
maxValue=nowValue;
res=matching;
matching.clear();
nowValue=0;
}
matching.clear();
nowValue=0;
}
//当people不参与匹配时的情况
Map<string, Map<string, int>> remaining=possibleLinks;
remaining.remove(people);
for (string key : remaining) {
vector<string> elementsToRemove; // 用于存储需要移除的元素
for (string x : remaining[key]) {
if (x == people) {
elementsToRemove.push_back(x);
}
}
// 移除需要移除的元素
for (string x : elementsToRemove) {
remaining[key].remove(x);
}
}
Set<Pair> ans=maximumWeightMatching(remaining);
ans+=matching;
if(nowValue>maxValue){
maxValue=nowValue;
res=ans;
matching.clear();
nowValue=0;
}
}
return res;
}
Part Three: Disaster Planning
需要编写这样一个函数:
bool canBeMadeDisasterReady(const Map<string, Set<string>>& roadNetwork,
int numCities,
Set<string>& supplyLocations);
其中roadnetwork是城市图的邻接表,numCites是最多在几个城市放置物资。如果能做到全部城市ready那么就把物资放置在哪几个城市写进supplylocations并且返回true,如果不行就返回false。numCites可以为0但不能为负。
题目为我们提供了以下的思路:
对于任意一个城市,证明它能够达到disaster-ready的形式的尝试有且仅有以下两种:
- 选择这个城市,作为物资储备的城市
- 选择这个城市紧挨的城市之一,作为物资储备的城市
如果这个选择能够递归地覆盖所有城市的话,那么我们就实现了我们最终的目标。但是如果未能实现的话,我们跳至下一个选项。
这个思路同样可以处理甲烷状城市路线图(有两个相邻的物资储备城市)->在其他点位的邻居的遍历中,就会考虑到这个情况
bool help(const Map<string, Set<string>>& roadNetwork,
int numCities,
Set<string>& supplyLocations,
Set<string>& uncoveredCity) {
if (uncoveredCity.isEmpty()){
return true;
}
else if (numCities == 0 && !uncoveredCity.isEmpty()){
return false;
}
if (roadNetwork.isEmpty()){
return true;
}
//第一种情况,选择此城市作为物资供应点
string city = uncoveredCity.first();
supplyLocations.add(city);
Set<string> trythis = uncoveredCity - city - roadNetwork[city];
if(help(roadNetwork, numCities - 1, supplyLocations,trythis)){
return true;
}
else{
//这里是回溯
supplyLocations.remove(city);
}
//第二种情况,不选择此城市,选他的邻居作为物资供应点
for (string neighbor: roadNetwork[city]){
supplyLocations.add(neighbor);
trythis = uncoveredCity - neighbor - roadNetwork[neighbor];
if(help(roadNetwork, numCities - 1, supplyLocations,trythis)){
return true;
}
else{
//回溯
supplyLocations.remove(neighbor);
}
}
//走到这里说明两种情况都无法成立,返回false
return false;
}
bool canBeMadeDisasterReady(const Map<string, Set<string>>& roadNetwork,
int numCities,
Set<string>& supplyLocations) {
if (numCities < 0){
error("Haiya!Negtive number!");
}
//设置未覆盖城市的集合并赋值
Set<string> uncoveredCity;
for (string city: roadNetwork){
uncoveredCity += city;
}
return help(roadNetwork, numCities, supplyLocations, uncoveredCity);
}