求包含重复元素的全排列

liangbch 2008-11-14 11:17:37
我们知道,n个不同的元素共有你n!中排列,但对于包含相同的元素,则排列数则没有则这么多,如n个元素,扣除重复的元素后有m个,则排列数>=m! 而小于等于n!
例如,包含3个元素的数组{2,2,3} 有3种排列
{2,2,3}
{2,3,2}
{3,2,2}

现在我的问题,给定一个数组,数组中的元素以非递减排列,输出这个数组的所有排列方式。

今天早上,编了一个程序,实现了这个功能,但空间复杂度不太理想,不知各位高手能否给出更好的算法或者改进我的算法。

#include <stdlib.h>
#include <stdio.h>
#include <memory.h>


//生成有重复元素的全排列
//作者:liangbch@263.net, 2008-11-14

//#define _MY_DEBUG

#ifdef _MY_DEBUG
#define MAX_LEN 8
#else
#define MAX_LEN 256 //最多可以生成256个数的全排列
#endif

typedef struct _pair
{
int v; //value
int c; //count
}PAIR;

void printPermutation( int data[],int len)
// len: data 数组的长度
{
int i;
for (i=0;i<len;i++)
{
if (i==0)
printf("%d",data[i]);
else
printf(",%d",data[i]);
}
printf("\n");
}

void permutation( int newData[],
PAIR arrCount[],
int len,int level)
// len:countArray 数组元素的个数
// level: newData 数组已被填充的元素的个数
{
int i,j;
bool bFind=false;
PAIR tArray[MAX_LEN];

for (i=0;i<len;i++)
{
for (j=0;j<len;j++) //复制数组从arrCount到tArray
tArray[j]=arrCount[j];

if ( tArray[i].c>0)
{
bFind=true;
newData[level]= tArray[i].v;
tArray[i].c--;
#ifdef _MY_DEBUG
printf("#tArray[%d].c=%d\n",i,tArray[i].c);
printf("#new item= data[%d]=%d\n",level,tArray[i].v);
#endif
permutation( newData,tArray,len,level+1);
}
}
if (!bFind)
{
printPermutation(newData,level);
}
}

//输出data各个元素的一个全排列
//数组data中的各个元素必须以非递减排序,数组中元素可以重复
//假如有n个元素,则这个算法的空间复杂度为n*n,递归深度为n
void printAllPermutation(int data[],int n)
{
PAIR countArray1[MAX_LEN];
int newData[MAX_LEN];
int i,j;

//根据data初始化countArray1
countArray1[0].v=data[0];
countArray1[0].c=1;
for (j=0,i=1;i<n;i++)
{
if ( data[i]== countArray1[j].v)
{
countArray1[j].c++;
}
else
{
j++;
countArray1[j].v=data[i];
countArray1[j].c=1;
}
}
permutation( newData,countArray1,j+1,0);
}

int main(int argc, char* argv[])
{
//int a[]={2,3,5};
int a[]={2,2,3,5};
//int a[]={2,2,3,3,5};
printAllPermutation(a,sizeof(a)/sizeof(int));
return 0;
}
...全文
501 点赞 收藏 18
写回复
18 条回复
切换为时间正序
请发表友善的回复…
发表回复
liangbch 2008-12-09
各位网友共提供了7份代码。整理后(统一编程接口,去掉C++的iostream)的代码如下:

#include <stdio.h> 
inline void Swap(int& a, int& b)
{
// 交换a和b
int temp = a;
a = b;
b = temp;
}

//该程序有csdn dobear_0922 提供
//生成list [k:m ]的所有排列方式
void Perm(int list[], int k, int m)
{
int i;

if (k == m) //输出一个排列方式
{
for (i = 0; i <= m; i++)
{
if (i==0)
printf("%d",list[i]);
else
printf(",%d",list[i]);
}
printf("\n");
}
else // list[k:m ]有多个排列方式
{
// 递归地产生这些排列方式
for (i=k; i <= m; i++)
{
Swap (list[k], list[i]);
Perm (list, k+1, m);
Swap (list [k], list [i]);
}
}
}

void dobear_0922() //不能将数组
{
int a[]={2,2,3,5};

Perm(a, 0, 2); // 没有去掉 重复 元素
}


#define N 10

bool is_used[N];
int result[N];


void init(int n)
{
for (int i = 0; i < n; i++)
{
is_used[i] = false;
}
}

int test(int input[], int n, int k)
{
int ret = 0;
if (k == n)
{
for (int i = 0; i < n; i++)
{
printf("%d ",result[i]);
}
printf("\n");
return 1;
}
for (int i = 0; i < n; i++)
{
if (is_used[i] == false)
{
if (i == 0)
{
is_used[i] = true;
result[k] = input[i];
ret += test(input,n,k + 1);
is_used[i] = false;
}
else
{
if (input[i - 1] != input[i])
{
is_used[i] = true;
result[k] = input[i];
ret += test(input,n,k + 1);
is_used[i] = false;
}

if (input[i - 1] == input[i] && is_used[i - 1] == true)
{
is_used[i] = true;
result[k] = input[i];
ret += test(input,n,k + 1);
is_used[i] = false;
}
}
}
}
return ret;
}


int currenttt()
{
int input[] = {2, 2, 3} ;
int n=sizeof(input)/sizeof(int);
int num;

init(n);

num= test(input,n,0);

printf("\ntotal %d permutation\n",num);

return 0;
}


/*
顾名思义,这种方法的思想就是将所有n元排列按"字典顺序"排成队,以12…n为第一个排列,排序的规则,也就是由一个排列(p)=(p1p2…pn)直接生成下一个排列的算法可归结为
(1) 求满足关系式p[k-1]<p[k] 的k的最大值,设为i,

(2) 求满足关系式p[i-1]<p[k] 的k的最大值,设为j,

(3) p[i-1] 与p[j] 互换位置得
(q)=(q1 q2 … qn);
(4) (q)=(q1 q2 … qi-1 qi qi+1 … qn)中qi qi+1 … qn部分的顺序逆转,得
q1 q2 … qi-1 qn…qi+1 qi
这便是所求的下一个排列。
*/


void print_permutation(int *a,int n)
{
int i;
for (i=0;i<n ;i++ ) printf("%3d ",a[i]);
printf("\n");
}

void swap(int *a,int i,int j)
{
int t=a[i];
a[i]=a[j];
a[j]=t;
}

void permutation(int *a,int n)
{
int i,j,k;

print_permutation(a,n);

for (i=n-1;i>0 && a[i]<=a[i-1];i-- ) ;
if (i==0)
return;

for (j=n-1;j>0 && a[j]<=a[i-1];j-- ) ;
swap(a,i-1,j);

for (j=i,k=n-1;j<k;j++,k--)
swap(a,j,k);

permutation(a,n);
}


int tailzhou()
{
int num[]={2,2,3};
permutation(num,sizeof(num)/sizeof(int));
return 0;
}


//provide by tailzhou 2
void permutation2(int *a,int n)
{
int i,j,k;

while(1)
{
print_permutation(a,n);

for (i=n-1;i>0 && a[i]<=a[i-1];i-- ) ;
if (i==0)
return;

for (j=n-1;j>0 && a[j]<=a[i-1];j-- ) ;
swap(a,i-1,j);

for (j=i,k=n-1;j<k;j++,k--)
swap(a,j,k);
}
}

int tailzhou2()
{
int num[]={2,2,3};
permutation2(num,sizeof(num)/sizeof(int));
return 0;
}



//这是我写的代码//给出一个字符串的所有排列,重复项过滤掉

int flags[256];
//char max,min;

//destination, 排序后的字符串
//insrPos,字母插入位置
//len, 待排序字符串总长度
void allRank(int* destination,int insrPos,char max, char min,int len)
{
// static int count;
int i = 0;
//recursion finished
if(insrPos == len)
{
//printf("%d:%s,len = %d, insrPos = %d\n",++count,destination, len, insrPos);
//printf("%s\n",destination);
for (i=0;i<len;i++)
printf("%d ",destination[i]);
printf("\n");
return ;
}
i = min;
while(i <= max)
{
//字母exists
if( flags[i])
{
destination[insrPos] = i;
flags[i] -= 1;
allRank(destination,insrPos+1,max,min,len);
flags[i] += 1;
}
i++;
}
}

void allArray(int* source, int len)
{
int i;
int max, min;
int p[101] = {0};

for(i = 0; i < 256; i++)
{
flags[i] = 0;
}

max = min = flags[0];

for(i = 0; i < len; i++)
{

flags[source[i]] +=1;
if(max < source[i])
max = source[i];
else if(min > source[i])
min = source[i];

}

allRank(p,0,max,min,len);
}


int TADICAN()
{
int num[]={2,2,3};
allArray(num,sizeof(num)/sizeof(int));
return 0;
}


inline int Swap2(int& a, int& b)
{// 交换a和b
// 如果 a、b 不是同一个成员,但是值相等,就不交换
if ( (&a != &b) && (a == b)) // C 语法都快忘了,不知道对不对
return 0;

int temp = a;
a = b;
b = temp;

return 1;
}

void Perm2(int list[], int k, int m)
{ //生成list [k:m ]的所有排列方式
int i;
if (k == m) {//输出一个排列方式
for (i = 0; i <= m; i++)
printf("%d ",list[i]);
putchar('\n');
}
else // list[k:m ]有多个排列方式
// 递归地产生这些排列方式
for (i=k; i <= m; i++) {
if (Swap2(list[k], list[i])) {
Perm (list, k+1, m);
Swap2 (list [k], list [i]);
}
}
}

int tiger_zhao() //可重复,但正确
{
int s[]={2,2,3};
Perm2(s, 0, 2);
return 0;
}



//全排列吗?递归实现。没有考虑非递减。
void swap3(int& a, int& b)
{
int temp;
temp=a;
a=b;
b=temp;
}

void permStr(int str[],int i,int len)
{
int j;
if( i==len-1)
{
int j;
for (j=0;j<len;j++)
{
printf("%d ",str[j]);
}
printf("\n");
}
else
{
for( j=i;j < len;j++)
{
swap3(str[i],str[j]);
permStr(str,i+1,len);
swap3(str[i],str[j]);
}
}
}

void hairetz()
{
int str[]={2,2,3};
permStr(str,0,sizeof(str)/sizeof(int));
}

int main(int argc, char* argv[])
{
//dobear_0922(); //没有去掉重复元素
//currenttt(); //正确,代码较复杂,使用到了全局变量
//tailzhou(); //正确,递归归本,代码较简单,不需辅助空间
//tailzhou2(); //正确,非递归归本,代码较简单,不需辅助空间
//TADICAN(); //正确,代码较复杂,使用到了全局变量
//tiger_zhao(); //错误,有重复的排列
hairetz(); //错误,有重复的元素
return 0;
}

回复
全排列吗?递归实现。没有考虑非递减。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>

void swap(char *str1,char *str2)
{
char temp;
temp=*str1;
*str1=*str2;
*str2=temp;
}

void permStr(char *str,int i)
{
//printf("%d",i);
if(i==strlen(str)-1)
printf("%s\n",str);
else
{
for(int j=i;j<strlen(str);j++)
{
//printf("i %d,j %d",i,j);
swap(&str[i],&str[j]);
permStr(str,i+1);
swap(&str[i],&str[j]);
}
}
}

void main()
{
char str[]={"abcd"};
permStr(str,0);

}
回复
pigqqren 2008-11-19
eeeeeeeeeeee
回复
Tiger_Zhao 2008-11-19
3楼的代码很容易懂,改成可重复的(不需要有序)
#include <stdio.h> 
inline boolean Swap(char& a, char& b)
{// 交换a和b
// 如果 a、b 不是同一个成员,但是值相等,就不交换
if ((&a <> &b) && (a == b)) // C 语法都快忘了,不知道对不对
return false;

char temp = a;
a = b;
b = temp;

return true;
}

void Perm(char list[], int k, int m)
{ //生成list [k:m ]的所有排列方式
int i;
if (k == m) {//输出一个排列方式
for (i = 0; i <= m; i++)
putchar(list[i]);
putchar('\n');
}
else // list[k:m ]有多个排列方式
// 递归地产生这些排列方式
for (i=k; i <= m; i++) {
if (Swap(list[k], list[i])) {
Perm (list, k+1, m);
Swap (list [k], list [i]);
}
}
}

int main()
{
char s[]="121";
Perm(s, 0, 2);
return 0;
}
回复
liangbch 2008-11-19
这两天很忙,到下周有时间仔细看下结贴。
回复
绿色夹克衫 2008-11-18
有比较详细讲解的字典序法!lz自己看看吧!

http://hi.baidu.com/flycjh1122/blog/item/7006c9111316597aca80c49b.html
回复
WizardOz 2008-11-18
声援一下,揭帖时别忘给点辛苦分。
回复
TADICAN 2008-11-18
这是我写的代码//给出一个字符串的所有排列,重复项过滤掉
#include "StdAfx.h"
#include <cstdlib>
#include <iostream>
using namespace std;


int len;
int flags[256];
//char max,min;

//destination, 排序后的字符串
//insrPos,字母插入位置
//len, 待排序字符串总长度
void allRank(char* destination,int insrPos,char max, char min)
{
// static int count;
int i = 0;
//recursion finished
if(insrPos == len)
{
//printf("%d:%s,len = %d, insrPos = %d\n",++count,destination, len, insrPos);
//printf("%s\n",destination);
puts(destination);
return ;
}
i = min;
while(i <= max)
{
//字母exists
if( flags[i])
{
destination[insrPos] = i;
flags[i] -= 1;
allRank(destination,insrPos+1,max,min);
flags[i] += 1;
}
i++;
}
}
void allArray(char* source)
{
int i;
char max, min;
//char *p = (char*) malloc(sizeof(char)*(len + 1));
char p[101] = {0};
len = strlen(source);
for(i = 0; i < 256; i++)
{
flags[i] = 0;
}
max = min = flags[0];
//record
for(i = 0; i < len; i++)
{

flags[source[i]] +=1;
if(max < source[i])
max = source[i];
else if(min > source[i])
min = source[i];

}

p[len] = '\0';
allRank(p,0,max,min);

//free(p);
}
int main(int argc, char *argv[])
{
char c[101];
//while(EOF!=scanf("%s",c))
while(cin >> c)
{
allArray(c);
//getchar();
}
return 0;
}

下面有一个用STL算法写的,比我的快。
#include <iostream>
#include <algorithm>
using namespace std;
char s[110];
int main()
{
while( scanf("%s",s)!=EOF )
{
int len = strlen(s);
sort(s,s+len);
do
{
printf("%s\n",s);
}
while(next_permutation(s,s+len));
}
return 0;
}

以上代码都是经过自动化程序测试的。我写的代码通过测试用了20s,第二个程序用了0s(标准库就是强!)
回复
medie2005 2008-11-14
字典序法。
SGI STL已经实现为next_permutation()函数。
可以直接看源码。
想深入了解,看taocp卷四。
回复
liangbch 2008-11-14
这么多人给出答案,谢谢大家了,有空看看。今天加分未果,明天将分数加到200.
回复
tailzhou 2008-11-14
非递归版本:

void permutation(int *a,int n)
{
int i,j,k,t;

while(1)
{
print_permutation(a,n);

for (i=n-1;i>0 && a[i]<=a[i-1];i-- ) ;
if (i==0) return;

for (j=n-1;j>0 && a[j]<=a[i-1];j-- ) ;
swap(a,i-1,j);

for (j=i,k=n-1;j<k;j++,k--) swap(a,j,k);
}
}

回复
currenttt 2008-11-14
辅助空间有一个存放最终结果的int result[]:O(N)
还有一个记录每个数字是否被使用过的bool is_used[]:O(N),用递归解决,只是在原先没有重复元素的全排列基础上稍微改动了一下,改动的结果是,如果判断当前正在形成的一个排列在之前已经出现过,则停止当前排列。

递归中的判断就是在去除排列
回复
wiowei 2008-11-14
数学上来说:
n个数中有k个数字,每个数字重复次数分别为a_1,a_2,...,a_k,这时的排列数为n!/(a_1!*a_2!*...*a_k!)

迭代的方法得到所有排列数:
0 如果n个数除去重复数所剩的数字数目为m,则这剩下的m个数能插入的空位为m+1
1 在m+1个空位插入第一个重复数(a_1个)就得到包含第一个数字的全排列,此后得到的空位为m+a_1+1个
2 在m+1+a_1个空位插入第二个重复数(a_2个)得到包含第一、二的数字的全排列,此后得到的空位为m+a_1+a_2+1个
...
k 再m+a_1+a_2+...+a_(k-1)个空位插入第k个重复数(a_k个)得到包含所有重复数的全排列,及n个数的全排列

这里每个“空位”均能插入多个重复数字,即如果i个空位插入j个相同数字,则插入可能性有Ci(1)*Cj-1(0)+Ci(2)*Cj-1(1)+...+Ci(j)*Cj-1(j-1)个(C为组合数)。

先mark,等大家的讨论,算法容后再想
回复
tailzhou 2008-11-14
临时写了个;


/*
顾名思义,这种方法的思想就是将所有n元排列按“字典顺序”排成队,以12…n为第一个排列,排序的规则,也就是由一个排列(p)=(p1p2…pn)直接生成下一个排列的算法可归结为
(1) 求满足关系式p[k-1]<p[k] 的k的最大值,设为i,

(2) 求满足关系式p[i-1]<p[k] 的k的最大值,设为j,

(3) p[i-1] 与p[j] 互换位置得
(q)=(q1 q2 … qn);
(4) (q)=(q1 q2 … qi-1 qi qi+1 … qn)中qi qi+1 … qn部分的顺序逆转,得
q1 q2 … qi-1 qn…qi+1 qi
这便是所求的下一个排列。
*/

#include <stdio.h>

void print_permutation(int *a,int n)
{
int i;
for (i=0;i<n ;i++ ) printf("%3d ",a[i]);
printf("\n");
}

void swap(int *a,int i,int j)
{
int t=a[i];
a[i]=a[j];
a[j]=t;
}

void permutation(int *a,int n)
{
int i,j,k,t;

print_permutation(a,n);

for (i=n-1;i>0 && a[i]<=a[i-1];i-- ) ;
if (i==0) return;

for (j=n-1;j>0 && a[j]<=a[i-1];j-- ) ;
swap(a,i-1,j);

for (j=i,k=n-1;j<k;j++,k--) swap(a,j,k);

permutation(a,n);
}

int main()
{
int num[]={2,3,3,4,4};
permutation(num,sizeof(num)/sizeof(int));
return 0;
}

回复
currenttt 2008-11-14
之前ms有人问过类似的问题,这个是我当时写的代码。

测试数据一:
n = 3
input[] = {2, 2, 3}

测试数据二:
n = 5
input[] = {2, 2, 3, 3, 4}



#include <iostream>
using namespace std;

#define N 10

int input[N];
bool is_used[N];
int result[N];
int n;

int cmp(const void *a1, const void *a2)
{
int t1 = *(int*)a1;
int t2 = *(int*)a2;
return t1 - t2;
}

void init(int n)
{
for (int i = 0; i < n; i++)
{
is_used[i] = false;
}
}

int test(int k)
{
int ret = 0;
if (k == n)
{
for (int i = 0; i < n; i++)
{
cout << result[i] << ' ';
}
cout << endl;
return 1;
}
for (int i = 0; i < n; i++)
{
if (is_used[i] == false)
{
if (i == 0)
{
is_used[i] = true;
result[k] = input[i];
ret += test(k + 1);
is_used[i] = false;
}
else
{
if (input[i - 1] != input[i])
{
is_used[i] = true;
result[k] = input[i];
ret += test(k + 1);
is_used[i] = false;
}

if (input[i - 1] == input[i] && is_used[i - 1] == true)
{
is_used[i] = true;
result[k] = input[i];
ret += test(k + 1);
is_used[i] = false;
}
}
}
}
return ret;
}

int main()
{

cin >> n;
init(n);

for (int i = 0; i < n; i++)
{
cin >> input[i];
}
cout << endl;

qsort(input, n, sizeof(int), cmp);

int num = test(0);

cout << num << endl;

system("pause");
return 0;
}
回复
dobear_0922 2008-11-14
贴一个不重复的:
#include <stdio.h> 
inline void Swap(char& a, char& b)
{// 交换a和b
char temp = a;
a = b;
b = temp;
}

void Perm(char list[], int k, int m)
{ //生成list [k:m ]的所有排列方式
int i;
if (k == m) {//输出一个排列方式
for (i = 0; i <= m; i++)
putchar(list[i]);
putchar('\n');
}
else // list[k:m ]有多个排列方式
// 递归地产生这些排列方式
for (i=k; i <= m; i++) {
Swap (list[k], list[i]);
Perm (list, k+1, m);
Swap (list [k], list [i]);
}
}

int main()
{
char s[]="123";
Perm(s, 0, 2);
return 0;
}
回复
dobear_0922 2008-11-14
看看,关注
回复
tailzhou 2008-11-14
普通的“字典序法”不行么?
回复
发动态
发帖子
数据结构与算法
创建于2007-08-27

3.2w+

社区成员

数据结构与算法相关内容讨论专区
申请成为版主
社区公告
暂无公告