热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

STL进阶指南:第十篇深入解析全排列算法

在编程笔试和面试中,全排列算法因其适中的难度而备受青睐,不仅能够考察应聘者的算法基础,还能测试其对递归和回溯的理解。本文将深入解析全排列算法的实现原理,探讨其应用场景,并提供优化建议,帮助读者更好地掌握这一重要算法。

http://blog.csdn.net/morewindows/article/details/7370155

 

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

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

用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
#include
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 &#61;&#61; m){static int s_i &#61; 1;printf(" 第%3d个排列\t%s\n", s_i&#43;&#43;, pszStr);}else{for (int i &#61; k; i <&#61; m; i&#43;&#43;) //第i个数分别与它后面的数字交换就能得到新的排列{Swap(pszStr &#43; k, pszStr &#43; i);AllRange(pszStr, k &#43; 1, m);Swap(pszStr &#43; k, pszStr &#43; i);}}
}
void Foo(char *pszStr)
{AllRange(pszStr, 0, strlen(pszStr) - 1);
}
int main()
{printf(" 全排列的递归实现\n");printf(" --by MoreWindows( http://blog.csdn.net/MoreWindows )--\n\n");char szTextStr[] &#61; "123";printf("%s的全排列如下:\n", szTextStr);Foo(szTextStr);return 0;
}

或者是&#xff1a;

BOOL OK(char *str,int begin,int end)
{for(int i&#61;begin;i}void perm(char *str,int begin,int end)
{if(begin&#61;&#61;end){cout<}void Foo(char *str)
{perm(str,0,3);
}



二&#xff0e;去掉重复的全排列的递归实现

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

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

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

 


二&#xff0e;去掉重复的全排列的递归实现

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

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

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

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


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

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



三&#xff0e;全排列的非递归实现

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

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

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

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

 

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


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

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

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

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

 

 

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

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

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

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


推荐阅读
  • 深入解析Spring启动过程
    本文详细介绍了Spring框架的启动流程,帮助开发者理解其内部机制。通过具体示例和代码片段,解释了Bean定义、工厂类、读取器以及条件评估等关键概念,使读者能够更全面地掌握Spring的初始化过程。 ... [详细]
  • 实用正则表达式有哪些
    小编给大家分享一下实用正则表达式有哪些,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下 ... [详细]
  • 历经三十年的开发,Mathematica 已成为技术计算领域的标杆,为全球的技术创新者、教育工作者、学生及其他用户提供了一个领先的计算平台。最新版本 Mathematica 12.3.1 增加了多项核心语言、数学计算、可视化和图形处理的新功能。 ... [详细]
  • 深入理解 .NET 中的中间件
    中间件是插入到应用程序请求处理管道中的组件,用于处理传入的HTTP请求和响应。它在ASP.NET Core中扮演着至关重要的角色,能够灵活地扩展和自定义应用程序的行为。 ... [详细]
  • 本题来自WC2014,题目编号为BZOJ3435、洛谷P3920和UOJ55。该问题描述了一棵不断生长的带权树及其节点上小精灵之间的友谊关系,要求实时计算每次新增节点后树上所有可能的朋友对数。 ... [详细]
  • 本文介绍了如何使用JavaScript的Fetch API与Express服务器进行交互,涵盖了GET、POST、PUT和DELETE请求的实现,并展示了如何处理JSON响应。 ... [详细]
  • 本文介绍如何从字符串中移除大写、小写、特殊、数字和非数字字符,并提供了多种编程语言的实现示例。 ... [详细]
  • 深入解析Java虚拟机(JVM)架构与原理
    本文旨在为读者提供对Java虚拟机(JVM)的全面理解,涵盖其主要组成部分、工作原理及其在不同平台上的实现。通过详细探讨JVM的结构和内部机制,帮助开发者更好地掌握Java编程的核心技术。 ... [详细]
  • 在高并发需求的C++项目中,我们最初选择了JsonCpp进行JSON解析和序列化。然而,在处理大数据量时,JsonCpp频繁抛出异常,尤其是在多线程环境下问题更为突出。通过分析发现,旧版本的JsonCpp存在多线程安全性和性能瓶颈。经过评估,我们最终选择了RapidJSON作为替代方案,并实现了显著的性能提升。 ... [详细]
  • 采用IKE方式建立IPsec安全隧道
    一、【组网和实验环境】按如上的接口ip先作配置,再作ipsec的相关配置,配置文本见文章最后本文实验采用的交换机是H3C模拟器,下载地址如 ... [详细]
  • 丽江客栈选择问题
    本文介绍了一道经典的算法题,题目涉及在丽江河边的n家特色客栈中选择住宿方案。两位游客希望住在色调相同的两家客栈,并在晚上选择一家最低消费不超过p元的咖啡店小聚。我们将详细探讨如何计算满足条件的住宿方案总数。 ... [详细]
  • 2018-2019学年第六周《Java数据结构与算法》学习总结
    本文总结了2018-2019学年第六周在《Java数据结构与算法》课程中的学习内容,重点介绍了非线性数据结构——树的相关知识及其应用。 ... [详细]
  • Coursera ML 机器学习
    2019独角兽企业重金招聘Python工程师标准线性回归算法计算过程CostFunction梯度下降算法多变量回归![选择特征](https:static.oschina.n ... [详细]
  • 使用Nginx反向代理实现多域名端口映射
    本文介绍如何通过配置本地hosts文件和Nginx反向代理,实现多个虚拟域名的端口映射,使用户可以通过标准HTTP端口80访问不同后端服务。 ... [详细]
  • 本文总结了优化代码可读性的核心原则与技巧,通过合理的变量命名、函数和对象的结构化组织,以及遵循一致性等方法,帮助开发者编写更易读、维护性更高的代码。 ... [详细]
author-avatar
恐龙
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有