Java 全排列 交换法与前缀法的对比

从n个不同元素中任取m(m≤n)个元素,按照一定的顺序排列起来,叫做从n个不同元素中取出m个元素的一个排列。当m=n时所有的排列情况叫全排列。

公式:全排列数f(n)=n!(定义0!=1),如1,2,3三个元素的全排列为:
1,2,3
1,3,2
2,1,3
2,3,1
3,1,2
3,2,1
共321=6种。

第一种:交换法(没有字典序)
通过字符数组每个字符相互交换形成全排列,但是要注意在每次交换后要记得回溯,保证下一次的交换是原本的数组顺序才能得到新的排列顺序。

如上图所示,以最左边排列为例,在进行一次排列,因为交换,所以数组的值会变化到k=3时的对应排列,如红色箭头所示,如果想要进行下一次排列交换,必须恢复到原数组ABC的顺序,所以我们使用回溯即黑色箭头所示,将k=3的排列恢复到最一开始的ABC。

import java.util.*;

public class 全排列_无字典序{
	//k代表交换到第几个字符
	static void f(char a[],int k) {
		//全部交换完毕打印
		if (k==a.length) {
			for (int i=0;i<a.length;i++) System.out.print(a[i]+" ");
			System.out.println();
			return ;
		}
		//从第k层开始进行交换
		for (int i=k;i<a.length;i++) {
			swap(a,k,i);
			f(a,k+1);
			swap(a,k,i);//必须回溯到数组最初始的状态
		}
	}
	//交换函数
	static void swap(char a[],int i,int j) {
		char t=a[i];
		a[i]=a[j];
		a[j]=t;
	}
	public static void main(String args[]) {
		Scanner in=new Scanner(System.in);
		String s=in.next();
		char a[]=s.toCharArray();
		f(a,0);
	}
}
**输入:**
ABC
**输出:**
A B C 
A C B 
B A C 
B C A 
C B A 
C A B

//无去重
**输入:**
ABCC
**输出:**
A B C C 
A B C C 
A C B C 
A C C B 
A C C B 
A C B C 
B A C C 
B A C C 
B C A C 
B C C A 
B C C A 
B C A C 
C B A C 
C B C A 
C A B C 
C A C B 
C C A B 
C C B A 
C B C A 
C B A C 
C C B A 
C C A B 
C A C B 
C A B C 

这种方法可以适用于只要求求全排列,而不注重其顺序,因为我们可以观察出来因为交换的缘故,输出的第5和第6个排列其实没有按照字典序排列。

第二种:前缀法(有字典序)
交换法利用字符数字对每个字符进行交换完成全排列,而前缀法使用字符串,在不同的前缀上加上没有被用过的其他字符形成不同排列。

但是需要注意的时的是,如果使用前缀法,对有重复出现的字符比如ABCC,当C成为前缀后,他会认为C已经在前缀里,就不会把他加入前缀,于是我们要加判断条件,保证前缀里的已有字符必须小于原数组里的重复字符的数,也就是保证两个C都能进入排序。

import java.util.Scanner;
import java.util.*;

public class 全排列_有字典序 {

	//全排列,a为原数组,s为前缀
	static void f(char a[],String s) {
		//当前缀和和原数组长度相同打印
		if (s.length()==a.length) {
			System.out.println(s);
			return ;
		}
		//不重样的遍历增加至前缀里
		for (int i=0;i<a.length;i++) {
			char t=a[i];
			/* 判断重复使用,切记保证 例如:原数组为ABCC,
			 * 就算前缀里已经有了C,但是只有一个
			 * 我们需要加入另一个,就判断他们之间的个数
			 * 如果进行这个特判,那将永远不会加入第二个C
			 * 递归永远结束不了*/
			if (count(t,s)<count(t,a)) {
				f(a,s+t);
			}
		}
		
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner in=new Scanner(System.in);
		String s=in.next();
		char a[]=s.toCharArray();
		f(a,"");
	}

	static int count(char t,String s) {
		int sum=0;
		for (int i=0;i<s.length();i++) {
			if (t==s.charAt(i)) sum++;
		}
		return sum;
	}
	static int count(char t,char s[]) {
		int sum=0;
		for (int i=0;i<s.length;i++) {
			if (t==s[i]) sum++;
		}
		return sum;
	}
}
**输入:**
AABC
**输出:**
AABC
AACB
ABAC
ABCA
ACAB
ACBA
BAAC
BACA
BCAA
CAAB
CABA
CBAA

至此,我们进行对比:
交换法: 简单,可以自动处理重复字符,但没有字典排序
前缀法: 复杂,需要手动处理重复字符,但是具备字典序

全排列_交换法_排序去重:
当我们碰到需要排序时又记不住前缀法,就可以使用简单的交换法加上TreeSet排序去重都安排上了。

import java.util.*;

public class 全排列_交换法_排序去重 {

	static Set <String> set=new TreeSet <String> ();
	//k代表交换到第几个字符
	static void f(char a[],int k) {
		//交换完毕后加入set进行排序去重
		if (k==a.length) {
			String s="";
			for (int i=0;i<a.length;i++) {
				s+=a[i]+"";
			}
			set.add(s);
			return ;
		}
		//从第k层开始进行交换
		for (int i=k;i<a.length;i++) {
			swap(a,k,i);
			f(a,k+1);
			swap(a,i,k);//必须回溯到数组最初始的状态
		}
	}
	//交换函数
	static void swap(char a[],int i,int j) {
		char t=a[i];
		a[i]=a[j];
		a[j]=t;
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner in=new Scanner(System.in);
		String s=in.nextLine();
		char a[]=s.toCharArray();
		f(a,0);
		//遍历输出set全排列结果
		Iterator it=set.iterator();
		while (it.hasNext()) System.out.println(it.next());
	}

}
**输入:**
ABCC
**输出:**
ABCC
ACBC
ACCB
BACC
BCAC
BCCA
CABC
CACB
CBAC
CBCA
CCAB
CCBA

只要排序不要排序也可以将交换法的结果全部收集后进行排序,都是可以的。

但是如果遇到要去除重复的排列,最后一种方法可以说是最简单又实用的啦。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值