《编程之法》1.3字符串的全排列,组合,重复排列,八皇后问题

题目描述:输入一个字符串,打印出该字符串中字符的所有排列,例如输入"abc",输出"abc","acb","bac","bca","cab","cba"

解法一:递归实现

类似于图的深度遍历搜索求全路径的算法,每次交换两个数,并输出,按照递归的方法,如求abcd的全排序,1:先求abcd后面的bcd全排列(同样先求b后面cd的全排列,然后b与后面的元素依次交换);2:求ab交换后的bacd后面的acd全排列(同样先求a后面cd的全排列,然后a与后面的元素依次交换);3:先ac交换后的cbad后面的bad全排列(同样先求b后面ad的全排列,然后b与后面的元素依次交换);4:求ad交换后的dbca后面的bca全排列(同样先求b后面ca的全排列,然后b与后面的元素依次交换)。

时间复杂度为O(n!)。

#include <iostream>
#include <string>
using namespace std;

void AllPermutation(string &str, int start, int end){
	if(start == end){
		cout << str << endl;
		return;
	}
	int i;
	for(i = start; i <= end; i++){
		swap(str[start], str[i]);
		AllPermutation(str, start+1, end);
		swap(str[start], str[i]);
	}
}

int main(){
	string str;
	while(cin >> str){
		if(str.size() == 0)//字符串长度为0直接返回
			break;
		AllPermutation(str, 0, str.size()-1);
	}
	return 0;
}

解法二:按字典序排列

如输出zaf的全排列,先sort排序为afz,然后按字典序依次输出:afz, azf, faz, fza, zaf, zfa。时间复杂度为O(n!)。

求当前字符串的下一个字典序字符串的算法思路:(以967812543为例)

1,找出排列中最后一个连续升序序对的首位位置i;如上面的67,78和12,25都为升序序对,其中25为最后一个升序子序列,故所求i为2所在的下标5。

2,找出排列中i位置之后的最后一个比ai大的位置j;如上面的i=5,ai=2,则i右边比ai大的有5,4,3,选择最后一个比ai大的位置,即j为3所在的下标8。

3,交换i和j所对应的元素,之后将第i+1位到最后的部分翻转,这是因为i是最后一个升序序对首位置,则后面的必定全部是降序排列;如上面先交换2和3,变为967813542,在将下标6开始的剩余子串逆置,变为967813245。

#include <iostream>
#include <string>
#include <algorithm>
using namespace std;

void AllPermutation(char *str, int num){
	int i, j;
	//输出当前序列
	for(i = 0; i < num; ++i)
		cout << str[i];
	cout << endl;
	//寻找下一字典序序列
	//寻找i
	for(i = num-2; i >= 0; --i)
		if(str[i] < str[i+1])
			break;
	//递归结束条件:跳出递归,说明此时str中字符串完全降序
	if(i < 0)
		return;
	//寻找j
	for(j = num-1; j >= i+1; --j)
		if(str[j] > str[i])
			break;
	//交换i,j所对应元素,并将下标i后面的字符串逆置
	swap(str[i], str[j]);
	reverse(str+i+1, str+num); //和sort函数一样为标准函数模板调用
	AllPermutation(str, num);
}

int main(){
	char str[110];
	while(cin >> str){
		int num = 0;
		while(str[num]) ++num; //注意别写成while(str[num++]);
		if(num == 0)//字符串长度为0直接返回
			break;
		sort(str, str+num);//注意要先排序
		AllPermutation(str, num);
	}
	return 0;
}

举一反三:

1,字典序的所有排列:已知输入字符串的各个字符是不同的,按照字典序输出它的所有组合。如输入aa,则输出aa,ab,ba,bb。

解法一:

先分析abc: 输出aaa,aab,aac,aba,abb,abc,aca,acb,acc,baa......,可以分析一下规律

先分配一个数组ans[]存放新字符串,同样运用递归,递归的临界条件是输出字符串中所有字符都相等,且该字符为原字符串最大的那个字符,也先要sort原字符串str[],很显然第一个ans全由最小的字符组成,如ans[] = {a,a,a...a}。

如何求当前字符串的下一字典序字符串:

先在str[]中设置一个全局升序指针j用于循环向右移动,赋初值指向str[]中第二个字符

a,若ans[]的最后一个元素小于str[n-1](字符串中最大元素),交换str[j]和ans[num-1],之后j前移并进入下次递归;如aab变为aac,aba变为abb

b, 若ans[]的最后一个元素等于于str[n-1],则可调用一个自定义函数,函数的作用是指针i指向ans[]最后一个位置,使i所指元素ans[]变为str[0],并令指针i前移,若ans[i]仍然等于str[i-1],令其等于str[0]后,则继续前移至ans[i]<str[i-1],此时将str[i]赋值成一个稍微比他大的那个字符,并且此时重新令j=0;如acc变为baa。

算法复杂度,由于需要输出n^n个结果,且每次递归都要遍历一次数组,则时间复杂度为O(n^n*n) = O(n^n)。

#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int j;
char str[110], ans[110];

void Access(int cur, int num){
	while(ans[cur] == str[num-1]){
		ans[cur] = str[0];
		--cur;
	}
	int i;
	for(i = 0; i < num; i++)
		if(ans[cur] == str[i])
			break;
	ans[cur] = str[i+1];
	j = 0;
}
void AllPermutation(int num){
	int i;
	//先输出当前字符串
	for(i = 0; i < num; ++i)
		cout << ans[i];
	cout << endl;
	//临界条件
	int flag = 0;
	for(i = 0; i < num; ++i)
		if(ans[i] != str[num-1])
			flag = 1;
	if(!flag)
		return;
	//交换ans[num-1],str[j]
	if(ans[num-1] < str[num-1])
		ans[num-1] = str[j];
	else
		Access(num-1, num);
	++j;
	if(j == num)
		j = 0;
	AllPermutation(num);
}

int main(){
	while(cin >> str){
		int num = 0;
		while(str[num]) ++num; //注意别写成while(str[num++]);
		if(num == 0)//字符串长度为0直接返回
			break;
		sort(str, str+num);
		int i;
		for(i = 0; i < num; ++i)
			ans[i] = str[0];
		j = 1;
		AllPermutation(num);
	}
	return 0;
}

2,字符的所有组合:如输入“abc”,输出"a","b","c","ab","ac","bc","abc"。

解法一:我们要分开求长度为1的组合,长度为2的组合,...,长度为n的组合。先考虑其中一种情况,如长度为k时的情况,这个时候我们又可以分为两种情况进行考虑:

a, 如果组合里包含第一个字符,则从所有剩余n-1个字符中选取k-1个字符;

b, 如果组合中不包含第一个字符,则从剩余的n-1个字符中选取k个字符。

这可以使用递归方式解决,使用vector来保存已加入当前组合的值,如下,记住Combination这个模式(临界判断-->输出并返回-->加入当前字符并递归-->去掉当前字符并递归),该模式在求多个数相加等于固定数时也会用到。

有两种返回条件:1,如1,2,3,4,5中当求长度为3的组合时,此时vector只收集了4,5,很显然再递归会造成下标溢出;2,正好某次递归中k==0,数组中可能还有字符也可能正好没有了,此时并直接输出,若正好此时pos=num-1,再次递归也会造成下标溢出,最好是返回。

#include <iostream>
#include <vector>
using namespace std;
vector<char> result;
int num;
void Combination(char *str, int pos, int k){
	if(pos == num && k != 0)//有两种返回条件:临界条件
		return;
	if(k == 0){
		vector<char>::iterator iter = result.begin();
		for( ; iter < result.end(); ++iter)
			cout << *iter;
		cout << endl;
		return; //此时也需返回,否则str[pos]=str[num]可能未定义
	}
	result.push_back(str[pos]);
	Combination(str, pos+1, k-1); //从字符串下标pos+1起的后面选择剩余的k-1个字符
	result.pop_back();
	Combination(str, pos+1, k); //从字符串下标pos+1起的后面选择剩余的k个字符
}

int main(){
	char str[110];
	while(cin >> str){
		num = 0;
		while(str[num]) num++;
		if(num == 0) break;
		int i;
		for(i = 1; i <= num; ++i)
			Combination(str, 0, i);
	}
	return 0;
}

解法二:以abc为例,若三个字符a,b,c分别对应一个二进制数的第0,1,2位,则可用二进制001代表"a",二进制010代表"b",二进制011代表"ab",二进制100代表"c",...111代表"abc",换句话说,1~7中的任何一个数字正好对应原字符串的一个组合。

根据数学知识:长度为n的字符串的组合数 = C1/n + C2/n + ... + Cn/n = 2^n - C0/n = 2^n - 1;,故只需对1~2^n-1的数进行遍历,对于该范围的某个k,分析它的二进制位在0~n-1位有哪些为1,则输出str[]中对应的字符;哪些为0,则跳过。

#include <iostream>
#include <cmath>
using namespace std;
void Combination(char *str, int n){
	int num = (int)pow(2.0, n) - 1;
	int i, j;
	for(i = 1; i <= num; ++i){
		for(j = 0; j < n; j++){
			if(i & (1 << j)) //注意:&是按位与,&&是逻辑与(整体逻辑判断)
				cout << str[j];
		}
		cout << endl;
	}
}

int main(){
	char str[110];
	while(cin >> str){
		int n = strlen(str);
		Combination(str, n);
	}
	return 0;
}

相关题目:

1,求正方体对面顶点和相等数组:输入一个含有8个数字的数组分别置于正方体的8个顶点上,使得正方体三组相对的面上的4个顶点和都相等,即:

a1+a2+a3+a4=a5+a6+a7+a8; a1+a3+a5+a7=a2+a4+a6+a8; a1+a2+a5+a6=a3+a4+a7+a8。

解法:对这8个数全排列,对每个全排列进行条件判断,判断成立则输出,否则什么也不做。

#include <iostream>
using namespace std;
bool IsEqual(int nums[]){
	int sum1 = nums[0] + nums[1] + nums[2] + nums[3];
    int sum2 = nums[4] + nums[5] + nums[6] + nums[7];
    int sum3 = nums[0] + nums[2] + nums[4] + nums[6];
    int sum4 = nums[1] + nums[3] + nums[5] + nums[7];
    int sum5 = nums[0] + nums[1] + nums[4] + nums[5];
    int sum6 = nums[2] + nums[3] + nums[6] + nums[7];
    if(sum1 == sum2 && sum3 == sum4 && sum5 == sum6)
        return true;
    else
        return false;
}

void AllPermutation(int *nums, int start, int end){
	if(start == end){
		if(IsEqual(nums)){
			int i;
			for(i = 0; i <= end; ++i)
				cout << nums[i] << " ";
			cout << endl;
		}
		return;
	}
	int i;
	for(i = start; i <= end; i++){
		swap(nums[start], nums[i]);
		AllPermutation(nums, start+1, end);
		swap(nums[start], nums[i]);
	}
}

int main(){
	int nums[110], n;
	while(cin >> nums[0]){ //输入8个数表示正方体的8个顶点
		int i;
		for(i = 1; i <= 7; ++i)
			cin >> nums[i];
		AllPermutation(nums, 0, 7);
	}
	return 0;
}

2,八皇后问题

问题描述: 在8 X 8的国际象棋上摆放八个皇后,使其不能相互攻击,即任意两个皇后不得处于同一行,同一列或者同一对角线上,求出所有符合条件的摆法。

问题分析: 任意两个皇后不得处于同一行,由此可得每个皇后都单独占据一行。我们可以定义一个数组ColumnIndex[8],其中ColumnIndex[i]表示处在第i行位置的那个皇后对应在ColumnIndex[i]列,例如ColumnIndex1 = 3 表示处在第1行的皇后在第3列上。 接下来,分别用0~7这8个数字对ColumnIndex进行初始化。注意,此时所有的皇后不同行也不同列。 因此,我们只需要对ColumnIndex数组进行全排列,判断每一个排列所对应的8个皇后的位置是否在对角线上即可。

#include <iostream>
#include <string>
using namespace std;
bool IsNoDiag(int *nums){
	int i, j;
    for(int i = 0; i < 8; i++){
        for(int j = i + 1; j < 8; j++){
            if(i - j == nums[i] - nums[j] || i - j == nums[j] - nums[i])
				return false;
		}
	}
    return true;;
}

void AllPermutation(int *nums, int start, int end){
	//临界条件
	if(start == end){
		if(IsNoDiag(nums)){
			int i;
			for(i = 0; i <= end; ++i)
				cout << "(" << i << ", " << nums[i] << ")" << " ";
			cout << endl;
		}
		return;
	}
	//下次递归
	int i;
	for(i = start; i <= end; ++i){
		swap(nums[start], nums[i]);
		AllPermutation(nums, start+1, end);
		swap(nums[start], nums[i]);
	}
}

int main(){
	int nums[8] = {0, 1, 2, 3, 4, 5, 6, 7};
	AllPermutation(nums, 0, 7);
}

时间: 2024-12-19 17:53:22

《编程之法》1.3字符串的全排列,组合,重复排列,八皇后问题的相关文章

全排列方法求解八皇后问题

#include<iostream> using namespace std; int C[8]; int res = 0;//多少组解 void EightQueen(int n,int curr) { if (curr == n) { for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { if (C[i] == j) cout << "Q "; else cout <<

Lambda&Java多核编程-6-方法与构造器引用

在Lambda&Java多核编程-2-并行与组合行为一文中,我们对Stream<Contact>里的每一位联系人调用call()方法,并根据能否打通的返回结果过滤掉已经失效的项. 应该注意到此时filter(..)中Lambda的写法有些特殊: // ....filter(Contact::call)// ... 按常理我们应该使用s -> s.call(),但是这里却将参数.箭头以及对参数调用方法全部用其类型Contact的方法标签(暂且这样称呼)call来代替,而这个::就跟

POJ C程序设计进阶 编程题#2:字符串中次数第2多的字母

编程题#2:字符串中次数第2多的字母 来源: POJ (Coursera声明:在POJ上完成的习题将不会计入Coursera的最后成绩.) 注意: 总时间限制: 1000ms 内存限制: 65536kB 描述 输入一串长度不超过500个符号的字符串,输出在串中出现第2多的英语字母(大小写字母认为相同)和次数(如果串中有其它符号,则忽略不考虑).如果有多个字母的次数都是第2多,则按串中字母出现的顺序输出第1个. 例 ab&dcAab&c9defgb 这里,a 和 b都出现3次,c和d都出现2

字符串的全排列JAVA实现

package com.kpp; /** * 求字符串的全排列 * 递归的思想 * 比如 abcde 先求出abcd的全排列,然后将e分别插入全排列的5个位置 * a 全排列 a * ab 全排列 ab ba * abd 全排列即是 cab acb abc cba bca bac * * @author kpp * */ public class QuanPaiLie { /** * @param args */ public static void main(String[] args) {

28. 字符串的全排列之第2篇[string permutation with repeating chars]

[本文链接] http://www.cnblogs.com/hellogiser/p/string-permutation-with-repeating-chars.html [题目] 输入一个字符串,打印出该字符串中字符的所有排列.例如输入字符串abc,则输出由字符a.b.c所能排列出来的所有字符串abc.acb.bac.bca.cab和cba.例如输入字符串aba,则输出由字符a.b所能排列出来的所有字符串aab.aba.baa. [分析] 之前的博文28.字符串的排列之第1篇[String

串口通讯编程中涉及到的字符串处理关键字及处理字符串对应函数

一   字符串处理关键字总结 作用 关键字 比较两个字符串. StrComp 变换字符串. StrConv 大小写变换. Format, LCase, UCase 建立重复字符的字符串. Space, String 计算字符串长度. Len 设置字符串格式. Format 重排字符串. LSet, RSet 处理字符串. InStr, Left, LTrim, Mid, Right, RTrim, Trim 设置字符串比较规则. Option Compare 运用 ASCII 与 ANSI 值.

字符串数组全排列——逐个追加组合算法

我们在笔试面试过程中经常会遇到关于排列与组合的问题,其实这些可以通过递归简单的实现,看下面两个例子: (1)关于字符串排列的问题 输入一个字符串,打印出该字符串中字符的所有排列.例如输入字符串abc,则输出由字符a.b.c所能排列出来的所有字符串abc.acb.bac.bca.cab和cba. 可以这样想:固定第一个字符a,求后面两个字符bc的排列.当两个字符bc的排列求好之后,我们把第一个字符a和后面的b交换,得到bac;接着我们固定第一个字符b,求后面两个字符ac的排列.现在是把c放到第一位

显示字符串的全排列

显示字符串的全排列: 1 public static void AllSequenceofString(String string){ 2 if(string == null) 3 return; 4 char[] chars = string.toCharArray(); 5 Permutation(chars,0); 6 } 7 private static void Permutation(char[] chars, int index) { 8 // TODO Auto-generate

【编程题目】对称子字符串的最大长度 ★

73.对称字符串的最大长度(字符串).题目:输入一个字符串,输出该字符串中对称的子字符串的最大长度.比如输入字符串“google”,由于该字符串里最长的对称子字符串是“goog”,因此输出 4. 虽然知道会有简单的方法,可脑子就是转不动了,只好用最常见的,对所有可能的字符串判断是否为对称的.再输出最大长度 O(N3) /* 73.对称字符串的最大长度(字符串). 题目:输入一个字符串,输出该字符串中对称的子字符串的最大长度. 比如输入字符串“google”,由于该字符串里最长的对称子字符串是“g