排列(递归与非递归)算法及接口设计

本文我将首先用递归和非递归算法实现对整数数组的全排列,然后通过提炼接口,将这种算法运用到实现对其它数据类型的全排列,对long数组,列表,字符串等。

 

下面代码是用来实现对整数数组的排列的算法,具体原理见这里

	public static List<int[]> perm(int[] a) {
		return perm(a, 0, a.length);
	}
	public static List<int[]> perm(int[] a, int fromIndex, int toIndex) {
		List<int[]> result = new ArrayList<int[]>();
		perm(a, fromIndex, toIndex, result);
		return result;
	}
	
	private static void perm(int[] a, int fromIndex, int toIndex, List<int[]> result) {
		if (toIndex - fromIndex <= 1) {
			result.add(a.clone());
		} else {
			for (int i = fromIndex; i < toIndex; i++) {
				swap(a, fromIndex, i);
				perm(a, fromIndex+1, toIndex, result);
				swap(a, fromIndex, i);
			}
		}
	}
	
	private static void swap(int[] a, int i, int j) {
		int temp = a[i]; a[i] = a[j]; a[j] = temp;
	}
 

这里将所有的结果放在一个List中,这样对小的数据很好,但是对大的数据可能会OutOfMemory,在我的机器上当数据的大小>=10时就会OutOfMemory。我需要将它转换成非递归算法,并返回Iterator,这样保存在内存中的数据极少,而不会OutOfMemory。将该程序转换成非递归算法时,需要先画出递归树,具体该怎么做,这里就不详述了,见代码:

	
	public static Iterator<int[]> permIterator(int[] a, int fromIndex, int toIndex) {
		return new IntArrayPermIterator(a, fromIndex, toIndex);
	}
	public static Iterator<int[]> permIterator(int[] a) {
		return new IntArrayPermIterator(a, 0, a.length);
	}
	public static Iterable<int[]> permIterable(int[] a, int fromIndex, int toIndex) {
		return new IntArrayPermIterator(a, fromIndex, toIndex);
	}
	public static Iterable<int[]> permIterable(int[] a) {
		return new IntArrayPermIterator(a, 0, a.length);
	}
	
	// 将递归翻译成非递归
	private static class IntArrayPermIterator implements Iterator<int[]>, Iterable<int[]> {
		private int[] array;
		private int toIndex;
		private LinkedList<StackFrame> frames = new LinkedList<StackFrame>();
		
		public IntArrayPermIterator(int[] a, int fromIndex, int toIndex) {
			this.array = a;
			this.toIndex = toIndex;
			frames.add(new StackFrame(fromIndex));
		}

		public boolean hasNext() {
			return frames.size() > 0;
		}

		public int[] next() {
			StackFrame frame = frames.getLast();
			swap(array, frame.fromIndex, frame.currentIndex);
			for (int i = frame.fromIndex + 1; i < toIndex; i++) {
				frames.addLast(new StackFrame(i));
			}
			int[] result = array.clone();
			frame = frames.removeLast();
			while (!frames.isEmpty()) {
				frame = frames.getLast();
				swap(array, frame.fromIndex, frame.currentIndex);
				if (frame.currentIndex != toIndex - 1) {
					frame.currentIndex++; break;
				}
				frames.removeLast();
			}
			
			return result;
		}
		
		public void remove() {
			throw new UnsupportedOperationException();
		}

		public Iterator<int[]> iterator() {
			return this;
		}
	}

	
	private static class StackFrame {
		int fromIndex;
		int currentIndex;
		
		public StackFrame(int fromIndex) {
			this.fromIndex = fromIndex;
			this.currentIndex = fromIndex;
		}
		public String toString() {
			return String.format("[%d, %d]", fromIndex, currentIndex);
		}
	}

  IntArrayPermIterator实现Iterator和Iterable接口,实现Iterable接口是为了能够在利用JDK5中增强for循环:

		for (int[] a: permIterable(new int[]{1, 2, 3})) {
			System.out.println(Arrays.toString(a));
		}
 

这样做的问题是,如果要实现对long数组,char数组,List数据,字符串的排列,我几乎需要将它们重写一遍。我想重用这些算法,该怎么办呢?每当这时我都会想到提取接口。让我们观察递归版本的perm(int[] a, int fromIndex, int toIndex, List<int[]> result)方法(非递归版本类似),它用到对数组a的进行操作的方法只有swap方法和a.clone()方法,因此可以将它提取出一个接口叫做Permable,为了不指定toIndex,我添加了一个新方法size()。另外为了不和Object中clone()方法冲突,我使用了copy这个名字。接口如下:

	public interface Permable<T> {
		void swap(int i, int j);
		T copy();
		int size();
	}

 该接口使用了类型参数T,表示返回的数据类型。使用这个接口,递归版本的排列算法为:

	public static <T> List<T> perm(Permable<T> data) {
		return perm(data, 0, data.size());
	}
	
	public static <T> List<T> perm(Permable<T> data, int fromIndex, int toIndex) {
		List<T> result = new ArrayList<T>();
		perm(data, fromIndex, toIndex, result);
		return result;
	}
	
	private static <T> void perm(Permable<T> data, int fromIndex, int toIndex, List<T> result) {
		if (toIndex - fromIndex <= 1) {
			result.add(data.copy());
		} else {
			for (int i = fromIndex; i < toIndex; i++) {
				data.swap(fromIndex, i);
				perm(data, fromIndex+1, toIndex, result);
				data.swap(fromIndex, i);
			}
		}
	}

 要对long数组,字符串,List等进行排序,只需要提供它们自己的Permable接口实现:

	public static class LongArrayPermable implements Permable<long[]> {
		private long[] data;
		public LongArrayPermable(long[] data) {
			this.data = data;
		}
		public void swap(int i, int j) {
			long temp = data[i]; data[i] = data[j]; data[j] = temp;
		}
		public long[] copy() {
			return data.clone();
		}
		public int size() {
			return data.length;
		}
	}

	public static class CharArrayPermable implements Permable<char[]> {
		private char[] data;
		public CharArrayPermable(char[] data) {
			this.data = data;
		}
		public void swap(int i, int j) {
			char temp = data[i]; data[i] = data[j]; data[j] = temp;
		}
		public char[] copy() {
			return data.clone();
		}
		public int size() {
			return data.length;
		}
	}
	
	public static class StringPermable implements Permable<String> {
		private char[] data;
		public StringPermable(String str) {
			this.data = str.toCharArray();
		}
		public StringPermable(char[] data) {
			this.data = data;
		}
		public String copy() {
			return new String(data);
		}
		public int size() {
			return data.length;
		}
		public void swap(int i, int j) {
			char temp = data[i]; data[i] = data[j]; data[j] = temp;
		}
	}

	public static class ObjectArrayPermable implements Permable<Object[]> {
		private Object[] data;
		public ObjectArrayPermable(Object[] data) {
			this.data = data;
		}
		public void swap(int i, int j) {
			Object temp = data[i]; data[i] = data[j]; data[j] = temp;
		}
		public Object[] copy() {
			return data.clone();
		}
		public int size() {
			return data.length;
		}
	}
	
	public static class ListPermable<E> implements Permable<List<E>> {
		private List<E> data;
		private Class<?> implementation;
		
		public ListPermable(List<E> data, Class<?> listImplementation) {
			this.data = data;
			this.implementation = listImplementation;
		}
		public ListPermable(List<E> data) {
			this(data, data.getClass());
		}
		
		@SuppressWarnings("unchecked")
		public List<E> copy() {
			if (ArrayList.class == implementation) {
				return new ArrayList<E>(data);
			} else {
				try {
					List<E> list = (List<E>) implementation.newInstance();
					list.addAll(data);
					return list;
				} catch (InstantiationException e) {
					throw new IllegalStateException(e);
				} catch (IllegalAccessException e) {
					throw new IllegalStateException(e);
				}
			}
		}

		public int size() {
			return data.size();
		}

		public void swap(int i, int j) {
			E temp = data.get(i); data.set(i, data.get(j)); data.set(j, temp);
		}
	}
 

现在要得到字符串,long数组的排列,可以:

		for (long[] a : perm(new LongArrayPermable(new long[]{1, 2, 3}))) {
			System.out.println(Arrays.toString(a));
		}
		for (String a : perm(new StringPermable("abc"))) {
			System.out.println(a);
		}

它该接口用在非递归版本的排列算法上也很简单,只需要做几处简单的替换就可以了,这里就不列举了,见附件。

 

参考文章:

全排列算法原理和实现: http://www.blogjava.net/nokiaguy/archive/2008/05/11/199838.html

如何用栈实现递归与非递归的转换: http://www.chinaunix.net/jh/23/331522.html ,这篇文章给我启发,将递归算法转换成非递归算法时需要行画出递归树。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值