【Code pratice】—— 纸牌三角形

D a t e : 2022 − 10 − 04 \color{FFCC99}{Date:2022-10-04} Date20221004

E v e r y o n e \color{FFCC99}{Everyone} Everyone h a s \color{FFCC99}{has} has t o \color{FFCC99}{to} to g r o w \color{FFCC99}{grow} grow u p , \color{FFCC99}{up,} up, b u t \color{FFCC99}{but} but n o t \color{FFCC99}{not} not e v e r y o n e \color{FFCC99}{everyone} everyone u n d e r s t a n d \color{FFCC99}{understand} understand g r e w \color{FFCC99}{grew} grew u p ! \color{FFCC99}{up!} up!

🍕1. 纸牌三角形🍕

🍔题目🍔

A,2,3,4,5,6,7,8,9 共9张纸牌排成一个正三角形(A按1计算)。要求每个边的和相等。如果考虑旋转、镜像后相同的算同一种,一共有多少种不同的排法呢?三角形如下

      A  
    2   3
  4       5
6   7   8   9

🍔思路🍔

本题是典型的全排列算法题目,通过对9个数字进行全排列,就可以计算出所有的三角形种类
什么是全排列?
全排列算法是一种经典的递归算法,如{1,2,3}的全排列为{1, 2, 3}, {1, 3, 2}, {2, 1, 3}, {2, 3, 1}, {3, 1, 2}, {3, 2, 1},一共6钟排列结果。全排列就是将全部可能出现的排序都列出来。而每一个数字能出现在任一个位置,如果不固定的话,那排序起来就会十分混乱,排序结果也不好记录,所以递归求解的思路是每一次全排列都固定一个元素,然后求剩下元素的全排列,直到就剩一个元素的全排列时结束本次全排列,而且每一次全排列完成后,都应先将数字排序恢复到本次全排列开始前的状态,以保证下一次的全排列都是基于初始状态进行的。

举个例子
对{1, 2, 3}进行全排列,下面按照上述全排列递归算法的思路实际模拟一遍
(每一次递归的第一步都是先判断当前遍历的是不是最后一个元素,如果是则说明全排列完成一次)

  • 1次递归调用,从头开始遍历,此时选择当前第一个元素1为固定元素,并将固定元素1与当前遍历的元素1进行换位,剩余元素为{2, 3},此时排列为{1, 2, 3};
  • 2次递归调用(基于第1次):从剩余元素开始遍历,此时选择当前第一个元素2为固定元素,并将固定元素2与当前遍历的元素2进行换位,剩余元素为{3},此时排列为{1, 2, 3};
  • 3次递归调用(基于第2次):从剩余元素开始遍历,此时选择当前第一个元素3为固定元素,并将固定元素3与当前遍历的元素3进行换位,剩余元素为空,此时排列为{1, 2, 3};
  • 4次递归调用(基于第3次):因为第4次递归调用时已经选择到了最后一个元素,遍历结束,取得第一个全排列结果{1, 2, 3},第4次递归结束;
  • 因为第3次递归调用已经遍历结束,将本次递归调用开始前进行的换位元素返回原位置(3 <-> 3),得到开始前的排列{1, 2, 3};
  • 5次递归调用(基于第2次):第2次递归往下遍历,将固定元素2与遍历元素3进行换位,剩余元素为空,此时排列为{1, 3, 2};
  • 6次递归调用(基于第5次):因为第6次递归调用时已经选择到了最后一个元素,遍历结束,取得第二个全排列结果{1, 3, 2}, 第6次递归结束;
  • 因为第5次递归调用已经遍历结束,将本次递归调用开始前进行的换位元素返回原位置(3 <-> 2),得到开始前的排列{1, 2, 3};
  • 因为第2次递归调用已经遍历结束,所以将本次递归调用开始前进行的换位元素返回原位置(2 <-> 2),得到开始前的排列{1, 2, 3};
  • 7次递归调用(基于第1次):第1次递归往下遍历,并将固定元素1与遍历元素2进行换位,此时排列为{2, 1, 3};
  • 8次递归调用(基于第7次):此时选择剩余元素的第一个元素3为固定元素,并将固定元素3与当前遍历的元素3进行换位,剩余元素为空,此时排列为{2, 1, 3};
  • 9次递归调用(基于第8次):因为第9次递归调用时已经选择到了最后一个元素,遍历结束,取得第三个全排列结果{2, 1, 3},第9次递归结束;
  • 因为第8次递归调用已经遍历结束,将本次递归调用开始前进行的换位元素返回原位置(3 <-> 3),得到开始前的排列{2, 1, 3};
  • 10次递归调用(基于第7次):第7次递归往下遍历,将固定元素1与遍历元素3进行换位,剩余元素为空,此时排列为{2, 3, 1};
  • 11次递归调用(基于第10次):因为第11次递归调用时已经选择到了最后一个元素,遍历结束,取得遍历结束,取得第四个全排列结果{2, 3, 1}, 第11次递归结束;
  • 因为第10次递归调用已经遍历结束,将本次递归调用开始前进行的换位元素返回原位置(1 <-> 3),得到开始前的排列{2, 1, 3};
  • 因为第7次递归调用已经遍历结束,将本次递归调用开始前进行的换位元素返回原位置(2 <-> 1),得到开始前的排列{1, 2, 3};
  • 12次递归调用(基于第1次):第1次递归往下遍历,并将固定元素1与遍历元素3进行换位,剩余元素为{1},此时排列为{3, 2, 1};
  • 13次递归调用(基于第12次):此时选择剩余元素第一个元素1为固定元素,与遍历元素1进行换位,剩余元素为空,此时排列为{3, 2, 1};
  • 14次递归调用(基于第13次):因为第14次递归调用时已经选择到了最后一个元素,遍历结束,取得遍历结束,取得第五个全排列结果{3, 2, 1}, 第14次递归结束;
  • 因为第13次递归调用已经遍历结束,将本次递归调用开始前进行的换位元素返回原位置(1 <-> 1),得到开始前的排列{3, 2, 1};
  • 15次递归调用(基于第12次):第12次递归往下遍历,将固定元素1与遍历元素2进行换位,此时排列为{3, 1, 2};
  • 16次递归调用(基于第15次):因为第16次递归调用时已经选择到了最后一个元素,遍历结束,取得遍历结束,取得第六个全排列结果{3, 1, 2}, 第16次递归结束;
  • 因为第12次递归调用已经遍历结束,将本次递归调用开始前进行的换位元素返回原位置(1 <-> 3),得到开始前的排列{1, 2, 3};
  • 因为第1次递归调用已经遍历结束,将本次递归调用开始前进行的换位元素返回原位置(1 <-> 1),得到开始前的排列{1, 2, 3};

递归算法处理

  1. 确定递归函数的参数和返回值
    参数:这里要求的是全排列的排列个数,所以参数一定有元素数组;要对整个数组进行遍历,那么就少不了下标和范围,所以这里的参数就是{数组、起始下标、数组长度}
    返回值:这里不需要返回值,全排列个数通过全局变量进行累加计算,若放在函数中,则每次都会被重置
void func(vector<int>& arr, int start, int len);
  1. 确定终止条件
    上面一开始就说到了终止条件:当遍历到最后一个元素时,即说明完成一个全排列
    所以终止条件就是当 下标值 >= 数组长度
if (start >= len)
  1. 确定单层递归的逻辑
    由上面的模拟可以看到,每一次递归都是新的遍历,所以有一个最外围的循环遍历逻辑,而每次递归前后都有换位操作,所以最终确定如下单层逻辑
for ()
{
    swap();
    func();
    swap();
}

🍔代码🍔

void func(vector<int>& arr, int start, int len)
{
    if (start >= len)
    {
        count++;
        return;
    }
    for (i = start; i < len; i++)
    {
        swap();
        func(arr, i + 1, len);
        swap();
    }
}

以上是我对全排列算法的粗糙理解,但是本题还有一个关键点:考虑旋转、镜像后相同的算同一种
因为三角形有三个角,也就是说每个数字都能在三个角上出现一次,所以旋转后相同算同一种的话,就需要将全排列总数 / 3;
镜像对CV战士来说好理解,就是2分一模一样的数据,所以镜像后算同一种的话,就需要将全排列总数 / 2;
最终计算结果就是 全排列总数 / 3 / 2,即总数 / 6;

🍔总结🍔

递归算法一般都很难理顺,自己一开始自己按原理推理的时候也一直弄乱,记不清当前递归的条件。本题的关键就在于全排列算法和确定旋转/镜像的关系

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ltd Pikashu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值