问题定义
给定一个集合{a1, a2, ..., an}, 要求输出集合中元素的所有排列。
例如: 集合{1,3}的全排列有: {1,3 }, {3,1}
解决方案
对于全排列问题,已经存在很多递归和非递归的算法来解决这个问题,作为学习笔记,我这里只列举两种比较经典且易懂的算法。有兴趣的同学可以参考以下链接
方法(1)基于交换的递归方法: 求n个元素的全排列可以先从n个元素中选一个作为首元素,然后排列剩下的元素的全排列。注意 一个元素的全排列是其本身。
例: Perm({a, b, c} = {a}.Perm({b, c}) + {b}.Perm({a, c}) + {c}.Perm({a, b})
/**
*@brief: 元素全排列-基于交换的递归实现
*
*@idea: 求n个元素的全排列可以选从n个中选一个作为首元素,然后排列剩下的元素的全排列。
* 一个元素的全排列是其本身。
*
*@note: 原算法没有考虑到元素重复会导致生成的排列重复的情况。
* 改进的方法是在每一论选取一个元素作为首元素时,先判断下这个元素是否已经选取过。
*
**/
#include<iostream>
using namespace std;
/
/**
*@brief: Swap two elements
*/
template<class T>
void Swap(T &a, T &b)
{
T temp(a);
a = b;
b = temp;
}
/**
*@brief: print all premutation of the given array
*@param A: array
*@param size: the size of the array
*@param n: the start index of the sub array to permutate
*/
template<class T>
void Permutation(T A[], int size, int n)
{
if (n == size - 1)
{
for (int i=0; i<size; ++i)
{
cout<<A[i]<<" ";
}
cout<<endl;
}
for (int i=n; i<size; ++i)
{
//avoid repeated permutations
int k;
for (k=n; k<i; ++k)
{
if (A[k] == A[i]) break;
}
if (k < i) continue;
Swap(A[n], A[i]);
Permutation(A, size, n+1);
Swap(A[n], A[i]);
}
}
//
int main()
{
int Arr[] = {1, 3, 3, 4, 5};
Permutation(Arr, 5, 0);
return 0;
}
值得注意的是, 原算法思想没有考虑到元素重复会导致生成的排列重复的情况。 改进的方法是在每一轮选取元素作为首元素时,先判断下这个元素是否已经选取过, 具体见代码。
方法(2)基于字典序法的非递归实现: 给定一个初始排列,通过字典序的转换规则不断得到它的下一个排列。 如果初始排列是从小到大的有序排列,那么最后能得到全排列。
得到下一个排列的方法:
*step 1: 从右到左扫描集合,找到第一个小于其右边元素的元素的下标i
*step 2: 从右往左扫描元素, 找到第一个大于A[i]的元素的下标j
*step 3: 交换 A[i] A[j]
*step4 : 反转A[i+1 ~ n]
/**
*@brief: 元素全排列-基于字典序法的非递归实现
*
*@idea: 给定一个初始排列,通过字典序的转换规则不断得到它的下一个排列。
* 如果初始排列是从小到大的有序排列,那么最后能得到全排列。
*@note:
(1)这种方法不会出现重复的排列,即使给定元素集合中有重复元素
(2)要得到所有排列, 元素必须先从小到大排序一遍
*@complexity: O(n*n!)
**/
#include<iostream>
#include<algorithm>
using namespace std;
//
/**
*@brief: swap two elements
**/
template<class T>
void Swap(T &a, T &b)
{
T temp(a);
a = b;
b = temp;
}
/**
*@brief: get next permutation
*step 1: 从右到左扫描集合,找到第一个小于其右边元素的元素的下标i
*step 2: 从右往左扫描元素, 找到第一个大于A[i]的元素的下标j
*step 3: 交换 A[i] A[j]
*step4 : 反转A[i+1 ~ n]
*@return bool: indicate whether has next permutation
*/
template<class T>
bool Next_perm(T A[], int size)
{
int i, j;
//step 1
for (i=size-2; i>=0; --i)
{
if (A[i] < A[i+1]) break;
}
//if is the last permuation
if (i < 0) return false;
//step 2
for (j = size-1; j>=0; --j)
{
if (A[j] > A[i])
{
//step3
Swap(A[i], A[j]);
break;
}
}
//step 4
while (++i < --size)
{
Swap(A[i], A[size]);
}
return true;
}
/**
*@brief: print all permutations of the given array
*/
template<class T>
void Permutation(T A[], int size)
{
do
{
//print the current permutation
for (int i=0; i<size; ++i)
{
cout<<A[i]<<" ";
}
cout<<endl;
}while (Next_perm(A, size));
}
///
int main()
{
int Arr[] = {3, 1, 3, 4, 5};
sort(Arr, Arr+5);
Permutation(Arr, 5);
return 0;
}
应用这个非递归算法要注意以下两点:
(1)这种方法不会出现重复的排列,即使给定元素集合中有重复元素
(2)要得到所有排列, 元素必须先从小到大排序一遍
*****作为我在CSDN上的开篇博客,在这里留个纪念。希望各位大神多多指教,写得不对之处,在希望各位秉承善意地指出,谢谢!*************