排列:从n个元素中任取m个元素,并按照一定的顺序进行排列,称为排列;
全排列:当n==m时,称为全排列;
比如:集合{ 1,2,3}的全排列为:
{ 1 2 3} { 1 3 2 } { 2 1 3 } { 2 3 1 } { 3 2 1 } { 3 1 2 }
我们可以将这个排列问题画成图形表示,即排列枚举树,比如下图为{1,2,3}的排列枚举树,此树和我们这里介绍的算法完全一致;
算法思路:
(1)n个元素的全排列=(n-1个元素的全排列)+(另一个元素作为前缀);
(2)出口:如果只有一个元素的全排列,则说明已经排完,则输出数组;
(3)不断将每个元素放作第一个元素,然后将这个元素作为前缀,并将其余元素继续全排列,等到出口,出口出去后还需要还原数组;
这里先把集合中的元素理解为不会出现重复了,那么实现的方法(C++)如下:
成员管理,互评,文件共享,事务通知 #include <iostream> using namespace std;int sum = 0;//记录有多少种组合
void Swap(char str[], int a, int b) { char temp = str[a]; str[a] = str[b]; str[b] = temp; }
void Perm(char str[], int begin, int end) { if (begin == end) { for (int i = 0; i <= end; i++) { cout << str[i]; } cout << endl; sum++; return; } else { for (int j = begin; j <= end; j++) { Swap(str, begin, j); Perm(str, begin + 1, end); Swap(str, j, begin); } } }
int main() { int n; char c[16]; char tmp;
cin >> n; tmp = getchar(); // 接受回车 if (1 <= n && n <= 15) { for (int i = 0; i < n; i++) { c[i] = getchar(); } Perm(c, 0, n - 1); } cout << sum; cout << endl; return 0; }
实现后效果如下图:
有重复元素的排列问题
然后现在的题目要求是排列中的元素是包含相同元素的,给定n以及待排的n个可能重复的元素。计算输出n个元素的所有不同排列,因此上面那个算法显然还是不够好,因为相同的元素都当成不同的元素,因此有了重复的排列在里面
去掉重复符号的全排列:在交换之前可以先判断两个符号是否相同,不相同才交换,这个时候需要一个判断符号是否相同的函数。也就是下面的IsSwap();
对122,第一个数1与第二个数2交换得到212,然后考虑第一个数1与第三个数2交换,此时由于第三个数等于第二个数,所以第一个数不再与第三个数交换。再考虑212,它的第二个数与第三个数交换可以得到解决221。
去掉重复的规则:去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换。
#include <iostream> using namespace std;int sum=0;//记录有多少种组合
void Swap(char str[], int a, int b) { char temp = str[a]; str[a] = str[b]; str[b] = temp; }
bool IsSwap(char *pchar, int nBegin, int nEnd) { for (int i = nBegin; i < nEnd; i++) if (pchar[i] == pchar[nEnd]) return false; return true; }
void Perm(char str[], int begin, int end) { if (begin==end) { for (int i = 0; i <= end; i++) { cout << str[i]; } cout << endl; sum++; return; } else { for (int j = begin; j <= end; j++) { if (IsSwap(str, begin, j)) { Swap(str, begin, j); Perm(str, begin + 1, end); Swap(str, j, begin); } } } }
int main() { int n; char c[16]; char tmp;
cin >> n; tmp = getchar(); // 接受回车 if (1 <= n && n <= 15) { for (int i = 0; i < n; i++) { c[i] = getchar(); } Perm(c, 0, n - 1); } cout << sum; cout << endl; return 0; }
非递归的实现
实现思路:
要考虑全排列的非递归实现,先来考虑如何计算字符串的下一个排列。如"1234"的下一个排列就是"1243"。只要对字符串反复求出下一个排列,全排列的也就迎刃而解了。
如何计算字符串的下一个排列了?来考虑"926520"这个字符串,我们从后向前找第一双相邻的递增数字,"20"、"52"都是非递增的,"26 "即满足要求,称前一个数字2为替换数,替换数的下标称为替换点,再从后面找一个比替换数大的最小数(这个数必然存在),0、2都不行,5可以,将5和2交换得到"956220",然后再将替换点后的字符串"6220"颠倒即得到"950226"。
对于像"4321"这种已经是最“大”的排列,采用STL中的处理方法,将字符串整个颠倒得到最“小”的排列"1234"并返回false。
//全排列的非递归实现 #include <iostream> using namespace std; 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() { int sum = 0; char szTextStr[16]; cin >> szTextStr; char tmp = getchar(); // 接受回车 //加上排序 qsort(szTextStr, strlen(szTextStr), sizeof(szTextStr[0]), QsortCmp); int i = 1; cout << endl << endl << endl; do{ cout<<szTextStr<<endl; sum++; } while (Next_permutation(szTextStr));cout << sum<<endl; return 0; }
总结:
排列在笔试面试中很热门,在百度和迅雷的校园招聘以及程序员和软件设计师的考试中都考到了,因此了解全排列算法对我们都很有好处。也是算法的一个基本思想。递归算法的思路比较直,而非递归的就比较难去想到使用这种方法来实现。
1.全排列就是从第一个数字起每个数分别与它后面的数字交换。
2.去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换。
3.全排列的非递归就是由后向前找替换数和替换点,然后由后向前找第一个比替换数大的数与替换数交换,最后颠倒替换点后的所有数据。
本文向大家介绍C#递归算法之归并排序,包括了C#递归算法之归并排序的使用技巧和注意事项,需要的朋友参考一下 归并排序是利用递归和分而治之的技术将数据序列划分成为越来越小的半子表,再对半子表排序,最后再用递归步骤将排好序的半子表合并成为越来越大的有序序列,归并排序包括两个步骤,分别为: 1)划分子表 2)合并半子表 首先我们来讨论归并算法,归并算法将一系列数据放到一个向量中,索引范围为[first,
本文向大家介绍C#递归算法之快速排序,包括了C#递归算法之快速排序的使用技巧和注意事项,需要的朋友参考一下 上两片第归算法学习: 1)递归算法之分而治之策略 2)递归算法之归并排序 上一篇学习中介绍了了递归算法在排序中的一个应用:归并排序,在排序算法中还有一种算法用到了递归,那就是快速排序,快速排序也是一种利用了分而治之策略的算法,它由C.A.R发明,它依据中心元素的值,利用一系列递归调用将数
本文向大家介绍C#排序算法之归并排序,包括了C#排序算法之归并排序的使用技巧和注意事项,需要的朋友参考一下 本文实例为大家分享了C#实现归并排序具体代码,供大家参考,具体内容如下 代码: 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持呐喊教程。
本文向大家介绍Java算法之递归算法计算阶乘,包括了Java算法之递归算法计算阶乘的使用技巧和注意事项,需要的朋友参考一下 本文为大家分享的java算法计算阶乘,在学习Java课程时经常会遇到求阶乘问题,今天接跟大家一起探讨一下 代码如下: 运行结果:
本文向大家介绍java递归算法的实例详解,包括了java递归算法的实例详解的使用技巧和注意事项,需要的朋友参考一下 递归三要素: 1、明确递归终止条件; 2、给出递归终止时的处理办法; 3、提取重复的逻辑,缩小问题规模。 1、1+2+3+…+n 2、1 * 2 * 3 * … * n 3、斐波那契数列 前两项均为1,第三项开始,每一项都等于前两项之和。即:1,1,2,3,5,8,… 4、二叉树的遍
主要内容:递归的底层实现机制编程语言中,我们习惯将函数(方法)调用自身的过程称为 递归,调用自身的函数称为 递归函数,用递归方式解决问题的算法称为 递归算法。 函数(方法)调用自身的实现方式有 2 种,分别是: 1) 直接调用自身,例如: 2) 间接调用自身,例如: 程序中,function1() 函数内部调用了 function2() 函数,而 function2() 函数内部又调用了 function1() 函数。也就是