程序员的数学基础课
黄申
LinkedIn 资深数据科学家
83374 人已学习
新⼈⾸单¥68
登录后,你可以任选4讲全文学习
课程目录
已完结/共 58 讲
导读 (1讲)
基础思想篇 (18讲)
程序员的数学基础课
15
15
1.0x
00:00/00:00
登录|注册

07 | 排列:如何让计算机学会“田忌赛马”?

比较函数
递归调用
树状结构
不重复排列
重复排列
全排列
密码长度
密码可能性
交叉对比
测试代码
代码实现
数学中的排列
孙膑的策略
思考题
小结
暴力破解密码
网络安全事件
田忌和齐王的比赛
计算机为田忌安排赛马
田忌赛马故事
暴力破解密码如何使用排列思想
如何让计算机学会“田忌赛马”?

该思维导图由 AI 生成,仅供参考

你好,我是黄申。
“田忌赛马”的故事我想你肯定听过吧?田忌是齐国有名的将领,他常常和齐王赛马,可是总是败下阵来,心中非常不悦。孙膑想帮田忌一把。他把这些马分为上、中、下三等。他让田忌用自己的下等马来应战齐王的上等马,用上等马应战齐王的中等马,用中等马应战齐王的下等马。三场比赛结束后,田忌只输了第一场,赢了后面两场,最终赢得与齐王的整场比赛。
孙膑每次都从田忌的马匹中挑选出一匹,一共进行三次,排列出战的顺序。是不是感觉这个过程很熟悉?这其实就是数学中的排列过程。
我们初高中的时候,都学过排列,它的概念是这么说的:从 n 个不同的元素中取出 m(1≤m≤n)个不同的元素,按照一定的顺序排成一列,这个过程就叫排列(Permutation)。当 m=n 这种特殊情况出现的时候,比如说,在田忌赛马的故事中,田忌的三匹马必须全部出战,这就是全排列(All Permutation)。
如果选择出的这 m 个元素可以有重复的,这样的排列就是为重复排列(Permutation with Repetition),否则就是不重复排列(Permutation without Repetition)。
看出来没有?这其实是一个树状结构。从树的根结点到叶子结点,每种路径都是一种排列。有多少个叶子结点就有多少种全排列。从图中我们可以看出,最终叶子结点的数量是 3x2x1=6,所以最终排列的数量为 6。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

排列在计算机中的应用是一个重要的话题,本文通过“田忌赛马”故事引出排列的概念,并探讨了其在计算机中的应用。作者以“田忌赛马”为例,介绍了排列的树状结构和全排列的数量计算方法,并通过代码示例展示了如何使用递归来生成所有可能的马匹出战顺序。文章还讨论了排列在密码暴力破解中的应用,解释了密码长度和字符类型对破解难度的影响。总体而言,本文生动有趣地引出了排列的概念,并通过实例和代码展示了排列在计算机中的应用,为读者提供了一种有趣的学习方式。文章还提出了一个思考题,鼓励读者尝试编写代码来暴力破解一个4位字母密码,从而加深对排列概念的理解。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《程序员的数学基础课》
新⼈⾸单¥68
立即购买
登录 后留言

全部留言(103)

  • 最新
  • 精选
  • alic
    password = 'bacdce' classes = ['a', 'b', 'c', 'd', 'e'] def get_password(n, result = ''): if n == 0: if result == password: print(password) else: for i in classes: new_result = copy.copy(result) new_result = new_result + i get_password(n - 1, new_result) get_password(6)

    作者回复: 可以的👍

    2018-12-28
    4
    28
  • 菩提
    交作业: public class L7_2 { public static void calLetterList(ArrayList<String> l, ArrayList<String> result) { if (result.size() == l.size()) { System.out.println(result); return; } for (String letter : l) { ArrayList<String> newResult = (ArrayList<String>) result.clone(); newResult.add(letter); calLetterList(l, newResult); } } public static void main(String[] args) { ArrayList<String> l = new ArrayList<>(Arrays.asList("a", "b", "c", "d", "e")); calLetterList(l, new ArrayList<>()); } }

    作者回复: 很赞

    2018-12-30
    12
  • Joe
    C++形式交作业,好像用list数据结果会方便一点。 /** permutaion: 排列。 * 从n个数中选出m个数的方式,若不考虑顺序Cn(m),若考虑顺序An(m) */ /* 问题:密码排列 * 假设有一个 4 位字母密码,每位密码是 a~e 之间的小写字。 * 编写密码可能排列方式。 */ #include <iostream> #include <vector> using namespace std; class Permutation { private: int resultCount_ = 0; public: /** Details: 根据输入字母列表,获得所有的排列方式。 * params: result- 当前排列形式, candidate- 未排列字母表。 * return: null */ void breakPassword(vector<string> result, vector<string> candidate) { int len = candidate.size(); if (0 == len) { // 无字母剩余,输出排列结果 outputResult(result); resultCount_++; return; } for (int i = 0; i < len; i++) { vector<string> resultNew; vector<string> candidateLeft; // 读取排列字母 resultNew = result; resultNew.push_back(candidate[i]); // 获得剩余字母表 candidateLeft = candidate; vector<string>::iterator it = candidateLeft.begin(); candidateLeft.erase(it + i); // 递归 breakPassword(resultNew, candidateLeft); } } // 输出结果 void outputResult(vector<string> result) { for (unsigned int i = 0; i < result.size(); i++) { cout << result[i]; } cout << endl; } // 获得所有可能密码总数 int getResultCount() { return resultCount_; } }; int main(void) { vector<string> fourAlphabetString = {"a", "b", "c", "d", "e"}; vector<string> res; Permutation test; test.breakPassword(res, fourAlphabetString); cout << "可能的密码形式:"; cout << test.getResultCount() << "种" << endl; }

    作者回复: c语言确实更简洁👍

    2019-01-09
    9
  • 罗耀龙@坐忘
    茶艺师学编程 1、学完这节课要记住的 ●不重复排列 n!/(n-m)! (1≤m≤n) ●不重复全排列 n! ●重复排列 n^m 2、田忌赛马也好,穷举破解法也好,背后的数学原理都是一样的,就是排列。由此我获得两点体会 ●这就是所谓“等价问题”。正因为存在“等价”,才能实现“融会贯通”。 ●正如黄老师所说,在确定好数学的解决办法后,程序的解法也自然出来了。 把这项本领练到极致的话,也许就能像那位高德纳(《计算机程序设计艺术》的作者),总是能用最慢的电脑获得编程大赛的第一名。

    作者回复: 确实,懂了很多数学原理,算法设计就很简单了,而且效率和质量都会得到提升

    2020-03-30
    7
  • 瓶子dian
    var chars = ['a', 'b', 'c', 'd', 'e'] var result = [] function getPassword(passwordChars, num, password) { if (num == 0) { return result.push(password) } else { for (var i = 0; i < passwordChars.length; i++) { getPassword(passwordChars, num - 1, password + passwordChars[i]) } } } getPassword(chars, 4, '')

    作者回复: 代码很简洁

    2019-01-09
    2
    3
  • alic
    怎么用递归来求?

    作者回复: 具体是指哪道题目?

    2018-12-28
    3
  • 毛毛
    最笨的方法,一个数组A容纳a~e,四个for循环遍历数组A,拼成一个新一维数组B,多个数组B再拼成二维数组,就是最后结果。

    作者回复: 密码短的话,循环嵌套就可以了。如果密码很长,或者长度是满足某种条件的,就需要递归

    2018-12-28
    3
  • 上善若水
    python3 一行解决 answer, = [f"{a}{b}{c}{d}" for a in "abcde" for b in "abcde" for c in "abcde" for d in "abcde" if f"{a}{b}{c}{d}" in password]

    作者回复: Python确实很简洁👍

    2022-07-06
    1
  • suiyueranzly
    来补作业了,老师 -------------------------代码----------------------------------- /** * 排列 * * @param passwords 待排列的字符 * @param results 排列的结果 ***/ public void range(ArrayList<String> passwords, ArrayList<String> results) { //如果为空则不需要排列 if (passwords.isEmpty()) { String collect = String.join("", results); System.out.print(collect + "\t"); } for (int i = 0; i < passwords.size(); i++) { String password = passwords.get(i); ArrayList<String> newResult = (ArrayList<String>) results.clone(); ArrayList<String> newPassword = (ArrayList<String>) passwords.clone(); newResult.add(password); newPassword.remove(i); range(newPassword,newResult); } }

    作者回复: 逻辑清晰

    2019-01-07
    1
  • 买了就等于学了
    田忌赛马还可以用贪心算法来解

    作者回复: 是的,就是比较麻烦一些

    2022-02-18
收起评论
显示
设置
留言
99+
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部