【算法】——全排列算法讲解

article/2025/10/14 7:27:30

前言:

今天,我给大家讲解的是关于全排列算。我会从三个方面去进行展开:

  1. 首先,我会给大家分析关于全排列算法的思想和定义;
  2. 紧接着通过手动实现出一个全排列代码来带大家见见是怎么实现的;
  3. 最后我会给出两道题帮助大家去进行理解记忆。


目录

前情摘要

(一)定义和公式讲解

1、定义

2、公式

(二)全排列的初始思想

(三)代码实现

1、递归不去重

2、递归去重

3、非递归实现

(四)题目讲解

1、字符串的排列

(五)总结


前情摘要

  • 在今后的找工作中,全排列相对来说还是一个比较常见问题。不管是不是做算法岗的,在许多的大公司面试中都会考察应聘者有关全排列的问题(就算不让你编写完整代码,也会让你描述大致的思路)。接下来,我就带领大家去学习有关全排列的相关知识。

(一)定义和公式讲解

1、定义

  • 全排列就是从n个不同元素中任取m(m≤n)个元素,按照一定的顺序排列起来,叫做从n个不同元素中取出m个元素的一个排列。
  • 当m=n时所有的排列情况叫全排列
  • 简单点来说就是:全排列的含义就是一个序列所有的排序可能性

我们通过简单的举例带大家直观的认识一下:

💨 假设现在数组中有【1 2 3】这样的三个元素,那么对其进行排列之后结果是什么呢?

我相信聪明的小伙伴们已经知道答案了,具体的如下:

  1. 123
  2. 132
  3. 213
  4. 231
  5. 321
  6. 312

 

💨 假设现在数组中有【1 2 3 4】这样的四个元素,那么对其进行排列之后结果是什么呢?

具体的如下:

  1. 1234
  2. 1243
  3. 1324
  4. 1342
  5. 1432
  6. 1423
  7. 2134
  8. 2143
  9. 2314
  10. 2341
  11. 2431
  12. 2413
  13. 3214
  14. 3241
  15. 3124
  16. 3142
  17. 3412
  18. 3421
  19. 4231
  20. 4213
  21. 4321
  22. 4312
  23. 4132
  24. 4123

2、公式

因此,综上所述,我们可以发现全排列得到的结果的数量跟数学中的全排列问题是一样的

  • 对于给定的序列,假设序列中 N个元素,对其进行全排列之后我们可以得到的排序数量为 N!

(二)全排列的初始思想

1、上述我给出了一个序列的全排列的结果以及全排列得到的最终的结果数量。

2、接下来,我简单的讲述一下上述全排列是如何得到的,对其具体的实现过程进行描述。

💨 具体过程如下:

  • 1、我们以上述数组中的【1 2 3 4】,这里是排序好的,为了更好的说明,我们以【2 1 3 4】为例;
  • 2、首先,我们先保持第一个元素不变,即【2】不变,对数组中剩余的元素【1 3 4】进行排序;
  • 3、同上,继续上述过程。此时我们保持剩余数组中的【1】不变,对【3 4】进行排序;
  • 4、在往下遍历。保持【3】不变,对【4】对进行全排列,由于【4】只有一个,它的排列只有一种:4。因此上述完成之后我们可以得到一种排序结果,即【2 1 3 4】;
  • 5、接下来因为【4】已经遍历过,所以不能在以【4】打头了。此时需要交换【3 4】这两个元素的位置,得到新的一种排序,即【2 1 4 3】 ;
  • 6、完成上述的操作之后,此时【3 4】的情况都写完了,现在进行回溯,则不能以【1】打头了;
  • 7、所以现在我们需要交换的对象就是【1 3】,依次类推,最终我们可以得到以【2】开头的排列有以下几种:
2134
2143
2314
2341
2431
2413
  • 8、在接下来就是改变第一个位置的元素,即通过剩余的【 1 3 4 】作为头元素,在重复上述过程即可得到最终的全排列结果。

(三)代码实现

通过上述的分析,我们不难发现一个问题那就是:

  1. 对于给定的序列,刚开始时我们知道序列的第一个元素,剩余的序列元素此时又可以看成是一个全排列的例子;
  2. 我们可以对剩余的序列进行与开始相同的操作,直到中只一个元素为止。这样我们就获得了所有的可能性。因此不难看出这是一个递归的过程。

接下来,我们简单的实现一下这样的代码:

1、递归不去重

  • 思路如下:
  1. 求n位的字符串的全排列,先确定第0位,然后对后面n-1位进行全排列,在对n-1为进行全排列时,先确定第1位,然后对后面的n-2位进行全排列,...由此得到递归函数和递归的结束条件;
  2. 因此解决n-1位元素的全排列就能解决n位元素的全排列了。

  • 代码如下:
//生成向量的所有排列
void permute(string str, int left, int right)
{//如果遍历到 left == right ,说明全排列已经做完了只需输出即可if (left == right) cout<<str<<endl;else {// 递归情况:生成剩余元素的排列for (int i = left; i <= right; i++) {// 将当前元素与第一个元素交换//保持第一个元素固定并生成其余元素的排列swap(str[left], str[i]);// 递归调用permute(str, left+1, right);//进行回溯swap(str[left], str[i]);}}
}int main() 
{string str = "211";int size = str.size();permute(str, 0, size-1);return 0;
}

💨 上面的程序乍一看没有任何问题了。但是当我们去想去打印输出时,看看运行的结果会怎么样:

  • 结果如下:

  • 现象解释:
  1. 从上我们可以发现出现了好多的重复。
  2. 重复的原因当然是因为我们列举了所有位置上的可能性,而没有太多地关注其可能存在重复的数值。

2、递归去重

那么此时很多小伙伴就要问了,我们想实现去重的排列可不可以呢?答案当然是可以的。具体如下:

  • 思路如下:

去重跟不去重相差的其实就是对于元素的值的比较,我们设置一个 flag 标志位,来判断当前元素之后的元素是否于当前元素相同,再利用标志位去对其设限即可实现递归去重操作。

  • 代码如下:
void generatePermute(vector<int>& arry, int start, vector<vector<int>>& tmp) 
{if (start == arry.size()) {tmp.push_back(arry);return;}for (int i = start; i < arry.size(); i++) {// 检查与前一个元素的交换是否会产生重复项bool flag = false;for (int j = start; j < i; j++) {if (arry[i] == arry[j]) {flag = true;break;}}if (!flag) {swap(arry[start], arry[i]);generatePermute(arry, start+1, tmp);swap(arry[start], arry[i]);}}
}vector<vector<int>> permute(vector<int>& arry) 
{vector<vector<int>> tmp;sort(arry.begin(), arry.end());generatePermute(arry, 0, tmp);return tmp;
}int main() 
{vector<int> arry = {1, 1, 2};vector<vector<int>> res = permute(arry);for (auto& e1 : res) {for (auto& e2 : e1) {cout << e2 << " ";}cout << endl;}return 0;
}
  • 运行结果:

 

💨  我们在把 arry数组中的元素换为【1 2 2】,看最终结果是否正确,具体如下:

 

  • 代码解释:
  1. 上述实现生成了输入向量 arry 的所有排列,同时避免了重复。
  2. permute 函数首先对输入向量进行排序,以将相同值的元素分组在一起。

  3. 然后,它调用 generatePermute函数,以递归方式生成从给定索引开始的所有排列。generatePermute函数将当前索引处的元素与所有后续元素交换以生成排列,同时还检查与以前交换的元素的重复项。

3、非递归实现

由于非递归的方法是基于对元素大小关系进行比较而实现的,所以这里暂时不考虑存在相同数据的情况。

  • 代码如下:
void permute(vector<int>& arry) 
{sort(arry.begin(), arry.end());stack<pair<int, int>> tmp;tmp.push({-1, 0});while (!tmp.empty()) {int i = tmp.top().first, j = tmp.top().second;tmp.pop();if (i >= 0) swap(arry[i], arry[j]);if (j == arry.size() - 1) {// 输出排列for (int k = 0; k < arry.size(); k++) {cout << arry[k] << " ";}cout << endl;} else {for (int k = arry.size() - 1; k > j; k--) {tmp.push({j, k});}tmp.push({-1, j+1});}}
}int main() 
{vector<int> arry = {0,2,2};permute(arry);return 0;
}
  • 代码解释:
  1. main() 函数创建一个包含三个元素的向量 arry,并以 arry 作为输入调用 permute() 函数;
  2. 首先,函数void permute(vector<int>&arry)将整数向量的引用作为输入,该向量使用STL中的sort()函数按升序排序;
  3. 其次,维护一堆整数对 {i,j},其中 i 和 j 是数组 arry 的索引;
  4. 最初,堆栈包含对 {-1, 0};
  5. 然后,该算法从堆栈中重复弹出顶部对,通过交换索引 i 和 j 处的元素来更新 arry(如果 i >= 0),并将新对推送到堆栈上;
  6.  最后,当栈中元素全被弹出,栈顶和栈底相遇,则所有状态全被穷尽。arry 的所有排列都已生成并打印到标准输出。

(四)题目讲解

接下来,我通过LeetCode上的三道题目给大家练练手,帮助大家理解记忆!!!

1、字符串的排列

链接如下:字符串的排列

💨 题目如下:

 💨 思路讲解:

  1. 上述我们是通过数组求全排列,而本题是字符串进行相关操作;
  2. 其实字符串与数组没有区别,一个是数字全排列,一个是字符全排列,因此大致思路与上述是类似;
  3. 为了便于后序去重,我们先优先按照字典序排序,因为排序后重复的字符就会相邻,后续递归找起来也很方便;
  4. 首先使用临时变量去组装一个排列的情况:每当我们选取一个字符以后,就确定了其位置,相当于对字符串中剩下的元素进行全排列添加在该元素后面,给剩余部分进行全排列就是一个子问题,因此可以使用递归;

 💨 代码展示:

class Solution {
public:vector<int> arry;vector<string> res;void dfs(const string &s, int num, string &tmp){//临时字符串满了加入输出if(num == s.size()){res.push_back(tmp);return;}//遍历所有元素选取一个加入for(int i=0; i<s.size(); ++i){if(arry[i] == 0){tmp+=s[i];arry[i]=1;dfs(s,num+1,tmp);arry[i] = 0;tmp.pop_back();while(i<s.size()-1  && s[i] == s[i+1])i++;}}}vector<string> permutation(string s) {//先对字符串进行排序处理sort(s.begin(), s.end());//标记每个位置的字符是否被使用过sstring tmp;arry=vector<int>(s.size());dfs(s,0,tmp);return res;}
};

💨 代码解释:

1️⃣ 首先优先按照字典顺序进行排序;

2️⃣ 创建一个 arry 数组标记字典中的字母是否被选择;

3️⃣紧接着就进入dfs 函数进行递归操作;

4️⃣写出递归的条件:

    ①如果填充的字符串已经到达该最后的长度,那么说明这个字符串就是我们需要的;

    ②紧接着进行遍历操作,查找还未使用的序列,并尝试使用该字母:

  • 如果该怎么标记为0 ,则表示没有使用过,如果没有使用过则把它加入到 【tmp】里面,紧接着标记为1,表示已经使用过该字母;
  • 然后从当前位置的下一个位置开始在进行遍历操作;
  • 等到它从下一个位置遍历后来之后,我们就需要弹出【tmp】中的元素,并把它标记为0在进行下一步遍历

    ③如果我们已经使用了【i】位置处的值,那么后面与它相等的就不在使用,直接进行 ++操作

 💨 结果展示:

 


还有两道题,一道是去重的,另外一道是不去重的,大家可以自己尝试做做看,如果以下两题自己会做了,那么对于全排列算法,大家基本上就掌握了。

链接如下:全排列

链接如下:全排列 ||


(五)总结

到此,关于全排列算法就讲到这里了。希望本文对大家有所帮助,感谢各位的观看!!


http://chatgpt.dhexx.cn/article/BkW3V54c.shtml

相关文章

算法 | 全排列问题(图文详解)

目录 一.全排列的定义 1.什么是全排列 2.例子 二.code 三.分析 一.全排列的定义 1.什么是全排列 从n个不同元素中任取m&#xff08;m≤n&#xff09;个元素&#xff0c;按照一定的顺序排列起来&#xff0c;叫做从n个不同元素中取出m个元素的一个排列。当mn时所有的排列情…

全排列算法思路解析

1.全排列的定义和公式&#xff1a; 从n个数中选取m&#xff08;m<n&#xff09;个数按照一定的顺序进行排成一个列&#xff0c;叫作从n个元素中取m个元素的一个排列。由排列的定义&#xff0c;显然不同的顺序是一个不同的排列。从n个元素中取m个元素的所有排列的个数&#…

全排列算法

全排列的概念 排列 从n个数中选取m&#xff08;m<n&#xff09;个数按照一定的顺序进行排成一个列&#xff0c;叫作从n个元素中取m个元素的一个排列。不同的顺序是一个不同的排列。从n个元素中取m个元素的所有排列的个数&#xff0c;称为排列数&#xff08;几种排法&#…

激活JRebel

生成新的 GUID 将生成的GUID 粘贴到此处 https://jrebel.qekang.com/ 如&#xff1a; https://jrebel.qekang.com/cf0c9d95-c31f-4e75-bc5a-146291b8bb71

JRebelXRebel的配置和使用(进阶篇)

JRebel&XRebel的配置和使用 嘚吧嘚设置JRebel快捷键XRebel使用 嘚吧嘚 之前简单介绍了JRebel&XRebel的安装和使用&#xff0c;不了解的朋友可以补补课哈&#x1f606;。 JRebel&XRebel这款插件不仅仅可以用来热部署&#xff0c;所以继续分享一下这款插件的相关使…

IDEA热部署JRebel插件激活教程

JRebel简介 JRebel是一款实现热部署的开发工具&#xff0c;它可以允许你在启动程序时修改java代码直接进行编译生效&#xff0c;无须手动重启。热部署的实现会为你节省了大量重启时间&#xff0c;明显提高个人开发效率。 安装JReable 同其它插件安装一样&#xff0c;请按照以…

Idea热部署插件JRebel+XRebel

1、下载地址&#xff1a;https://plugins.jetbrains.com/plugin/4441-jrebel-and-xrebel/versions 保险起见这里建议下载2022.4.1版本&#xff0c;否则容易出现JRebel LS client not configured的问题&#xff0c;如果你已经出现了&#xff0c;可以参考 https://blog.csdn.ne…

JRebel 热部署

前言 Jrebel 可快速实现热部署&#xff0c;节省了大量重启时间&#xff0c;提高了个人开发效率。 IDEA上原生是不支持热部署的&#xff0c;一般更新了 Java 文件后要手动重启 Tomcat 服务器&#xff0c;才能生效&#xff0c;浪费时间浪费生命&#xff0c;目前对于idea热部署最…

IDEA热部署插件JRebel使用

JRebel安装与激活 JRebel 使用 此时已经安装好并已激活&#xff0c;我们使用 JRebel debug的时候&#xff0c;修改代码&#xff0c;不能实现热部署&#xff0c;因此还需要设置其他地方 1.项目自动编译 设置 compiler.automake.allow.when.app.running ctrlshiftA 或者 help->…

IDEA插件-热部署:JRebel

一、摘要 springboot项目开发过程中通常修改了某分部代码需要重启服务才能生效。通过JRebel插件可以实现热部署&#xff0c;避免了频繁重启服务。 JRebel是一套JavaEE开发工具。 Jrebel 可快速实现热部署&#xff0c;节省了大量重启时间&#xff0c;提高了个人开发效率。 二、…

Jrebel安装使用教程 及 Jrebel 4.2版本激活失效的处理(超简洁明了)

写在前面&#xff1a;不小心更新了Jrebel插件导致插件用不了qwq&#xff0c;经过查阅多方面的资料总结出了解决这个问题的方法&#xff0c;现分享给大家~ &#xff08;如果原本没安装JRebel插件请直接从第三步开始看~&#xff09; 第一步&#xff1a;删除4.2版本 先在Plugins…

JRebel插件热部署快速入门教程

文章目录 引入插件安装插件激活打开激活窗口激活插件 插件使用设置项目热更新热更新说明演示热更新 引入 视频讲解JRebel安装、使用、部署 Jrebel能够非常方便的帮助我们进行项目的热更新&#xff0c;尤其是前端也嵌在后端工程中的单体项目&#xff0c;热更新能减少一半的开发…

JRebel插件使用详解

JRebel插件安装与使用图文教程 JRebel是一款JVM插件&#xff0c;它使得Java代码修改后不用重启系统&#xff0c;立即生效。IDEA上原生是不支持热部署的&#xff0c;一般更新了 Java 文件后要手动重启 Tomcat 服务器&#xff0c;才能生效&#xff0c;浪费时间浪费生命。 目前对…

javaweb开发,提高效率利器——JRebel

JRebel用了有一段时间了&#xff0c;发现确实好用&#xff0c;节省了很多不必要的时间&#xff0c;提高了开发效率。在这里记录一下他的安装和使用过程&#xff0c;希望能帮助到有需要的人。 官网&#xff1a;https://www.jrebel.com/ 一、JRebel简介 jrebel是国外公司perfo…

JRebel 和 XRebel 激活

1&#xff1a;JRebel 和 XRebel的作用 JRebel&#xff1a;修改完代码&#xff0c;不想重启服务&#xff0c;就使想代码生效。 XRebel&#xff1a;请求中&#xff0c;各个部分代码性能监控。例如&#xff1a;方法执行时间&#xff0c;出现的异常&#xff0c;SQL执行时间&#x…

JRebel安装、最新激活方式

安装JRebel 1、在IDEA中一次点击 File->Settings->Plugins->Brows Repositories 2、在搜索框中输入JRebel进行搜索 3、找到JRebel for intellij 4、install 5、安装好之后需要restart IDEA 激活JRebel 生成 GUID 的网址 https://www.guidgen.com/用这个网址 生成的…

Jrebel激活

一般一个产品的生命周期就像一个抛物线。JRebel 亦是如此。 做 Java 开发&#xff0c;离不开热发布、热部署。JRebel 就是一套 JavaEE 开发工具 JRebel 可快速实现热部署&#xff0c;节省了大量重启时间&#xff0c;提高了个人开发效率。 JRebel 是一款 JAVA 虚拟机插件&…

idea使用JRebel插件详解

简介 JRebel是一套JavaEE开发工具。 Jrebel 可快速实现热部署&#xff0c;节省了大量重启时间&#xff0c;提高了个人开发效率。 JRebel是一款JAVA虚拟机插件&#xff0c;它使得JAVA程序员能在不进行重部署的情况下&#xff0c;即时看到代码的改变对一个应用程序带来的影响。JR…

Idea之热部署插件JRebel+XRebel

文章目录 Idea之热部署插件JRebelXRebel1. devtools热部署1. 使用流程2. 缺点 2. Jrebel and XRebel for IntelliJ1. 安装2. 激活JRebel-方式一3. 激活JRebel方式二4. 使用JRebel5. 使用XRebel Idea之热部署插件JRebelXRebel 热部署&#xff1a;就是在修改代码之后&#xff0c…

JRebel 热部署插件的安装使用

文章目录 Jrebel简介JRebel的安装和使用idea安装JRebelJRebel的使用 JRebel的Activation Jrebel简介 当你修改doGet&#xff0c;doPost等一些内容时&#xff0c;你再次访问&#xff0c;访问到的内容不变&#xff0c;除非重启或重新加载class文件。   用Jrebel 可快速实现热部…