C++字符串全排列(递归法)和(迭代法)以及next_permutation底层原理详解

news2024/9/22 13:36:55

目录

    • 前言
    • next_permutation的使用
    • 实现全排列的两种算法
      • 1. 递归法(全排列方便理解记忆的方法,作为备用方法)
        • 实现代码(无重复元素情况)
        • 有重复元素情况
      • 2. 迭代法(next_permutation底层原理)
        • 实现代码(有无重复不影响)

前言

next_permutation/prev_permutation是C++ STL中的一种实用算法;

功能是: 以迭代器的方式,将一个容器内容改变为他的下一个(或prev上一个)全排列组合;

在这里插入图片描述

next_permutation的使用

假设需要将字符串abcd的全排列依次打印,我们可以用next_permutation函数方便操作:

使用方法:

  1. 一般先sort成升序;(prev_permutation倒着全排列使用规则相反)

  2. 然后再do while配合调用全排列,循环输出,直到排列情况全部输出 返回false;

在这里插入图片描述

运行结果:

在这里插入图片描述

实现全排列的两种算法

当然仅仅只会用没有什么困哪的,如果面试官突然问你STL中这个全排列算法咋实现的呢?

1. 递归法(全排列方便理解记忆的方法,作为备用方法)

如果上面全排列,突然脑袋断片了,或者说考试中不让用封装好的库函数;

为了不至于连个全排列的思想都不会,可以用用相对好理解的递归法全排列

算法思想:(递归问题:按规则处理一个过程,剩下的过程是相同的处理方式,那么就可以进行函数递归调用)

  1. 从集合中依次选出每一个元素,作为排列的第一个元素
  2. 然后**对剩余的元素(第一个元素之后的)进行 同样操作 **;

如此递归处理,从而得到所有元素的全排列。

eg:

  • 以对字符串abc进行全排列为例,我们可以这么做:以abc为例

固定a,递归求后面bc的排列:求好: abc,acb后,b交换到第一位置,得到bac,如下固定b递归b后面的排列:
固定b,求后面ac的排列:bac,bca,求好后,c交换到第一位置,得到cba,如下固定c递归c后面的排列:
固定c,求后面ba的排列:cba,cab;结束 一共a,b,c分别当第一个元素进行了全排列,算法结束;

(注意:1. 每次交换下一个位置的时候,需要swap换回来,保证原始序列,再交换下一个位置的字母去第一个位置。2. 需要考虑有重复相同且挨着的数字情况,此时需要剪枝)

递归比较抽象,可以用简单地例子abc在纸上模拟画一下好理解;

实现代码(无重复元素情况)

#include<iostream>
#include<vector>
using namespace std;

//dfs实现全排列(无重复元素情况)
void dfs(string &s, int l, int r)
{
	if (l == r) {//递归终止,当前s可以输出了,已经是某一轮的完整排列,不能再排列了
		cout<<s<<endl;
		return;
	}

	for (int i = l; i < r; i++) {
        
            swap(s[l],s[i]);
			dfs(s,l+1,r);//递归
			swap(s[l], s[i]);//进行下一轮的swap dfs,需要先swap换回来原来的位置!否则会出现重复排列!
		
	}
}


int main()
{
	string s = "abcd";
    //sort(s.begin(),s.end())//sort一下,再配合dfs算法,可以实现按照字典序处理
    int len = s.size();
	dfs(s,0,len);
	return 0;
}

运行结果:

在这里插入图片描述

有重复元素情况

在这里插入图片描述

这种情况需要对代码做一个优化,不然的话按照上面的算法,会出现重复数字的重复排列情况;

优化很简单:

如果某个数字在之前的排列被换到首位置进行排列过,那本次交换就不进行;(其次,当dfs首次交换i==l的时候,即便出现过也需要进行);

整合一下就是if(i==l || s[i]没出现) -->进行交换)

代码:

#include<iostream>
#include<algorithm>
#include<set>
using namespace std;

//dfs实现全排列(含重复元素情况)
void dfs(string &s, int l, int r)
{
	if (l == r) {
		cout<<s<<endl;
		return;
	}
	set<char>st;//检测重复的set
	for (int i = l; i < r; i++) {
		if (i == l || st.find(s[i])==st.end()) {//防止后续进行重复排列
			st.insert(s[i]);//满足  记录这个字符 

			swap(s[l], s[i]);
			dfs(s, l + 1, r);
			swap(s[l], s[i]);
		}
	}
}


int main()
{
	string s = "aba";
	int len = s.size();
	dfs(s,0,len);
	return 0;
}

2. 迭代法(next_permutation底层原理)

比较抽象,难以理解,根据个人情况来理解;

一个全排列可看做一个字符串,字符串可有前缀、后缀。
规定: 生成给定全排列的下一个排列–> 所谓一个字符串的下一个排列,就是这个个字符串变化限制在尽可能短的后缀上,变化后的那个字符串; 这就要求这一个与下一个有尽可能长的共同前缀;变化限制在尽可能短的后缀上
eg:

839647521是1—9的排列。1—9的排列最前面的是123456789,最后面的987654321;

从右向左扫描若都是增的,就到了987654321,也就没有下一个了。否则找出第一次出现下降的位置。


如何得到346987521的下一个?

  • 首先对原生字符串排序,这个迭代算法是基于字典序排序好的字符串全排列;(所以之后从尾部,向前循环迭代,每次变化尽可能短的后缀,以此类推)
  1. 从尾部往前找第一个P(i-1) < P(i)的位置;: 346987521 种 最终找到6是第一个变小的数字,记录下6的位置i-1
  2. 从找到的 i 位置往后找到最后一个大于6的数:346987521 中最终找到7的位置,记录位置为m(m == r-1)
  3. swap(r-1,i-1) : 3 4 7 9 8 6 5 2 1
  4. 倒序翻转i位置后的所有数据 : 3 4 7 1 2 5 6 8 9
  5. 进行do-while循环,直到第一步之后,判断出 i==0 break; 全部排列完毕

很抽象,但是思想就是 一个字符串的下一个全排列:有尽可能长的共同前缀;变化限制在尽可能短的后缀上

大概知道步骤: 那么用排好序的123456789试试上面的过程,你会发现,每次变化尽可能短的后缀,有点像递归的感觉,一步一步逼近字符串0,index,此时完美结束; 或者用123这个例子体验一下6个全排列是咋来的 amazing

上面的过程多悟几遍就理解了; 大概知道思想面试的时候说说也行=-=

实现代码(有无重复不影响)

#include<iostream>
#include<algorithm>
#include<set>
using namespace std;

//dfs实现全排列



int main()
{
	string s = "abcd";
	
	sort(s.begin(),s.end());//记得先排序
	
	int len = s.size();
	do
	{
		cout << s << endl;//打印某次排列

		int i = s.size() - 1;
		int j;
		while (i > 0 && s[i] <= s[i - 1]) i--;//1.从后向前 找 第一个 s[i-1]<s[i]

		if (i == 0) break;

		j = i;

		while (j<len && s[j]>s[i - 1]) j++;//2.从i向后 找 最后一个 s[m]>s[i-1] 用j找,所以最后m==j-1

		swap(s[i - 1], s[j - 1]);//3. swap (i-1,m(j-1))

		reverse(s.begin() + i, s.end());  //4. 翻转 i后面的子串

		

	} while (1); //do while为了让第一个 abcd 也正常打印再全排列

	return 0;
}

这个算法理解起来对于我来说有点夸张,呜呜呜;

牛客题目链接

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/188457.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

全国青少年软件编程(Python)等级考试一级考试真题2022年12月——持续更新.....

1.关于Python语言的注释,以下选项中描述错误的是?( ) A.Python语言有两种注释方式:单行注释和多行注释 B.Python语言的单行注释以#开头 C.Python多行注释使用###来做为标记 D.注释用于解释代码原理或者用途 正确答案:C 2.下列代码执行后最有可能绘制出的图形是?(…

网络原理(TCP/IP五层协议)(三)

目录4.滑动窗口(效率机制)5.流量控制(安全机制)6.拥塞控制(安全机制)7.延迟应答(效率机制)8.捎带应答(效率机制)9.面向字节流10.TCP的异常处理4.滑动窗口(效率机制) 滑动窗口存在的意义就是在保证可靠性的前提下&#xff0c;尽量提高传输效率。 在这里可以看到&#xff0c;由于…

JSP 学生成绩管理系统myeclipse定制开发sqlserver数据库网页模式java编程jdbc

一、源码特点 JSP 学生成绩管理系统是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为SQLServer2008&#x…

layui 富文本layedit编辑、存储和回显

一、创建一个富文本编辑框 先定义一个textarea标签&#xff0c;给定一个id值&#xff0c;向页面引入layedit&#xff0c;然后调用layedit.build(id, options)构建富文本框 //官方给出的模板 <textarea id"demo" style"display: none;"></textar…

Linux的目录相关操作

目录 前言 处理目录的常见命令 cd&#xff08;change directory&#xff0c;切换目录&#xff09; pwd&#xff08;print working directory&#xff0c;显示目前所在的目录&#xff09; mkdir&#xff08;make directory&#xff0c;建立新目录&#xff09; rmdir&#x…

代码随想录算法训练营第十三天 | 第六章二叉树-理论基础,递归遍历,迭代遍历,统一迭代

一、参考资料二叉树理论基础文章讲解&#xff1a;https://programmercarl.com/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html 递归遍历题目链接/文章讲解/视频讲解&#xff1a;https://programmercarl.com/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E9%8…

Mobilenet v1-v3

MobileNet V1 理解 MobileNetV1的关键是理解深度可分离卷积 深度可分离卷积 Depthwise Separable Conv 深度可分离卷积单通道卷积&#xff08;提取特征&#xff09;逐点卷积&#xff08;增加维度&#xff09; 普通卷积 输入一个 12123 的一个输入特征图&#xff0c;经过256…

微服务/分布式初始

1.单体服务架构的特点 当服务单一、规模小、逻辑简单时&#xff0c;用一个单体服务就挺 单体服务的缺点 复杂程度高。维护成本越来越高&#xff0c;各个模块之间边界模糊&#xff0c;一个模块的改动可能导致整个服务出现问题&#xff0c;一点内存泄漏、一处指针错误就会让整…

汉诺塔+汉诺四塔(C/C++)

目录 汉诺塔 1 简介 2 代码思路 2.1 对于次数的理解 2.2 对于移动的理解 3 代码 4 加深理解 汉诺四塔 1 思路 2 代码 汉诺塔 1 简介 汉诺塔(Tower of Hanoi)&#xff0c;又称河内塔&#xff0c;是一个源于印度古老传说的益智玩具。大梵天创造世界的时候做了三…

关于e^x的部分公式和约算方法

常用的几个不等式: ex≥x1e^{x}\geq x1ex≥x1ln⁡x≤x−1\ln x\leq x-1lnx≤x−1ex≥exe^{x} \geq exex≥exex≥1xx22e^x\geq1x\frac{x^2}{2}ex≥1x2x2​ 当x>0时&#xff0c;ex≥ex(x−1)2x2−(e−2)x1e^x\geq ex(x-1)^{2}x^2-(e-2)x1ex≥ex(x−1)2x2−(e−2)x1 上述算式在…

数据库系统概论——函数依赖、码和范式(1NF、2NF、3NF、BCNF)详解

文章目录概念回顾1、函数依赖的定义1.1 平凡函数依赖和非平凡函数依赖1.2 完全函数依赖和部分函数依赖1.3 传递函数依赖2、码2.1 主码和候选码2.1主属性与非主属性2.2 全码2.3 外部码3、范式3.1 第一范式&#xff08;1NF&#xff09;3.2 第二范式&#xff08;2NF&#xff09;3.…

现在的互联网技术,已蜕变成区块链技术,人工智能技术

在互联网的进化过程中&#xff0c;我们看到了互联网技术的不断孪生与蝶变。现在的互联网技术&#xff0c;早已不再是传统意义上的互联网技术&#xff0c;而是蜕变成为了大数据技术&#xff0c;云计算技术&#xff0c;蜕变成为了区块链技术&#xff0c;人工智能技术。这些新的技…

【STM32笔记】HAL库ADC测量精度提高方案(利用内部参考电压VREFINT计算VDDA来提高精度)

【STM32笔记】HAL库ADC测量精度提高方案&#xff08;利用内部参考电压VREFINT计算VDDA来提高精度&#xff09; 多数STM32的MCU 都没有内部基准电压 如L496系列 但在外接VDDA时&#xff08;一般与VCC 3.3V连接&#xff09; 有可能VCC不稳定 导致参考电压不确定 从而使ADC测量不…

深度学习调参炼丹术(总结向)

调参控制变量&#xff0c;每次调一个值。 1.初始化方式&#xff1a;FC/CNN用kaiming uniform或normalize&#xff0c;Emendding选截断normalize 2.activation function&#xff1a;sigmoid(淘汰)、tanh(淘汰)、relu(推荐)、leakey-relu 3.优化器&#xff1a;SGD动量&#xff0…

若依配置教程(三)新建模块

若依模块化管理&#xff0c;使代码更加规范化&#xff0c;方便在不同文件夹下进行修改和开发。 接下来是新建模块的步骤&#xff1a; 文章目录**接下来是新建模块的步骤&#xff1a;**1.创建新的module2.配置pom.xml1.创建新的module 项目上鼠标右击&#xff1a; 然后修改项…

Kettle 实战教程

Kettle 实战教程1.引言....................................................................................81.1 编写目的...........................................................81.2 阅读对象...........................................................91.3 术…

DBCO-SS-Mal;DBCO二硫键-马来酰亚胺-点击化学

DBCO-SS-Maleimide&#xff1b;Mal-SS-DBCO 英 文 &#xff1a;DBCO-SS-Maleimide 中文&#xff1a;二苯并环辛炔-二硫键-马来酰亚胺 分子式&#xff1a;C30H30N4O5S2 分子量&#xff1a;590.71 存储条件&#xff1a;-20C&#xff0c;避光&#xff0c;避湿 用 途&#xff…

做自媒体,有哪些好用的工具和软件,这6大自媒体工具,力荐!

工具一&#xff1a;效率控 效率控是一个汇集很多工具的工具APP。使用它可以用来习惯养成&#xff0c;表情制作&#xff0c;二维码生成&#xff0c;倒数日&#xff0c;文字识别&#xff0c;配色方案等等。 它特别一个亮点的功能是剪切板增强&#xff0c;还有一个特别实用的功能是…

DevStyle,一个让Java开发更现代化的工具!

如果您喜欢Eclipse的强大功能&#xff0c;但对它的可用性和美观度没有很高的要求&#xff0c;那么从今天开始&#xff0c;请准备好从全新的角度来看待Eclipse。在之前暗黑的插件基础上&#xff0c;MyEclipse官方团队为大家带来了DevStyle。使用DevStyle&#xff0c;开发人员可以…

小程序提升篇-组件

提升篇学习目标如何自定义小程序组件小程序组件中behaviors的作用安装和配置vant-weapp组件库如何使用MobX实现全局数据共享如何对小程序API进行Promise化小程序组件学习目标创建并引用组件&#xff08;全局、局部、usingComponent&#xff09;修改组件样式&#xff08;option-…