/*
* szlFullPermutation.h
*/
#ifndef SZL_FULLPERMUTATION_H
#define SZL_FULLPERMUTATION_H
typedef char* STR;
typedef char** PSTR;
void fullPermutation(PSTR, STR );
void permutation(PSTR, STR, unsigned int);
#endif
/*
* szlTestFullPermutation.h
*/
#ifndef SZL_TEST_FULLPERMUTATION_H
#define SZL_TEST_FULLPERMUTATION_H
void testFullPermutation(char* );
#endif
/*
* szlTestFullPermutation.c
*/
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "szlFactorial.h"
#include "szlWidthOfInteger.h"
#include "szlFullPermutation.h"
#include "szlTestFullPermutation.h"
void testFullPermutation(char* strPattern){
int i;
int nPattern;
char **strDes;
int nDes;
int nPrintWidth;
nPattern = strlen( strPattern );
nDes = factorial( nPattern );
//memory allocation
strDes = (char **)malloc(sizeof(char*) * nDes);
assert(strDes);
for(i=0; i<nDes; i++){
strDes[i] = (char *)malloc(sizeof(char) * (1+nPattern));
assert(strDes[i]);
strDes[i][0] = '\0';
}
//排列
fullPermutation(strDes,strPattern);
//print the result
nPrintWidth = widthOfInteger(factorial(nPattern)); //szlWidthOfInteger.h, szlFactorial.h
for(i=0; i<nDes; i++){
fprintf(stdout, "%0*d %s\n",nPrintWidth, i+1, strDes[i]);
}
//free memory
for(i=0; i<nDes; i++){
if(strDes[i]){
free(strDes[i]);
}
}
if(strDes){
free(strDes);
}
}
/*
* szlFullPermutation.c
*/
#include "szlFullPermutation.h"
#include "szlFactorial.h"
#include "szlWidthOfInteger.h"
#include "szlGetNumOfOneInInt.h"
#include "szlPower.h"
#include <string.h>
void fullPermutation(PSTR, STR );
void permutation(PSTR, STR, unsigned int);
void fullPermutation(PSTR pstrDes, STR strSrc){
// 定义一个整数,相同索引位置上的值如果为1则表示在strSrc中对等位置的字符
// 已经被挖出; 字符串长度应该不会超出32位;因此1个整数应该就可以标记了。
unsigned int bMark;
//初始时没有被处理,所以只为0
bMark = 0;
//bMark用于表示strSrc的处理深度:
//bMark的二进制表示中,从左边起的第X位为1就表示字符数组strSrc的第X位已经
//被处理过。
//默认是从0开始索引的,假设。
permutation(pstrDes,strSrc,bMark);
}
void permutation(PSTR pstrDes, STR strSrc, unsigned int bMark){
int i,j,k; //循环变量
int nSrc; //源字符串的长度
PSTR pstrSec; //指向目标地址的相应位置,用于递归
int nSec; //分割的间距,目标地址为分为nSrc段,每段的大小是nSec = (nSrc-1)!
int nAddressed; //bMark中1的个数,也就是被使用的字符个数
unsigned int nMark;
unsigned int nSet;
nSrc = strlen(strSrc); //输入的字符串包含的字符个数
nAddressed = getNumOfOneInInt(bMark); //szlGetNumOfOneInInt.h
nSec = factorial(nSrc - nAddressed - 1);//pstrSec用于指向待处理的目标地址
//出口:传入只包含一个字符的字符串时结束
//两种情形:
//(1)输入恰好只有1个字符;
//(2)输入超过1个字符,但是只剩下1个字符未被处理;
if(2 > nSrc - nAddressed){
//只包含一个字符了,nAddressed中第一个为0值的低位位置
if(1 == nSrc){
pstrDes[0][0] = strSrc[0];
pstrDes[0][1] = '\0';
}
else{ //nAddressed!=0, 找到唯一的0的索引,0的总数不超过nSrc.
int nIndexOfZero = 0;
nMark = bMark;
while(0 != nMark){ //nAddressed!=0, so initially 0!=temp
if(!(nMark&1)){
break;
}
else{
nMark = (nMark >> 1);
nIndexOfZero++;
}
}
//将该位置的字符取出赋值
pstrDes[0][nSrc-1] = strSrc[nIndexOfZero];
pstrDes[0][nSrc] = '\0';
}
//程序结束
return;
}
// 循环:每次取出一个字符;
// 填入目标地址的适当的位置中;
nMark = bMark;
for(i=0,k=0;i<nSrc;i++){ // default: nSrc < sizeof(unsigned int)
if(!(1&nMark)){
nSet = 1;
nSet = (nSet << i);//将第i位置为1,最左边为第0位
pstrSec = pstrDes + k * nSec;//待处理的地址段
for(j=0;j<nSec;j++){
pstrSec[j][nAddressed] = strSrc[i];
}
k++;
//递归
permutation(pstrSec,strSrc,(nSet|bMark));
}
nMark = (nMark >>1 );
}
}
/*
* main.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "szlTestFullPermutation.h"
#include "szlTestSort.h"
int main(){
char str[10];
printf("str=\n");
scanf("%s",str);
testFullPermutation(str);
return 0;
}
偶然从http://blog.csdn.net/hqw19881118/article/details/9103903看到一个递归版本的,非常简洁,写成了C语言版本。
/* permutation.c */
#include <assert.h>
#include <stdio.h>
void swap_char (char * p, char * q){ /* swap two char */
if (p!=q){
*p = *p ^ *q;
*q = *p ^ *q;
*p = *p ^ *q;
}
}
void permutation(char* src, char* begin){ /* print permutation for the string src */
assert (NULL!=src && NULL!=begin);
if(*begin == '\0'){
printf ("%s\n", src);
}
else{
char * p;
for(p = begin; *p != '\0'; p++){ /* loop for strlen(src) times */
swap_char (p, begin); /* swap *p and *begin */
permutation(src, begin+1); /* recursion from next character */
swap_char (p, begin); /* restore: swap *p and *begin once more */
}
}
}
int main(int argc, char* argv[]){
char src[] = "abcd";
permutation(src, src);
return 0;
}
对上述的一个改进版本:升序打印。打印之前需要升序排列。
/* permutation.c : a sorted one (in ascendeng order) */
#include <assert.h>
#include <stdio.h>
#include <stdlib.h> /* qsort */
/* swap two char */
#define swap_char(r, q)\
if (r!=q){ \
*r = *r ^ *q; \
*q = *r ^ *q; \
*r = *r ^ *q; \
}
/* adjust sequence src so that the elements in it is in order:
* p is the only positon that may breaks the order */
void sort_char (char * src, char * p);
/* print permutation for the string src */
void permutation (char * src, char * begin);
/* for qsort */
int ch_cmp (const void * a, const void * b);
int main(int argc, char* argv[]){
char src[] = "1324";
qsort (src, 4, sizeof (char), ch_cmp);
permutation(src, src);
return 0;
}
/* print permutation for the string src */
void permutation (char * src, char * begin){
assert (NULL != src && NULL != begin);
if(* begin == '\0'){
printf ("%s\n", src);
}
else{
char * p;
for(p = begin; *p != '\0'; p++){ /* loop for strlen(src) times */
swap_char (p, begin); /* swap *p and *begin */
/* put *p into a position from the (begin+1) position so that the sequence
* from the (begin+1) position to then end position is in order */
sort_char (begin + 1, p);
permutation(src, begin + 1); /* recursion from next character */
swap_char (p, begin); /* restore: restore *p and *begin */
}
}
}
void sort_char (char * src, char * p){
char * q, * r, * t = p;
if (!('\0' == *src || '\0' == *(src + 1))){ /* there exists at least two characters */
q = src;
while ('\0' != *(q + 1)){
q++;
}
r = p - 1;
while (p > src){
if (*p < *r){
swap_char (p, r);
t = r;
}
p--;
r--;
}
r = p + 1;
while (p < q){
if (*p > *r){
swap_char (p, r);
t = r;
}
p++;
r++;
}
}
p = t; /* restore : new *p is the input *p */
}
int ch_cmp (const void * a, const void * b){
return *(char *)a - *(char *)b;
}
输出:
1234
1243
1324
1342
1423
1432
3124
3142
3214
3241
3412
3421
1234
1243
1324
1342
1423
1432
4123
4132
4213
4231
4312
4321
如果输入的字符串中有重复的字母,那么上述的算法的结果是有重复的。如何去掉重复的排列呢?
这里引用来自Huckbuteer1的博客
http://blog.csdn.net/Hackbuteer1/article/details/7462447#comments
的思想:
“由于全排列就是从第一个数字起每个数分别与它后面的数字交换。我们先尝试加个这样的判断——如果一个数与后面的数字相同那么这二个数就不交换了。如122,第一个数与后面交换得212、221。然后122中第二数就不用与第三个数交换了,但对212,它第二个数与第三个数是不相同的,交换之后得到221。与由122中第一个数与第三个数交换所得的221重复了。所以这个方法不行。”
“换种思维,对122,第一个数1与第二个数2交换得到212,然后考虑第一个数1与第三个数2交换,此时由于第三个数等于第二个数,所以第一个数不再与第三个数交换。再考虑212,它的第二个数与第三个数交换可以得到解决221。此时全排列生成完毕。
这样我们也得到了在全排列中去掉重复的规则——去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换。”
修改为:
/* permutation.c : a sorted one (in ascendeng order) */
#include <assert.h>
#include <stdio.h>
#include <stdlib.h> /* qsort */
/* swap two char */
#define swap_char(r, q)\
if (r!=q){ \
*r = *r ^ *q; \
*q = *r ^ *q; \
*r = *r ^ *q; \
}
/* adjust sequence src so that the elements in it is in order:
* p is the only positon that may breaks the order */
void sort_char (char * src, char * p);
/* print permutation for the string src */
void permutation (char * src, char * begin);
/* for qsort */
int ch_cmp (const void * a, const void * b);
/* decide whether there exists a character equal to *end */
int is_swap (char * begin, char * end){
char * p = begin;
for (; p<end; p++){
if (*p == *end){
return 0;
}
}
return 1;
}
int main(int argc, char* argv[]){
char src[] = "1224";
qsort (src, 4, sizeof (char), ch_cmp);
permutation(src, src);
return 0;
}
/* print permutation for the string src */
void permutation (char * src, char * begin){
assert (NULL != src && NULL != begin);
if(* begin == '\0'){
printf ("%s\n", src);
}
else{
char * p;
for(p = begin; *p != '\0'; p++){ /* loop for strlen(src) times */
if (is_swap (begin, p)){
swap_char (p, begin); /* swap *p and *begin */
/* put *p into a position from the (begin+1) position so that the sequence
* from the (begin+1) position to then end position is in order
*/
/* sort_char (begin + 1, p); *//* if call is_swap (), no longer sort */
permutation(src, begin + 1); /* recursion from next character */
swap_char (p, begin); /* restore: restore *p and *begin */
}
}
}
}
void sort_char (char * src, char * p){
char * q, * r, * t = p;
if (!('\0' == *src || '\0' == *(src + 1))){ /* there exists at least two characters */
q = src;
while ('\0' != *(q + 1)){
q++;
}
r = p - 1;
while (p > src){
if (*p < *r){
swap_char (p, r);
t = r;
}
p--;
r--;
}
r = p + 1;
while (p < q){
if (*p > *r){
swap_char (p, r);
t = r;
}
p++;
r++;
}
}
p = t; /* restore : new *p is the input *p */
}
int ch_cmp (const void * a, const void * b){
return *(char *)a - *(char *)b;
}
1224
1242
1422
2124
2142
2214
2241
2421
2412
4221
4212
4122
问题又来了:如何在有重复字符串的排列中保证输出是升序呢?