2022/4/19 第三次大作业心得 Cribbage Counting, Sierpinski Triangle, Domino Chain, Stock Cutting
运行环境:QT 6.0.2
Problem 1 Cribbage Counting //题目要求:计算并输出一手牌中所有点数和为15的牌组
一开始的问题在于无法通过for循环+递归的方式讨论所有组合
void countDisplayFifteens(const Vector<Card> & cards, GWindow *gw) { sum++; //判断运行次数 //displayVectorCards(cards, gw, 50, 50, 120); if(countVectorNumbers(cards) < 15){ return; } else if(countVectorNumbers(cards) == 15){ displayVectorCards(cards, gw, 50, 50, 120); } Vector<Card> card_copy = cards; //displayVectorCards(card_copy, gw, 50, 50, 120); for(int i = 0; i < card_copy.size(); i++){ card_copy.remove(i); return countDisplayFifteens(card_copy, gw); } }
但是我找不到问题在哪,尝试过用static但不行(其实我也不算理解static的用法,想着可能有用就用上了)
然后,经过一次又一次的debug,我终于知道了,后面的return是不会运行到的!既然前面有了return,后面的return是不会运行的!我必须要重新想方法。
后来,我想到了,既然是相当于找出所有扑克牌的组合,不就是等于找出集合中所有的子集吗?所以我需要学会找出所有子集的方法。‘’
void countDisplayFifteens(const Vector<Card> & cards, GWindow *gw) { //5S AH 9S 6H 5H int end = (1 << 5) - 1; for(int i = 0; i < end; i++){ Vector<Card> tem_Cards; //作为临时Vector储存牌组 for(int j = 0; j < 5; j++){ if(((1 << j) & i) != 0){ //用二进制数来判断该位是否存在 tem_Cards.add(cards[j]); } } if(countVectorNumbers(tem_Cards) == 15){ displayVectorCards(tem_Cards, gw, 50, 50, 120); } } }
现在又有新的问题了,牌无法换行显示,而是直接在原来的地方显示了。
在看第二题的过程中,我突然发现,我当时居然只知道return本函数,而这个return是不需要带的!所以,我可以延续开始的思路,用for+递归!
1 void countDisplayFifteens(const Vector<Card> & cards, GWindow *gw){
2 int num = countVectorNumbers(cards);
3 if(num < 15){
4 return;
5 }
6 else if(num == 15){
7 cardsSet.add(cards);
8 if(cardsSet.contains(cards)){
9 displayVectorCards(cards, gw, 50, 50, 120);
10 times++;
11 }
12 return;
13 }
14 for(int i = 0; i < cards.size(); i++){
15 Vector<Card> tem_Cards = cards;
16 tem_Cards.remove(i);
17 countDisplayFifteens(tem_Cards, gw);
18 }
19 }
新的问题又出现了,斯坦福的Set好像不能插入仿函数,难道说它不能比较自定义类型?
后来才知道,原来重载运算符就可以了。
这是新的递归方法:
1 void countDisplayFifteens(const Vector<Card> & cards, GWindow *gw){
2 int num = countVectorNumbers(cards);
3 if(num < 15){
4 return;
5 }
6 else if(num == 15){
7 if(!cardsSet.contains(cards)){
8 int YS = 170 + 120 * cardsSet.size();
9 displayVectorCards(cards, gw, 50, YS, 120);
10 cardsSet.add(cards);
11 }
12 return;
13 }
14 for(int i = 0; i < cards.size(); i++){
15 Vector<Card> tem_Cards = cards;
16 tem_Cards.remove(i);
17 countDisplayFifteens(tem_Cards, gw);
18 }
19 }
Problem 2 Sierpinski Triangle
递归分形题,不用多说。
Problem 3 Domino Chain //要求:判断是否可以组成多米诺骨牌链
这道题是我花时间最多的。我首先想到的是找出所有的情况再一一判断,但是题目要求使用递归。于是我这样想:
basecase是剩下两个可以组合成chain,然后每一次递归都是判断第一块能不能放两头和后面的能不能骨牌链。
但是,这样有个问题:只有特定的顺序才能得到正确的答案,不然会出现本来可以组成,但是由于最后两张牌不能组成之类的情况而误判的情况。
ps:把能不能放两头改成能不能插进去再试试!难点:如何讨论到所有情况?
后来求助于网络,只能找到Java版的“找出所有可能”的解,其他基本上都不符合要求。但是我没学过Java,好在有C++的底子在,勉强能翻译成C++语句。于是我就翻译过来了,现在问题到了如何将“找出所有可能”改为“是否能组成”呢?我想不到什么好方法,只能使用最笨的方法:把最开始的Domino链的长度当作参数传进去,当有一种可能时便返回true,如果没有就在最后返回false。
bool canFormDominoChain(Vector<Domino> & dominos, Vector<Domino> & chain, int Dsize){
2 for(int i = 0; i < dominos.size(); i++){
3 Domino dom = dominos.get(i);
4 if(canAppend(dom, chain)){
5 chain.add(dom);
6 if(chain.size() == Dsize){
7 return true;
8 }
9 //printDominoVector(chain);
10 Domino temSave = dominos.remove(i);
11 canFormDominoChain(dominos, chain, Dsize);
12 dominos.insert(i, temSave);
13 chain.remove(chain.size() - 1);
14 }
15 dom = dom.turnOver();
16 if(canAppend(dom, chain)){ //翻转后能不能加
17 chain.add(dom);
18 if(chain.size() == Dsize){
19 return true;
20 }
21 //printDominoVector(chain);
22 Domino temSave = dominos.remove(i);
23 canFormDominoChain(dominos, chain, Dsize);
24 dominos.insert(i, temSave);
25 chain.remove(chain.size() - 1);
26 }
27 }
28 return false;
29 }
30
31 bool canAppend(const Domino& dom, const Vector<Domino>& to){
32 bool a = to.isEmpty() || to.get(to.size() - 1).getRightDots() == dom.getLeftDots();
33 return a;
34 }
主要思路是:使用一条空Vector,储存骨牌链,然后讨论所有可能。当其中一种可能与骨牌原本长度相等时,就返回true。讨论各种可能的方法是使用树,如果某一支可以延续,就一直延续下去,直到层数等于原长度。如果层数都达不到原长度,就返回false。
但是,题目要求不能修改函数!
所以,仿照上面思路,我写出了不修改函数的方法:
1 bool canFormDominoChain(Vector<Domino> & dominos){
2 if(dominos.size() == 2){
3 return isDominoChain(dominos);
4 }
5 bool sunCan[dominos.size()];
6 for(int i = 0; i < dominos.size(); i++){
7 Domino temD = dominos.get(i);
8 //取子
9 Vector<Domino> copyDominos = dominos.subList(1);
10
11 //sunCan[i] = false; bool suncani = sunCan[i]; //debug专用
12 bool canApp = canAppend(copyDominos, temD);
13
14 if(canApp){ //不用翻转可以加
15 dominos.remove(i);
16 sunCan[i] = canFormDominoChain(dominos) && canApp; //除去这一个,剩下的是不是domino链,而且这个能不能加
17 //suncani = sunCan[i];//debug专用
18 dominos.insert(i, temD); //复原
19 }
20
21 temD = temD.turnOver();
22 bool canApp2 = canAppend(copyDominos, temD);
23
24 if(canApp2){ //翻转后可以加
25 dominos.remove(i);
26 sunCan[i] = canFormDominoChain(dominos) && canApp2; //除去这一个,剩下的是不是domino链,而且这个能不能加
27 //suncani = sunCan[i];//debug专用
28 dominos.insert(i, temD); //复原
29 }
30 }
31
32 bool re = false;
33 for(bool b:sunCan){
34 re = re || b;
35 }
36 return re;
37 }
主要思路:通过递归建立树,并使用一组变量储存每个根的子节点的每层的情况,当每一层都有true时,就可以组成,否则就不能。
Problem 3 Domino Chain Extension //要求:返回一个Set类型的集合,储存所有符合条件的dominoChain,使用递归。
说实话,我实在是想不出来如何写一个
Set<Vector<Domino>> formsALLDominoChains(Vector<Domino> & dominos)
递归函数来找出所有的情况。因为它返回的是一个Set类型的,但是传进去的是个Vector,该如何加,我想了很多种方法,但是都行不通。
无奈之下,我突然想到前面非extension的problem3的第一个解,我看了看,可以改成全局变量(在test函数里面初始化)。于是我改了改,将需要多传的参数变成了全局变量,这样就回避了题目的要求。
/* * Wrapper Function: formsALLDominoChains * Usage: dominoChainsSet = formsALLDominoChains(dominos) */ Set<Vector<Domino>> formsALLDominoChains(Vector<Domino> & dominos){ for(int i = 0; i < dominos.size(); i++){ Domino dom = dominos.get(i); if(canAppend(dom)){ chain.add(dom); if(chain.size() == Dsize){ temS.add(chain); //printDominoVector(chain); //cout << "Dsize = " << Dsize << endl; } Domino temSave = dominos.remove(i); formsALLDominoChains(dominos); dominos.insert(i, temSave); chain.remove(chain.size() - 1); } dom = dom.turnOver(); if(canAppend(dom)){ //翻转后能不能加 chain.add(dom); if(chain.size() == Dsize){ temS.add(chain); //printDominoVector(chain); //cout << "Dsize = " << Dsize << endl; } Domino temSave = dominos.remove(i); formsALLDominoChains(dominos); dominos.insert(i, temSave); chain.remove(chain.size() - 1); } } return temS; } /* Function: canAppend * _____________________ * To judge if the "dom" can append to the chain */ bool canAppend(const Domino& dom){ bool a = chain.isEmpty() || chain.get(chain.size() - 1).getRightDots() == dom.getLeftDots(); return a; }
至此,我感觉第一题和第三题很像,然后我才知道这东西叫“回溯算法”,其主要思路都是:
类型 back(列表)
if basecase:
return
for 一一列举列表中的东西:
存档
对列表操作(通常是remove)
back(列表) //递归
回档
差不多就是这样。
Problem 4 StackCutting //
经过老师提示,我知道了这是动态规划问题

浙公网安备 33010602011771号