STL系列之十 全排列(百度迅雷笔试题)

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

全排列在笔试面试中很热门,因为它难度适中,既可以考察递归实现,又能进一步考察非递归的实现,便于区分出考生的水平。所以在百度和迅雷的校园招聘以及程序员和软件设计师的考试中都考到了,因此本文对全排列作下总结帮助大家更好的学习和理解。对本文有任何补充之处,欢迎大家指出。

首先来看看题目是如何要求的(百度迅雷校招笔试题)。

用C++写一个函数, 如 Foo(const char *str), 打印出 str 的全排列,
如 abc 的全排列: abc, acb, bca, dac, cab, cba

 

一.全排列的递归实现

为方便起见,用123来示例下。123的全排列有123、132、213、231、312、321这六种。首先考虑213和321这二个数是如何得出的。显然这二个都是123中的1与后面两数交换得到的。然后可以将123的第二个数和每三个数交换得到132。同理可以根据213和321来得231和312。因此可以知道——全排列就是从第一个数字起每个数分别与它后面的数字交换。找到这个规律后,递归的代码就很容易写出来了:

//全排列的递归实现
#include <stdio.h>
#include <string.h>
void Swap(char *a, char *b)
{
	char t = *a;
	*a = *b;
	*b = t;
}
//k表示当前选取到第几个数,m表示共有多少数.
void AllRange(char *pszStr, int k, int m)
{
	if (k == m)
	{
		static int s_i = 1;
		printf("  第%3d个排列\t%s\n", s_i++, pszStr);
	}
	else
	{
		for (int i = k; i <= m; i++) //第i个数分别与它后面的数字交换就能得到新的排列
		{
			Swap(pszStr + k, pszStr + i);
			AllRange(pszStr, k + 1, m);
			Swap(pszStr + k, pszStr + i);
		}
	}
}
void Foo(char *pszStr)
{
	AllRange(pszStr, 0, strlen(pszStr) - 1);
}
int main()
{
	printf("         全排列的递归实现\n");
	printf("  --by MoreWindows( http://blog.youkuaiyun.com/MoreWindows )--\n\n");
	char szTextStr[] = "123";
	printf("%s的全排列如下:\n", szTextStr);
	Foo(szTextStr);
	return 0;
}

运行结果如下:

注意这样的方法没有考虑到重复数字,如122将会输出:

这种输出绝对不符合要求,因此现在要想办法来去掉重复的数列。

 

二.去掉重复的全排列的递归实现

由于全排列就是从第一个数字起每个数分别与它后面的数字交换。我们先尝试加个这样的判断——如果一个数与后面的数字相同那么这二个数就不交换了。如122,第一个数与后面交换得212、221。然后122中第二数就不用与第三个数交换了,但对212,它第二个数与第三个数是不相同的,交换之后得到221。与由122中第一个数与第三个数交换所得的221重复了。所以这个方法不行。

换种思维,对122,第一个数1与第二个数2交换得到212,然后考虑第一个数1与第三个数2交换,此时由于第三个数等于第二个数,所以第一个数不再与第三个数交换。再考虑212,它的第二个数与第三个数交换可以得到解决221。此时全排列生成完毕。

这样我们也得到了在全排列中去掉重复的规则——去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换。用编程的话描述就是第i个数与第j个数交换时,要求[i,j)中没有与第j个数相等的数。下面给出完整代码:

//去重全排列的递归实现
#include <stdio.h>
#include <string.h>
void Swap(char *a, char *b)
{
	char t = *a;
	*a = *b;
	*b = t;
}
//在pszStr数组中,[nBegin,nEnd)中是否有数字与下标为nEnd的数字相等
bool IsSwap(char *pszStr, int nBegin, int nEnd)
{
	for (int i = nBegin; i < nEnd; i++)
		if (pszStr[i] == pszStr[nEnd])
			return false;
	return true;
}
//k表示当前选取到第几个数,m表示共有多少数.
void AllRange(char *pszStr, int k, int m)
{
	if (k == m)
	{
		static int s_i = 1;
		printf("  第%3d个排列\t%s\n", s_i++, pszStr);
	}
	else
	{
		for (int i = k; i <= m; i++) //第i个数分别与它后面的数字交换就能得到新的排列
		{
			if (IsSwap(pszStr, k, i))
			{
				Swap(pszStr + k, pszStr + i);
				AllRange(pszStr, k + 1, m);
				Swap(pszStr + k, pszStr + i);
			}
		}
	}
}
void Foo(char *pszStr)
{
	AllRange(pszStr, 0, strlen(pszStr) - 1);
}
int main()
{
	printf("         去重全排列的递归实现\n");
	printf("  --by MoreWindows( http://blog.youkuaiyun.com/MoreWindows )--\n\n");
	char szTextStr[] = "122";
	printf("%s的全排列如下:\n", szTextStr);
	Foo(szTextStr);
	return 0;
}

运行结果如下:

 

OK,到现在我们已经能熟练写出递归的方法了,并且考虑了字符串中的重复数据可能引发的重复数列问题。那么如何使用非递归的方法来得到全排列了?

 

三.全排列的非递归实现

要考虑全排列的非递归实现,先来考虑如何计算字符串的下一个排列。如"1234"的下一个排列就是"1243"。只要对字符串反复求出下一个排列,全排列的也就迎刃而解了。

如何计算字符串的下一个排列了?来考虑"926520"这个字符串,我们从后向前找第一双相邻的递增数字,"20"、"52"都是非递增的,"26 "即满足要求,称前一个数字2为替换数,替换数的下标称为替换点,再从后面找一个比替换数大的最小数(这个数必然存在),0、2都不行,5可以,将5和2交换得到"956220",然后再将替换点后的字符串"6220"颠倒即得到"950226"。

对于像"4321"这种已经是最“大”的排列,采用STL中的处理方法,将字符串整个颠倒得到最“小”的排列"1234"并返回false。

这样,只要一个循环再加上计算字符串下一个排列的函数就可以轻松的实现非递归的全排列算法。按上面思路并参考STL中的实现源码,不难写成一份质量较高的代码。值得注意的是在循环前要对字符串排序下,可以自己写快速排序的代码(请参阅《白话经典算法之六 快速排序 快速搞定》),也可以直接使用VC库中的快速排序函数(请参阅《使用VC库函数中的快速排序函数》)。下面列出完整代码:

//全排列的非递归实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void Swap(char *a, char *b)
{
	char t = *a;
	*a = *b;
	*b = t;
}
//反转区间
void Reverse(char *a, char *b)
{
	while (a < b)
		Swap(a++, b--);
}
//下一个排列
bool Next_permutation(char a[])
{
	char *pEnd = a + strlen(a);
	if (a == pEnd)
		return false;
	char *p, *q, *pFind;
	pEnd--;
	p = pEnd;
	while (p != a)
	{
		q = p;
		--p;
		if (*p < *q) //找降序的相邻2数,前一个数即替换数
		{
			//从后向前找比替换点大的第一个数
			pFind = pEnd;
			while (*pFind <= *p)
				--pFind;
			//替换
			Swap(pFind, p);
			//替换点后的数全部反转
			Reverse(q, pEnd);
			return true;
		}
	}
	Reverse(p, pEnd);//如果没有下一个排列,全部反转后返回true
	return false;
}
int QsortCmp(const void *pa, const void *pb)
{
	return *(char*)pa - *(char*)pb;
}
int main()
{
	printf("         全排列的非递归实现\n");
	printf("  --by MoreWindows( http://blog.youkuaiyun.com/MoreWindows )--\n\n");
	char szTextStr[] = "abc";
	printf("%s的全排列如下:\n", szTextStr);
	//加上排序
	qsort(szTextStr, strlen(szTextStr), sizeof(szTextStr[0]), QsortCmp);
	int i = 1;
	do{
		printf("第%3d个排列\t%s\n", i++, szTextStr);
	}while (Next_permutation(szTextStr));
	return 0;
}

测试一下,结果如下所示:

将字符串改成"cba"会输出:

 

至此我们已经运用了递归与非递归的方法解决了全排列问题,总结一下就是:

1.全排列就是从第一个数字起每个数分别与它后面的数字交换。

2.去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换。

3.全排列的非递归就是由后向前找替换数和替换点,然后由后向前找第一个比替换数大的数与替换数交换,最后颠倒替换点后的所有数据。

 

转载请标明出处,原文地址:http://blog.youkuaiyun.com/morewindows/article/details/7370155

如果觉得本文对您有帮助,请点击‘顶’支持一下,您的支持是我写作最大的动力,谢谢。



 

Python实战:超分辨率图像质量评估全攻略——PSNR、SSIM、LPIPS与NIQE的代码实现与优化 本文详细介绍了在Python中实现超分辨率图像质量评估的完整方案,涵盖PSNR、SSIM、LPIPS和NIQE四大核心指标。通过模块化代码设计、多进程并行处理和GPU加速优化,帮助开发者构建高效、可扩展的评估系统,并提供了实战避坑指南与结果解读方法。 阅读详情

相关推荐

UE5.3.2 + VS2022社区版在Win11上的C++编译环境配置全解

Unreal Engine 5的C++开发依赖严格的工具链协同,其核心在于Visual Studio编译器版本、Windows SDK与引擎构建系统(UBT)的精准匹配。UE5.3.2强制要求MSVC v143.4(即VS2022 17.4+)及Windows 11 SDK 10.0.22621.0,任何偏差都会引发LNK2019链接失败、C1083头文件缺失或MSB8066构建中断等典型错误。该配置不仅关乎安装顺序,更涉及注册表清理、PATH环境变量、SDK路径注入与运行时DLL加载等底层机制。适用于Wi

weixin_33978722的博客 367

C++模板库STL之全排列

C++模板库STL之全排列

Serven的博客 1万+

【中软软件测试笔试题】中国软件与技术服务股份有限公司笔试一卷

请分别写出ISO7层架构,并分别说明TCP、UDP协议分别在处于哪一层,以及TCP、 UPD协议有什么区别。 答:7 应用层 6 表示层 5 会话层 4 传输层 3网络层 2 数据链路层 1 物理层 第四层;UDP是面向无连接、不可靠数据传输协议; TCP是面向连接、可靠数据传输协议......

Yanan990830的博客 1815

【C++ 】STL求全排列和组合

【代码】【C++ 】STL求全排列和组合。

格物致知 535

ArcGIS利用模型构建器批量导出nc文件中的不同波段数据

ArcGIS利用模型构建器批量导出nc文件中的不同波段数据

Yuanbaokk的博客 7252

相衬成像介绍

相衬成像介绍

qq_41981768的博客 2910

c++ STL 全排列

在c++的STL中有函数可以直接对数组元素进行全排列,即next_permutation和pre_permutation,这两个函数都可以实现全排列,只是排列的顺序不同,next_permutation作用为向后排序,而pre_permutation作用为向前排序。 需要头文件#include &lt;algorithm&gt; 示例 #include &lt;iostream&gt; ...

liushall 4784

cJSON库用完后内存泄漏?手把手教你正确使用cJSON_Delete和free释放资源

本文深入解析cJSON库的内存管理机制,重点解决使用后的内存泄漏问题。通过详细讲解cJSON_Delete和free的正确使用方法,帮助开发者在处理JSON数据时有效释放资源,避免内存泄漏。特别适用于嵌入式系统和资源受限环境,提升开发效率和系统稳定性。

weixin_30808693的博客 418

STL系列之十 全排列 百度迅雷笔试题

STL系列之十 全排列 百度迅雷笔试题

hfgyfd的博客 199

2026 企查查/天眼查:IP频率限制 + 验证码策略,企业工商数据采集实战

企业工商数据采集技术解析与解决方案 本文深度剖析了2026年企查查和天眼查平台的反爬机制与技术对抗方案。主要内容包括: 反爬机制分析: 采用JA4+ TLS指纹识别、极验v6验证码等多层防护 IP限制策略严格区分未登录/普通/VIP账号 精准识别并封禁数据中心IP 核心解决方案: 构建高可用住宅代理池,动态轮换真实用户IP 基于YOLOv8的验证码缺口识别技术(准确率99%) 分布式架构设计,支持千万级数据采集 多账号池管理与行为模拟技术 技术亮点: 动态住宅IP池实现1%以下封禁率 Playwright浏

专注于Python爬虫开发,分享爬虫技巧、项目实战与反爬经验,使用Scrapy、BeautifulSoup等工具,解决数据抓取难题。 1324

OSCP-1-3-natraj(本地文件包含、日志中毒、apache2配置漏洞、nmap提权)

像往常一样,我们将使用“Netdiscover”工具识别主机的 IP。所以,让我们首先用 nmap 列出所有 TCP 端口。我们从访问web服务(80端口)开始,在那里我们找到了几张关于Natraj的图片和信息,我们将查看源代码和robots.txt,似乎没有什么有用的。(或至少,目前)。因此,让我们继续进行。在 Dirb 及其默认字典的帮助下,我们找到了一个名为“”的目录。我们进入并列出一个名为“”的文件:如果我们执行它,我们会看到它什么也不做。我们可能需要添加其他内容。

墨痕诉清风的博客 528
上一篇: 位操作基础篇之位操作全面总结
下一篇: 进程通信之二 管道技术第一篇 输入输出的重定向
MoreWindows
MoreWindows 《白话经典算法》、《秒杀多线程》作者 《白话经典算法》、《秒杀多线程》作者 领域专家: 操作系统技术领域 领域专家: 操作系统技术领域
博客等级 码龄15年 2万+粉丝 157原创
评论 120
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值