对于c++而言,有一个可以直接调用的方法,就是对序列进行全排列并且去重,就是next_permtation();
这个就不多说了,那么这里我用直接用java自己手写一个呢,
全排列表示把集合中元素的所有按照一定的顺序排列起来,使用P(n, n) = n!表示n个元素全排列的个数(假设集合中没有重复元素)。
例如:{1, 2, 3}的全排列为:
123;132;
213;231;
312;321;
共6个,即3! = 6。
这个是怎么算出来的呢?
首先取一个元素,例如取出了1,那么就还剩下{2, 3}。
然后再从剩下的集合中取出一个元素,例如取出2,那么还剩下{3}。
以此类推,把所有可能的情况取一遍,就是全排列了,如图:
小记:全排列就是从第一个数字起每个数分别与它后面的数字交换。
知道了这个过程,算法也就写出来了:
将数组看为一个集合,每次固定一个数字,然后对后面的进行全排列,每次排完之后要进行复位,以便进行下一次交换
for(int i = s;i < c.length;i++) {
swap(c,s,i);
Perm(c,s+1,e);
swap(c,s,i);
}
小记:去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换。用编程的话描述就是第i个数与第j个数交换时,要求[i,j)中没有与第j个数相等的数。可以每次在需要交换时进行顺序查找;
上代码:
import java.util.*;
public class Main{
static Scanner sc = new Scanner(System.in);
//去重,判断是否能i和j能否交换,即区间[i,j),是否有和j相同的数
static boolean isSwap(char[] c,int i,int j) {
for(int k = i;k < j;k++) {
if(c[k]==c[j])
return false;
}
return true;
}
static void swap(char[] c,int i,int j) {
char te = c[i];
c[i] = c[j];
c[j] = te;
}
static void Perm(char[] c,int s,int e){
if(s==e-1) {
System.out.println(Arrays.toString(c));
return;
}
for(int i = s;i < c.length;i++) {
if(isSwap(c, s, i)) {
swap(c,s,i);
Perm(c,s+1,e);
swap(c,s,i);
}
}
}
public static void main(String[] args) {
char[] c = {'a','b','c','c'};
Perm(c,0,4);
}
}
补:使用标记数组去重
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
bool mark[256];
char str[4] ={'1','2','2','4'};
void swap(char* s,int a,int b){
char t;
t = s[a];s[a] = s[b];s[b] = t;
}
void permuation(int from,int to){
if(from == to){
for(int i = 0;i <= to;i++)
cout<<str[i];
cout<<endl;
return;
}
for(int i = from;i <= to;i++){
if(mark[str[i]]) continue;//标记数组去重
mark[str[i]] = true;
swap(str,i,from);
permuation(from+1,to);
swap(str,i,from);
mark[str[i]] = false;
}
}
int main(){
memset(mark,false,sizeof(mark));
permuation(0,3);
return 0;
}
补充:使用c++自带的next_permutation函数直接去重
#include<iostream>
#include<algorithm>
using namespace std;
char str[4] ={'1','2','2','4'};
int main(){
do{
printf("%s\n",str);
} while(next_permutation(str,str+4));
return 0;
}