问题描述
给出一个数组array[]=[A0, A1, A2, A3, ……],求出它的全排列,其中元素可能有重复。
样例输入
1 1 3 4
样例输出
1 1 3 4
1 1 4 3
1 3 1 4
1 3 4 1
1 4 1 3
1 4 3 1
3 1 1 4
3 1 4 1
3 4 1 1
4 1 1 3
4 1 3 1
4 3 1 1
分析
深度优先搜索实现无重复元素的全排列全排列很容易,只需要用深度优先搜索去依次访问array[]各元素,访问过的元素标记访问标识,退出访问(回溯)时取消访问标识,当array[]中所有元素都被访问时即产生了array[]的一个全排列。
只是这样简单实现的话就会有一个问题,比如对于1 1 1的输入会产生6个相同的1 1 1排列。一个简单的解决办法是在求出所有全排列后进行去重处理,去除重复排列。在代码实现的时候可以使用Set无重复元素的特点来实现。
更好的解决办法是对于每一个重复元素,只对其第一个元素进行深度优先搜索,即对于重复元素只进行一次深度优先搜索。在这里需要注意的是,重复元素虽然只进行一次深度优先搜索,但在这一次搜索中是会对所有重复元素进行访问的,不然无法满足搜索边界条件(当array[]中所有元素都被访问时即产生了array[]的一个全排列)。在这里,对于重复元素的非第一个元素的访问需要满足其前一个重复元素已经被访问,如此便可以保证这一次搜索是以第一个元素开始的,并且其后所有的重复元素都是按次序进行访问的。
这里以1 1 2(1_0 1_1 2)为例子说明以下。如果是针对无重复元素实现的深度优先搜索,会将以下6种情况全部输出。(1_0表示第一个重复元素,1_1表示第二个……)
而如果是我们优化过的算法则只会输出1 1 2,1 2 1,2 1 1三个排列(图中黄色排列)。仔细观察,我们输出的排列中,重复元素都是按照输入顺序输出的!也就是说如果输入是1_0 1_1 1_2,那么输出只有1_0 1_1 1_2,而没有1_1 1_0 1_2,1_2 1_0 1_1……
代码
import java.util.*;
public class Main {
//测试类
public static void main(String[] args) {
int n[] = {1,1,3,4};//测试数据
Permutation permutation = new Permutation(n);
while(permutation.hasNext()) {
int result[] = permutation.next();
for (int i:result) {
System.out.print(i+" ");
}
System.out.println();
}
}
}
class Permutation{
private HashMap<String,Boolean> visited;//判断元素是否被访问,已访问则Boolean=true
private int[] onePermutation;//记录array[]的一个全排列
private LinkedList<int[]> allPermutation;//存储array[]的所有全排列
private int[] array;//this.array = array
private int count = 0;//全排列序列
public Permutation(int array[],int start,int end) {//对array[]的[start,end)区间内元素进行全排列
this.array = Arrays.copyOfRange(array, start, end);
fullPermutation();
}
public Permutation(int array[]) {
this.array = Arrays.copyOf(array, array.length);
fullPermutation();
}
public boolean hasNext() {//判断是否还有排列未取出
if(count < allPermutation.size()) return true;
else return false;
}
public int[] next() {//取出排列数组
return allPermutation.get(count++);
}
private void fullPermutation() {//init操作
Arrays.sort(array);//排序
visited = new HashMap<String,Boolean>();
for(int i=0; i<array.length; i++) {//置所有array[]元素为未访问
//对元素array[i],设定key为(String.valueOf(array[i])+String.valueOf(i)),如此,array[]中任一元素均有唯一key
visited.put((String.valueOf(array[i])+String.valueOf(i)), false);
}
onePermutation = new int[array.length];
allPermutation = new LinkedList<int[]>();
fullPermutationSecond(0);
}
private void fullPermutationSecond(int step) {
if(step == array.length) {//边界条件,若array.length个元素均被访问,则产生了一个全排列
allPermutation.add(Arrays.copyOf(onePermutation, onePermutation.length));
}
//for和{之间的if:i==0是为了判断array[i]!=array[i-1]时不会访问越界;
//因为array[]是有序的,所以如果array[i]!=array[i-1]为真则表示array[i]为不重复元素或重复元素中的排在第一个的:
//如果array[i]==array[i-1]&&visited.get((String.valueOf(array[i-1])+String.valueOf(i-1)))为真则表示array[i]为重复元素中非第一个,
//并且在它前面一位的重复元素已经被访问。如此,在满足if条件的情况下,若array[]中有重复元素,则只会对重复元素进行一次递归,从而实现去除重复排列的目的
for(int i=0; i<array.length; i++) if(i==0||array[i]!=array[i-1]||array[i]==array[i-1]&&visited.get((String.valueOf(array[i-1])+String.valueOf(i-1)))){
if(!visited.get((String.valueOf(array[i])+String.valueOf(i)))) {//若array[i]未访问
onePermutation[step] = array[i];//访问array[i]
visited.put((String.valueOf(array[i])+String.valueOf(i)), true);//置array[i]为已访问
fullPermutationSecond(step+1);//访问下一个元素
visited.put((String.valueOf(array[i])+String.valueOf(i)), false);//置array[i]为未访问
}
}
}
}
相比利用深度搜索来求全排列,一个更加简单的办法是使用C++的next_Permutation函数。可是Java里没有怎么办?戳这里:用Java模拟C++的next_Permutation函数