字符串的全排列_全组合

全排列

全排列介绍两种方法,第一种为递归求全排列;第二种为STL求字典序全排列。

其中,第一种方法自己刚开始不会写,参考了http://blog.csdn.net/morewindows/article/details/7370155

第二种方法是自己根据对字典序性质的理解,自己想的。当然结果下来和上文中的思想也是完全一样。

约定:字符串数组记为arr;其长度为n,用变量size表示。

递归求全排列_未去重

先看一个例子。123的全排列有123, 132, 213, 231, 312, 321 。

其中132可看作123中的2和3交换位置。213可看作123中的1和2交换位置。

所以得到一种思路,就是先交换arr[0]和arr[i]的位置,0 < i < n,然后新的arr[0]和剩余的子串构成了一个排列。递归地对子串也进行交换操作。则可得到所有的排列。

下面在代码中分析:

/*
	k表示当前递归交换进行到的位置
*/
void permutations_recursive_dulplicated(char arr[], int k, int size)
{
	static int count = 0;

	if(k == size)//如果递归交换已经进行到了子串不可再分的状态
	{
		cout<<"第"<<++count<<"个全排列: "<<arr<<endl;
	}
	
	/*从位置k开始,将arr[k]与它后面的字符进行交换。*/
	for(int i = k;i < size;i++)
	{
		swap(arr + i, arr + k);
		permutations_recursive_dulplicated(arr, k + 1, size);//递归地求子串的排列
		swap(arr + i, arr + k);
	}

}

而在main函数中,以k为0,调用permutations_recursive_dulplicated() :

int main(int argc, char* argv[])
{
	char arr[] = "123";

	int size = strlen(arr);

	permutations_recursive_dulplicated(arr, 0, size);

	return 0;
}

递归求全排列_去重

上面的程序当碰到形如122的输入时,会产生重复的排列。所以我们这里要进行去重操作。

判断是否进行交换的函数isSwap() :

/*如果arr[end]的在位置begin到位置end-1的end-begin个值中已经出现过,
  则不进行交换;否则,进行交换*/
bool isSwap(char arr[], int begin, int end)
{
	bool flag = true;

	for(int i = begin;i < end;i++)
	{
		if(arr[i] == arr[end])
		{
			flag = false;
			break;
		}
	}

	return flag;
}

修改后的全排列函数permutations_recursive():

void permutations_recursive(char arr[], int k, int size)
{
	static int count = 0;

	if(k == size)
	{
		cout<<"第"<<++count<<"个全排列: "<<arr<<endl;
	}

	for(int i = k;i < size;i++)
	{
		if(isSwap(arr, k, i))
		{
			swap(arr + i, arr + k);
			permutations_recursive(arr, k + 1, size);
			swap(arr + i, arr + k);
		}
	}

}

STL字典序全排列

STL中有一个next_permutation()的模板,求当前字符串的下一个字典序排列。
如何求下一个字典序呢?以字符串FBEDCA为例,我们要求它的下一个字典序排列,后四位EDCA已经是最大,没办法更大了。所以它的下一个字典序排列,一定是对B进行交换。我们从后向前找可以替换B的字符。首先,最后一位的A,小于B,不能跟B交换,否则生成的新字符串更小。然后是倒数第二位的C,大于B,可以跟B进行交换。交换后得到FCEDBA。我们发现,如果将EDBA进行反转,得到ABDE,续在FC后面,则构成的FCABDE就是原字符串FBEDCA的下一个字典序排列。
这不是巧合。下面总结下我们的步骤。<1>我们从字符串的末尾开始,找相邻的逆序对。这里,如果arr[i] <= arr[i-1]就是“顺的”,反之就是“逆的”。从后面找第一个逆序对的原因是,后面是字典序的低位,后面的顺对已经“大得不能再大了”,要想更大,只有找到前面较高位的逆序对进行交换。当我们找到了第一个逆序对时,即例子中的E与B,我们就需要把B跟一个比B大的字符进行交换。这个字符不能在B的前面找,因为前面的是字典序高位,如果将B跟前面一个比它大的字符交换,新得到的字符串一定比原字符串小。因此我们只能在B的后面找。<2>我们从最后一位开始往前找,把满足大于B的第一个字符与B进行交换,也就是例子中倒数第二位的C。这时我们得到FCEDBA,交换过程可以保证,EDBA一定是非升序的,因为第<1>步找逆序对的时候,E和B是第一对逆序对,所以ED_A...一定满足非升序。又因为,第<2>步的时候,C是从后往前第一个大于B的字符,所以交换之后,B一定是大于等于A...的。而原先就有D >= C且 C > B,所以也可以得到D>B的结论。所以说,我们交换之后,形成的子串,例子中即EDBA,一定是非升序的。要求原字符串的下一个排列,我们希望,子串是最小的,所以我们将子串反转,就得到了非降序的ABDE。合并子串得到的FCABDE就是原字符串FBEDCA的下一个字典序排列。
代码如下:
bool next_permutation(char arr[], int size)
{
	bool flag = false;

	for(int i = size - 1;i > 0;i--)
	{
		if(arr[i] > arr[i - 1])
		{
			flag = true;//存在逆序对
			int j;

			/*从后往前找,找第一个大于arr[i-1]的字符*/
			for(j = size - 1;j > i - 1;j--)
			{
				if(arr[j] > arr[i - 1])
					break;
			}

			swap(arr[j], arr[i - 1]);
			reverse(arr, i, size - 1);
		}
		if(flag)
			break;
	}
	
	return flag;
}

permutation_STL()函数和main()函数中的调用:
void permutations_STL(char arr[], int size)
{
	while(next_permutation(arr, size))
	{
		cout<<arr<<endl;
	}
}

int main(int argc, char* argv[])
{
	char arr[] = "1233";

	int size = strlen(arr);

	permutations_STL(arr, size);


	return 0;
}

全组合

全组合介绍两种方法,一种用递归求解,一种用位运算求解。代码都是自己写的(不容易啊!!!没看别人的!)

递归求全组合

和前面递归求全排列类似的思想。每次把第k个位置上的字符换成'#'表示为空,然后递归求 剩余字符构成的子集合 的全组合;然后再把第k位上的字符换回原字符,再递归求 剩余字符构成的子集合 的全组合。打印的时候,碰到'#',就忽略不打印。
代码如下:
void printArr(char arr[])
{
	char *p = arr;
	while(*p != '\0')
	{
		if(*p != '#')
		{
			cout<<*p;
		}
		p++;
	}
	cout<<endl;
}

void combination_recursive(char arr[], int k, int size)
{
	char ch = '#';
	if(k == size)
	{
		printArr(arr);
	}
	else
	{
		swap(arr[k], ch);
		combination_recursive(arr, k+1, size);
		swap(arr[k], ch);
		combination_recursive(arr, k+1, size);
	}
}

位运算求全组合

求全组合其实就是每个元素选与不选的问题,这两种状态可以分别用1和0表示。这样,长度为n的字符串的一个组合就可以用一个位数为n的二进制数表示。而2^n个全组合正好对应了 0 ~2^n-1个二进制数。于是我们可以遍历0 ~ 2^n-1这些数,对于某个数,我们找出它哪几位是1,则对应位置的字符就处于选择状态,直接输出;那些是0的位,对应字符处于不选择状态,就不输出。
代码如下:
void combination_bits(char arr[], int size)
{
	int bound = power(2, size);
	for(unsigned int i = 0;i < bound;i++)//遍历从0到2^n-1的每个数
	{
		unsigned tmp;
		int count = 0;
		int ii = i;
		for(;count < size;count++)
		{
			tmp = ii;
			tmp = tmp>>1;
			tmp = tmp<<1;
			if(tmp ^ ii)//如果ii的最低位是1
			{
				cout<<arr[count];
			}
			ii = ii>>1;//ii整体右移,则次低位下轮变成最低位
		}
		cout<<endl;
	}
}

打完收工。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值