递归(四)—— 初识暴力递归之“打印字符串的全排列”

news2024/9/29 23:30:21

题目1:序列打印一个字符串的全排列

题目分析:结合一实例来理解题目,str = “abc”的全排列就是所求得的序列是 strp[0~2]的所有位的排列组合,strNew = {“abc”, “acb”, “bac”, “bca”,”cba”,”cab”}

思路1:枚举所有可能性, 假设str = “abcd”

Str[0]

Str[1]

Str[2]

Str[3]

a

b

c

d

d

c

c

b

d

d

b

d

c

b

b

c

b

a

c

d

d

c

c

a

d

d

a

d

a

c

c

a

c

a

b

d

d

b

b

a

d

d

a

d

a

b

b

a

c

a

b

d

d

b

b

a

d

d

a

d

a

b

b

a

从表格中可以看到,str[0] 有四种选择,{“a”,”b”, “c” , “d”};

当str[0]的选择确定后,str[1]的选择有3中,即在str[0]确定后,在剩下的三个字符中任意选择一个。

当str[0~1] 确定后,str[2]的选择有两个,即在str[0] 和str[1]选择后剩下的2个字符中,任意选择1个。

当str[0~2] 都确定后,只剩下1个字符,str[3] 只有唯一的字符可用。当没有任何字符可用于继续选择的时候,则认为得到了一个全排列组合。

代码实现

//代码段1
#include <list>
#include <string>
#include <iostream>

//rest: 被选择后,剩余的字符;
//path;已经确定的字符所构成的字符串
//ans:存放每一次得到的字符串
void fun_1(vector<char> rest, string path, list<string>& ans) {
	if (rest.empty()) {
		ans.push_back(path);
	}
	else {
		int count = rest.size();
		for (int i = 0; i < count; i++) {
			char cur = rest[i];
			rest.erase(rest.begin() + i);
			fun_1(rest, path+cur, ans);
			rest.insert(rest.begin()+i, cur);
		}	
	}
	return;
}

#define PERMUTATION //全排列

int main() {
#ifdef PERMUTATION
    list<string> ans;
    string strInput = "abc";
    auto permutationFun= [](string str, list<string>& ans) {
        if (str.length() == 0) return;
        const char* arrChar = str.c_str();
        vector<char> rest(str.size());
        for (int i = 0; i < str.size(); i++) {
            rest[i] = arrChar[i];
        }
        string path = "";
        fun_1(rest, path, ans);
    };
    
    permutationFun(strInput, ans);
    pirnt_list(ans);
#endif

    return 0;
}

代码分析

当代码运行到第18行的时候,遇到了函数调用,再次进入fun_1,直到 rest.empty() == true, 表示没有剩余字符,所有字符都被使用,则将path存入ans。

当代执行到19行时,表示需要恢复现场。试想,我们每次为当前位选中一个字符后,则认为下一位不可以再使用这个字符,就需要将此字符中rest中删除,而当一个全排类完成后,需要将删除的字符重新加入到rest。

运行结果

当 strInput  = “abcd”;

思路2

任意一位与其他位进行交换,即可以得到一个新的全排列的组合,直到枚举所有的交换可能。

index_ 0 与其它位交换的可选性是三种,即 index_0 <-> index_0, index_0 <-> index_1, index_0 <-> index_2;

index_1 与其它位交换的选择性只有两种,即index_1 <->index_1 和index_1 <-> index_2;

Index_2 是最后一位,所以它与其它位交换的可能性只有一种 index_2 <-> index_2

当每一次的交换路径从头走到尾,即index_0 到index_n 都完成了交换,我们认为得到了一个全排列序列。接下来回到最近一次做不同决策的位置。例如上例,当执行:

 index_0 <->index_0 ==> index_1 <-> index_1 ==> index_2 <-> index_2 之后,最近一次决策就是index_2 <-> index_2, 因为它已经是最后一位,只有这一种决策,那么再回到这个决策的上一个决策,index_1 <-> index_1,这个决策是有不同分支的,index_1 <-> index_2 , 按照这条新路径继续走下去,直到最后一位。然后回到index_0 <-> index_0。

index_0 是有其它决策的,index_0 <-> index_1 ,然后按照这条路径继续交换下去,逻辑同上。

直达index_0 的所有决策分支都走完,则认为找到了所有的全排列序列。

代码实现

//代码段2
#include <list>
#include <string>
#include <iostream>

void swap(string& str, int i, int j) {
	char temp = str[i];
	str[i] = str[j];
	str[j] = temp;
}

void pirnt_list(list<string> listInput) {
	for (list<string>::iterator item = listInput.begin(); item != listInput.end(); item++) {
		std::cout << *item << std::endl;
	}
}

//strPre: 每一次全排列后得到的字符串
//index: 当前需要确定的字符串位
//ans:存放每一次全排列得到的字符串
void fun_2(string strPre, int index, list<string>& ans) {
	if (index == strPre.length()) {
		ans.push_back(strPre);
	}
	else {
		for (int i = index; i < strPre.length(); i++) {
			swap(strPre, index, i);
			fun_2(strPre, index + 1, ans);
			swap(strPre, index, i);
		}
	}
	return;
}
#define PERMUTATION //全排列
int main() {  
#ifdef PERMUTATION
    list<string> ans;
    string strInput = "abcd";
    auto permutationFun = [](string str, list<string>& ans) {
        if (str.length() == 0) return;
        const char* arrChar = str.c_str();
        vector<char> rest(str.size());
        for (int i = 0; i < str.size(); i++) {
            rest[i] = arrChar[i];
        }
        string path = "";
        fun_2(str, 0, ans);
        
    };
    
    permutationFun(strInput, ans);
    pirnt_list(ans);
#endif
    return 0;
}

代码分析:

  1. 递归的base  case 就是 第22行 if(index == strPre.length()), 即当字符串中的所有index 都完成了交换,则将当前得到的序列存入ans
  2. 每一次将新的全排列序列写入ans后(即执行下一个支路前),都要恢复现场,即第29行代码.

运行结果

问题补充:打印一个字符串的全部排列,要求不出现重复排列。

问题分析:解决问题的思路与思路2是一样的, 只是在进行两个数据位交换的时候,增加一个判断,判断当前数据位与 要进行交换的数据位的字符是否相同,如果相同,则取消交换。

例如str = “aac”

index_0 与index_1 的字符相同,都是“a”,所以index_0 <-> index_1交换的这条支路的结果与 index _0  <->index_0 这条支路的结果式样的。所以,当判断到index_i <-> index_j 两个交换位的字符相同,则会推出交换。

代码实现

//代码段3
#include <list>
#include <string>
#include <iostream>

void swap(string& str, int i, int j) {
	char temp = str[i];
	str[i] = str[j];
	str[j] = temp;
}

void pirnt_list(list<string> listInput) {
	for (list<string>::iterator item = listInput.begin(); item != listInput.end(); item++) {
		std::cout << *item << std::endl;
	}
}


void fun_3(string strPre, int index, list<string>& ans) {
	if (index == strPre.length()) {
		ans.push_back(strPre);
	}
	else {
		bool* visited = new bool[256];
		for (int i = 0; i < 256; i++) visited[i] = false;
		for (int i = index; i < strPre.length(); i++) {
			if (!visited[strPre[i]]) {
				visited[strPre[i]] = true;
				swap(strPre, index, i);
				fun_3(strPre, index + 1, ans);
				swap(strPre, index, i);
			}
		}
		delete[] visited;
	}
	return;
}

#define PERMUTATION //全排列
int main() {  
#ifdef PERMUTATION
    list<string> ans;
    string strInput = "aac";
    auto permutationFun = [](string str, list<string>& ans) {
        if (str.length() == 0) return;
        const char* arrChar = str.c_str();
        vector<char> rest(str.size());
        for (int i = 0; i < str.size(); i++) {
            rest[i] = arrChar[i];
        }
        string path = "";
        fun_3(str, 0, ans);
        
    };
    
    permutationFun(strInput, ans);
    pirnt_list(ans);
#endif
    return 0;
}

代码分析

 设置标志位数据结构 bool* visited = new bool[256]; 初始化为false, visited[i] 表示对应assicll码为i

的字符是否出现过。

for (int i = index; i < strPre.length(); i++) {
            if (!visited[strPre[i]]) {
               ....
            }

 }

i 位置上的字符与index上字符要交换,只有i 位置上的字符是不曾出现过的,才会交换,如果visiited[strpre[i]] == ture, 表示在这条路基上这个字符与index发生过交换,只要交换过这个字符,就被置为true。

运行结果:

str = "aac",  使用fun_2 没有去重处理:

使用fun_3 去重处理函数: 

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

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

相关文章

外卖小哥必备!高性价比千元机,送餐更高效

有一群赶时间的人&#xff0c;经常看到他们慌忙的穿梭于大街小巷&#xff0c;他们不仅是城市的风景线&#xff0c;更是无数人心中的温暖使者——外卖小哥 在争分夺秒的背后&#xff0c;一台合适的手机&#xff0c;成为了他们不可或缺的必需品&#xff0c;那什么样的手机更方便呢…

从两眼放光到心碎一地《长相思》第二季搞笑爱情转折

这《长相思》第二季的剧情&#xff0c; 简直是心脏按摩器升级版啊&#xff01; 爷爷一开口&#xff0c;要给玱玹安排馨悦当王后 我这小心脏差点就跟着‘嘭’一声 "哎呀&#xff0c;以为要上演宫廷版《速度与激情》 结果小夭女神一出手&#xff0c; 不是醋坛子翻&#…

Ubuntu 20版本安装Redis教程,以及登陆

第一步 切换到root用户&#xff0c;使用su命令&#xff0c;进行切换。 输入&#xff1a; su - 第二步 使用apt命令来搜索redis的软件包&#xff0c;输入命令&#xff1a;apt search redis 第三步 选择需要的redis版本进行安装&#xff0c;本次选择默认版本&#xff0c;redis5.…

Redis基础教程(十九):Redis分区

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; &#x1f49d;&#x1f49…

79 单词搜索

题目 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 单词必须按照字母顺序&#xff0c;通过相邻的单元格内的字母构成&#xff0c;其中“相邻”单元格是那些水平相邻或…

视频短信群发平台的显著优势

视频短信&#xff0c;这一融合了视频、音频与文本的创新通信方式&#xff0c;不仅革新了传统短信的单一形式&#xff0c;更以其独特的魅力引领着移动通信的新风尚。它以移动视频格式&#xff08;如3GP、MP4&#xff09;为载体&#xff0c;通过GPRS网络和WAP无线应用协议&#x…

操作系统之进程控制

进程 一、进程创建1、进程2、fork函数&#xff08;1&#xff09;概念&#xff08;2&#xff09;创建进程执行到内核中的fork代码时&#xff0c;内核做的操作&#xff08;3&#xff09;返回值 3、常规用法4、代码5、执行结果 二、进程终止1、进程的退出结果2、常见退出方法&…

Transformer中的编码器和解码器结构有什么不同?

Transformer背后的核心概念&#xff1a;注意力机制&#xff1b;编码器-解码器结构&#xff1b;多头注意力等&#xff1b; 例如&#xff1a;The cat sat on the mat&#xff1b; 1、嵌入&#xff1a; 首先&#xff0c;模型将输入序列中的每个单词嵌入到一个高维向量中表示&…

JavaScript包管理器:yarn的安装与配置详解

Yarn是一个流行的JavaScript包管理器&#xff0c;它允许开发者使用代码来安装、更新和删除项目中的依赖包。Yarn的安装与配置过程相对简单&#xff0c;以下将详细说明这一过程&#xff1a; 一、Yarn的安装 Yarn的安装可以通过多种方式进行&#xff0c;主要取决于你的操作系统…

深入剖析C++的 “属性“(Attribute specifier sequence)

引言 在阅读开源项目源代码是&#xff0c;发现了一个有趣且特殊的C特性&#xff1a;属性。 属性&#xff08;attribute specifier sequences&#xff09;是在C11标准引入的。在C11之前&#xff0c;编译器特有的扩展被广泛用来提供额外的代码信息。例如&#xff0c;GNU编译器&…

3年经验的B端产品经理,应该是什么水平?

问你一个问题&#xff1a;你觉得3年经验的B端产品经理&#xff0c;应该是什么水平&#xff1f;很多朋友可能也没有仔细想过&#xff0c;自己3年后应该达到一个什么水平&#xff1f;能做什么体量的业务&#xff1f;要能拿多少薪资&#xff1f; 前几天和一个B端产品经理聊天&…

LangChain教程:构建基于GPT的应用程序

ChatGPT和GPT-4的成功表明&#xff0c;通过使用强化学习训练的大型语言模型&#xff0c;可以构建可扩展且功能强大的自然语言处理应用程序。 然而&#xff0c;响应的有用性取决于提示信息&#xff0c;这导致用户探索了提示工程领域。此外&#xff0c;大多数现实世界的自然语言…

突破AI性能瓶颈 揭秘LLaMA-MoE模型的高效分配策略

获取本文论文原文PDF&#xff0c;请在公众号【AI论文解读】留言&#xff1a;论文解读 本文介绍了一种名为“LLaMA-MoE”的方法&#xff0c;通过将现有的大型语言模型&#xff08;LLMs&#xff09;转化为混合专家网络&#xff08;MoE&#xff09;&#xff0c;从而解决了训练MoE…

3DMAX卡死也要安装的10大插件

在探索3DMAX的无限创意边界时&#xff0c;有些插件如同星辰般璀璨&#xff0c;即便面对插件偶尔的“倔强”卡顿&#xff0c;设计师们依然对其爱不释手&#xff0c;誓要将其纳入麾下。以下便是那份令人心动的“卡死也要安装”的10大插件清单&#xff0c;每个都蕴含着设计师对美的…

HKT DICT解决方案,为您量身打造全方位的一站式信息管理服务

随着大数据时代的到来&#xff0c;企业对现代化管理、数据整合与呈现的解决方案需求不断增长。为满足更多企业客户的多元化信息管理发展需求&#xff0c;香港电讯&#xff08;HKT&#xff09;强势推出全面、高效、安全、可靠的一站式DICT&#xff08;Digital Information and C…

Python数据处理之高效校验各种空值技巧详解

概要 在编程中,处理空值是一个常见且重要的任务。空值可能会导致程序异常,因此在进行数据处理时,必须确保数据的有效性。Python 提供了多种方法来处理不同数据对象的空值校验。本文将详细介绍如何对Python中的各种数据对象进行空值校验,并包含相应的示例代码,帮助全面掌握…

mipi协议中的calibration和scramble模式

在MIPI(Mobile Industry Processor Interface)协议中,calibration(校准)和scramble(加扰)模式是两个重要的特性,它们分别用于优化数据传输的准确性和减少信号干扰。以下是对这两个模式的详细解析: Calibration(校准)模式 目的与功能: 校准模式主要用于优化和补偿由…

备考无忧,张驰课堂与刷题共筑六西格玛考试坚实后盾

刷题对考中质协&#xff08;中国质量协会&#xff09;的六西格玛绿带和黑带考试具有显著的帮助&#xff0c;主要体现在以下几个方面&#xff1a; 一、巩固知识点 加深理解&#xff1a;刷题可以帮助考生更深入地理解和记忆六西格玛管理的相关知识点。通过反复练习&#xff0c;…

CAD应用程序开发工具CST CAD Navigator 1.4.0.1 正式发布—— 带来了 G 代码生成功能

CST CAD Navigator是一款兼容Windows和Linux的CAD应用程序。在其简单的界面下&#xff0c;有一个可以快速查看2D图纸和3D模型的强大核心。软件可以轻松地导入和导出文件&#xff0c;获取尺寸&#xff0c;并创建截面视图。 下载最新版CST CAD Navigatorhttps://www.evget.com/p…

七款知名电脑监控软件的介绍(2024年电脑监控软件整理推荐)

在信息化迅猛发展的今天&#xff0c;电脑监控软件成为企业管理和安全防护的重要工具。这类软件不仅有助于提高员工工作效率&#xff0c;还能防范数据泄露&#xff0c;保障企业的核心利益。以下是对几款知名电脑监控软件的介绍&#xff0c;它们在各自领域内都有出色表现。 固信…