数组的排列
Permutation of array(数组的排列)是一个常见的问题,解决这个问题有多种方法。下面是一个用Java实现的排列的示例代码,这个代码可以生成给定数组的所有排列。
// Permute.java -- A class generating all permutations import java.util.Iterator; import java.util.NoSuchElementException; import java.lang.reflect.Array; public class Permute implements Iterator { private final int size; private final Object [] elements; // copy of original 0 .. size-1 private final Object ar; // array for output, 0 .. size-1 private final int [] permutation; // perm of nums 1..size, perm[0]=0 private boolean next = true; // int[], double[] array won't work :-( public Permute (Object [] e) { size = e.length; elements = new Object [size]; // not suitable for primitives System.arraycopy (e, 0, elements, 0, size); ar = Array.newInstance (e.getClass().getComponentType(), size); System.arraycopy (e, 0, ar, 0, size); permutation = new int [size+1]; for (int i=0; i<size+1; i++) { permutation [i]=i; } } private void formNextPermutation () { for (int i=0; i<size; i++) { // i+1 because perm[0] always = 0 // perm[]-1 because the numbers 1..size are being permuted Array.set (ar, i, elements[permutation[i+1]-1]); } } public boolean hasNext() { return next; } public void remove() throws UnsupportedOperationException { throw new UnsupportedOperationException(); } private void swap (final int i, final int j) { final int x = permutation[i]; permutation[i] = permutation [j]; permutation[j] = x; } // does not throw NoSuchElement; it wraps around! public Object next() throws NoSuchElementException { formNextPermutation (); // copy original elements int i = size-1; while (permutation[i]>permutation[i+1]) i--; if (i==0) { next = false; for (int j=0; j<size+1; j++) { permutation [j]=j; } return ar; } int j = size; while (permutation[i]>permutation[j]) j--; swap (i,j); int r = size; int s = i+1; while (r>s) { swap(r,s); r--; s++; } return ar; } public String toString () { final int n = Array.getLength(ar); final StringBuffer sb = new StringBuffer ("["); for (int j=0; j<n; j++) { sb.append (Array.get(ar,j).toString()); if (j<n-1) sb.append (","); } sb.append("]"); return new String (sb); } public static void main (String [] args) { for (Iterator i = new Permute(args); i.hasNext(); ) { final String [] a = (String []) i.next(); System.out.println (i); } } }
这段代码实现了一个名为`Permute`的类,该类接受一个对象数组作为参数,并生成该数组的所有排列。它使用了迭代器模式来逐个返回排列。`Permute`类的构造函数接受一个对象数组作为参数,并将其复制到`elements`数组中。它还创建了一个与原始数组类型相同的新数组`ar`,用于存储输出结果。`permutation`数组存储了排列的索引。
在`Permute`类的`next`方法中,首先调用了`formNextPermutation`方法来生成下一个排列。然后使用循环找到当前排列的下一个较大的排列。如果找不到下一个排列,则将`next`标志设置为`false`,并重置`permutation`数组。最后,返回生成的排列。
`Permute`类还提供了`toString`方法,用于将排列转换为字符串表示。`main`方法使用`Permute`类来生成给定数组的所有排列,并将它们打印到控制台。
这个实现使用了Java的反射机制,因此可以处理任意类型的对象数组。然而,它不能处理原始类型的数组。
Permutation of array(数组的排列)是一个常见的问题,这段代码提供了一种解决方法。它通过生成给定数组的所有排列,并使用迭代器模式逐个返回排列。这个实现可以处理任意类型的对象数组。如果需要处理原始类型的数组,需要对代码进行修改。
排列数组是一个常见的问题,即将数组中的元素重新排列,以产生所有可能的排列组合。这个问题通常出现在需要枚举所有可能排列的情况下,例如在算法设计中或在解决某些数学问题时。
解决这个问题的一种常见方法是使用C++的标准库函数std::next_permutation
。该函数位于<algorithm>
头文件中,并且可以在C++中方便地实现排列数组。
下面是一个示例代码,展示了如何使用std::next_permutation
函数来排列数组:
int a[] = {3,4,6,2,1}; int size = sizeof(a)/sizeof(a[0]); std::sort(a, a+size); do { // 打印数组a的元素 } while(std::next_permutation(a, a+size));
在这个示例中,我们首先对数组a进行排序,以确保得到的排列是按照字典顺序的。然后,我们使用do-while
循环来重复调用std::next_permutation
函数,直到所有可能的排列都被枚举完毕。在每次循环中,我们可以对数组a的元素进行处理,例如打印出来。
总之,通过使用C++的std::next_permutation
函数,我们可以方便地解决排列数组的问题,并且能够枚举出所有可能的排列组合。这在算法设计和解决数学问题时非常有用。
排列数组(Permutation of array)问题的出现是因为需要打印出一个数组的所有排列。解决方法是使用递归算法,通过交换数组中的元素来生成所有可能的排列。代码如下:
public class Permute{ static void permute(java.util.Listarr, int k){ for(int i = k; i < arr.size(); i++){ java.util.Collections.swap(arr, i, k); permute(arr, k+1); java.util.Collections.swap(arr, k, i); } if (k == arr.size() -1){ System.out.println(java.util.Arrays.toString(arr.toArray())); } } public static void main(String[] args){ Permute.permute(java.util.Arrays.asList(3,4,6,2,1), 0); } }
这个算法的核心思想是,首先将数组的第一个元素和数组中的任意一个元素进行交换,然后递归地对剩下的元素进行排列。在递归调用之后,必须将交换过的元素再次交换回来,以恢复元素的顺序(即进行回溯)。当k等于数组长度减1时,打印出当前的排列。
然而,这个算法存在一些问题。首先,它是递归算法,不太适合与迭代器一起使用。其次,如果输入数组中允许重复元素,则无法得到正确的结果。例如,对于输入[3,3,4,4],期望的所有排列(不重复)应该是:
[3, 3, 4, 4] [3, 4, 3, 4] [3, 4, 4, 3] [4, 3, 3, 4] [4, 3, 4, 3] [4, 4, 3, 3]
但是如果直接应用上述的permute
函数,会得到[3,3,4,4]四次,这并不是我们期望的结果。这种情况下,正确的排列数是4!/(2!*2!)=6。
为了解决这个问题,可以修改上述算法,但是这样做会显得不够简洁。幸运的是,有一种更好的算法可以解决这个问题。这个算法可以处理重复值,并且不使用递归。核心思想是将任何对象的排列问题转化为整数的排列问题。为了得到一个整数数组的排列,首先将数组按升序排序,然后将其转化为降序排列。为了生成下一个排列,需要找到从末尾开始的第一个索引,该索引处的值不再是降序。然后将该索引处的值改为大于它的最小值,并将其后的部分按升序排列。下面是算法的核心部分:
// ind是一个整数数组 for(int tail = ind.length - 1;tail > 0;tail--){ if (ind[tail - 1] < ind[tail]){//还在递增 //找到最后一个不超过ind[tail-1]的元素 int s = ind.length - 1; while(ind[tail-1] >= ind[s]) s--; swap(ind, tail-1, s); //将尾部的元素顺序反转 for(int i = tail, j = ind.length - 1; i < j; i++, j--){ swap(ind, i, j); } break; } }
下面是完整的迭代器代码。构造函数接受一个对象数组,并使用HashMap将其映射为整数数组。代码如下:
import java.lang.reflect.Array; import java.util.*; class Permutationsimplements Iterator { private E[] arr; private int[] ind; private boolean has_next; public E[] output;//next()返回这个数组,将其设为public Permutations(E[] arr){ this.arr = arr.clone(); ind = new int[arr.length]; //将任何元素的数组转化为整数数组 - 使用第一次出现的索引进行枚举 Map hm = new HashMap (); for(int i = 0; i < arr.length; i++){ Integer n = hm.get(arr[i]); if (n == null){ hm.put(arr[i], i); n = i; } ind[i] = n.intValue(); } Arrays.sort(ind);//从升序开始 //output = new E[arr.length]; <-- 由于Java中的泛型不能这样做,所以使用反射 output = (E[]) Array.newInstance(arr.getClass().getComponentType(), arr.length); has_next = true; } public boolean hasNext() { return has_next; } /** * 计算下一个排列。每次返回相同的数组实例! * */ public E[] next() { if (!has_next) throw new NoSuchElementException(); for(int i = 0; i < ind.length; i++){ output[i] = arr[ind[i]]; } //获取下一个排列 has_next = false; for(int tail = ind.length - 1;tail > 0;tail--){ if (ind[tail - 1] < ind[tail]){//还在递增 //找到最后一个不超过ind[tail-1]的元素 int s = ind.length - 1; while(ind[tail-1] >= ind[s]) s--; swap(ind, tail-1, s); //将尾部的元素顺序反转 for(int i = tail, j = ind.length - 1; i < j; i++, j--){ swap(ind, i, j); } has_next = true; break; } } return output; } private void swap(int[] arr, int i, int j){ int t = arr[i]; arr[i] = arr[j]; arr[j] = t; } public void remove() { } }
使用示例代码如下:
Permutationsperm = new Permutations (new Integer[]{3,3,4,4,4,5,5}); int count = 0; while(perm.hasNext()){ System.out.println(Arrays.toString(perm.next())); count++; } System.out.println("total: " + count);
以上代码将打印出所有的210个排列。
对于问题中提到的为什么是4!/(2!*2!)=6而不是4!/(2!)=12的疑问,可以这样解释:首先,我知道答案是6(从[3,3,4,4]的例子中可以得到)。然后,我们将[3,3,4,4]想象成两个蓝色球和两个红色球。问题是如何排列这些球(相同颜色的球是一样的)。如果你已经排好了球的位置,那么交换蓝色球(2!种方式)或红色球(2!种方式)不会改变结果。现在,我们有4!种方式来放置4个球,但是交换蓝色球(2!种方式)或红色球(2!种方式)不会改变球的位置。所以最终答案是4!/(2!*2!)。
第一个算法的时间复杂度是O(n*n!),这是我尝试过的最快的排列算法。