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

这篇具有很好参考价值的文章主要介绍了【算法】——全排列算法讲解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言:

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

  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());
        //标记每个位置的字符是否被使用过s
        string tmp;
        arry=vector<int>(s.size());

        dfs(s,0,tmp);
        return res;
    }
};

💨 代码解释:

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

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

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

4️⃣写出递归的条件:

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

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

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

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

 💨 结果展示:

全排列算法,算法,数据结构与算法,数据结构,算法

 


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

链接如下:全排列

链接如下:全排列 ||


(五)总结

到此,关于全排列算法就讲到这里了。希望本文对大家有所帮助,感谢各位的观看!!文章来源地址https://www.toymoban.com/news/detail-706388.html

到了这里,关于【算法】——全排列算法讲解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【数据结构与算法】二叉树的知识讲解

    目录  一,二叉树的结构深入认识  二,二叉树的遍历  三,二叉树的基本运算 3-1,计算二叉树的大小 3-2,统计二叉树叶子结点个数 3-3,计算第k层的节点个数 3-4,查找指定值的结点         二叉树是不可随机访问的,二叉树的结构是通过双亲结点与孩子结点之间的连接进

    2024年02月08日
    浏览(32)
  • 数据结构与算法分析 第六章 图 作业讲解

     参考教材: 《数据结构(C语言版 第2版)》 严蔚敏,李冬梅,吴伟民编著,人民邮电出版社,2022年版。 截图未标明出处均为原创或取自《数据结构(C语言版 第2版)》~   本文对应的作业题讲解视频:   数据结构与算法分析作业讲解视频合集 https://www.bilibili.com/video/BV1N

    2024年02月03日
    浏览(35)
  • 深入讲解Linux内核中常用的数据结构和算法

    Linux内核代码中广泛使用了数据结构和算法,其中最常用的两个是链表和红黑树。 Linux内核代码大量使用了链表这种数据结构。链表是在解决数组不能动态扩展这个缺陷而产生的一种数据结构。链表所包含的元素可以动态创建并插入和删除。链表的每个元素都是离散存放的,

    2023年04月16日
    浏览(30)
  • 数据结构与算法分析 第五章 树和二叉树 作业讲解

     参考教材: 《数据结构(C语言版 第2版)》 严蔚敏,李冬梅,吴伟民编著,人民邮电出版社,2022年版。 截图未标明出处均为原创或取自《数据结构(C语言版 第2版)》~   本文对应的作业题讲解视频:   数据结构与算法分析作业讲解视频合集 https://www.bilibili.com/video/BV1N

    2024年02月02日
    浏览(30)
  • 数据结构与算法分析 第七章 串、数组和广义表 作业讲解

     参考教材: 《数据结构(C语言版 第2版)》 严蔚敏,李冬梅,吴伟民编著,人民邮电出版社,2022年版。 截图未标明出处均为原创或取自《数据结构(C语言版 第2版)》~   本文对应的作业题讲解视频:   数据结构与算法分析作业讲解视频合集 https://www.bilibili.com/video/BV1N

    2024年02月04日
    浏览(39)
  • 数据结构 -最短路径dijkstra(迪杰斯特拉)算法讲解及代码实现

            迪杰斯特拉算法是一种广义的贪心算法,求出局部最优解,再去求全局最优解 举例图:(起始点为1) 辅助数组: s:记录了目标顶点到其他顶点的最短路径是否求得(求得为1,否则为0) p:目标顶点到其他顶点的最短路径的前驱节点 (如,求得1-7-5的最短路径,那

    2024年02月11日
    浏览(25)
  • 【算法与数据结构】归并排序的代码实现(详细图解)以及master公式的讲解

    目录 1、归并排序  1.1、算法描述  1.2、图解说明 2、代码实现  3、master公式 3.1、公式以及结论 3.2、适用于某些特殊的递归 3.3、计算归并排序的时间复杂度 归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用 递归 或者说是 分治法 (Divide and Conquer)的一个非

    2024年02月08日
    浏览(46)
  • 【算法与数据结构】二叉树的三种遍历代码实现(上)—— 用递归序知识点讲解

      本篇博客 (上篇) 先带大家学习 递归方式 进行三种遍历, 而在后续的 (下篇) 中将为大家详细讲解非递归的三种遍历方式。 目录 1、二叉树 2、二叉树的递归遍历 2.1、先序遍历 2.2、中序遍历 2.3、后序遍历  二叉树(Binary tree)是树形结构的一个重要类型。许多实际问

    2024年02月08日
    浏览(32)
  • 数据结构:树和二叉树之-堆排列 (万字详解)

    目录 树概念及结构 1.1树的概念 1.2树的表示 ​编辑2.二叉树概念及结构 2.1概念 2.2数据结构中的二叉树:​编辑 2.3特殊的二叉树: ​编辑 2.4 二叉树的存储结构 2.4.1 顺序存储: 2.4.2 链式存储: 二叉树的实现及大小堆排列 1功能展示 2 定义基本结构 3 初始化 4打印 5销毁 6插入

    2024年02月07日
    浏览(33)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包