字符串全排列-非递归实现

一个算法命题:给定字符串S[0…N-1],设计算法,枚举S的全排列。如:123,全排列就是:123,132,213,231,312,321


算法思考

由123的全排列:123,132,213,231,312,321可知,这个全排列大小是有序的。也就是说,从最小的开始排列,每次只找比当前排列大一点的序列即可,这也是核心。

算法描述

起点:字典序最小的排列,例如12345

终点:字典序最大的排列,例如54321

过程:从当前排列生成字典序刚好比它大的下一个排列


一个演进:

21543的下一个排列的思考过程:

逐位考察哪个能增大:一个数右面有比它大的数存在,它就能增大。

那么最后一个能增大的数是——x = 1,1应该增大到多少?

增大到它右面比它大的最小的数——y = 3

应该变为23xxx。 显然,xxx应由小到大排:145 , 得到23145


整理成算法语言

 步骤:后找、小大、交换、翻转——

后找:字符串中最后一个升序的位置i,即:S[k]>S[k+1](k>i),S[i]

查找(小大):S[i+1…N-1]中比Ai大的最小值Sj;

交换:Si,Sj;

翻转:S[i+1…N-1]; 为什么要翻转?因为交换操作后,S[i+1…N-1]一定是降序的

以上是官话,比较难理解,我的理解如下

 
 
过程化:整理成算法语言
步骤:后找、小大、交换、翻转——
后找、查找:从倒数第二位开始作为基础位置,找基础位置后的比基础位置上的数大的且最小的数
交换:交换基础位置上的数 和 找到的比基础位置大的最小的数
翻转:将基础位置之后的数反转。因为此时基础位置后的数一定是降序的,要让它成为升序

java代码实现

	/*有序序列的全排列算法*/
	void allSequence(char[] s){
		System.out.println(s);//初始打印
		boolean isEnd = false;
		/*如果有找到可交换的位置,则可以进行下一次;否则完成全排序*/
		while(!isEnd){
			int[] fromAndToIndex = getSwapFromAndToIndex(s);
			if(fromAndToIndex != null){
				swapChar(s,fromAndToIndex[1],fromAndToIndex[0]);
				revertStr(s,fromAndToIndex[0]+1);//反转from右边的数,即右边的数从小到大排序
				System.out.println(s);
			}else{
				isEnd = true;
			}
		}
	}
	
	/**
	 * 获得字符串序列中两个交换位置,这两个位置上的数交换后,可以增大该字符串序列。这里获得的就是能最小增大的两个交换位置。
	 * 例如:1342,那么找两个位置上的数交换后能让他增大。那基础位置就可以从倒数第二个数开始,然后从基础位置往后找,<br/>
	 * 	若找到比基础位置上的数大的数,那此时交换两个数就能增大这个序列。并且要找到比基础位置上的数大并且是所有比它大的最小的数
	 * @param str
	 * @return 返回的int[2]数组元素。0位置元素记录交换的左边位置,1位置元素记录交换的右边位置 
	 */
	int[] getSwapFromAndToIndex(char[] str){
		int[] fromAndTo = new int[2];	//fromAndTo[0]记录基础位置
		int basis = str.length - 2;		//基础位置
		int afterBasis = basis + 1;		//可交换位置	
		fromAndTo[1] = -1;
		while(basis >= 0){
			if(str[basis] < str[afterBasis]){
				if(fromAndTo[1] < 0 || (fromAndTo[1] > 0 && str[fromAndTo[1]] > str[afterBasis])){
					fromAndTo[1] = afterBasis;
				}
			}
			if(++afterBasis > str.length - 1){
				if(fromAndTo[1] < 0){
					basis--;
					afterBasis = basis + 1;
					fromAndTo[1] = -1;
				}else{
					fromAndTo[0] = basis;
					return fromAndTo;
				}
			}
		}
		return null;
	}
	
	void swapChar(char[] s, int i, int j) {
		char temp = s[i];
		s[i] = s[j];
		s[j] = temp;
	}
	
	/**反转。把字符数组从from位置开始后的数反转*/
	void revertStr(char[] str, int from){
		if(str.length - from <= 1){
			return;
		}
		for(int i = from; i < (str.length + from)/2;i++){
			swapChar(str,i,str.length - 1 -(i - from));
		}
	}
	
	public static void main(String[] args) {
		StrRemove s1 = new StrRemove();
		String waitList1 = "123";
		String waitList2 = "1223";
		
		s1.allSequence(waitList1.toCharArray());
		System.out.println("-------------------------------------");
		s1.allSequence(waitList2.toCharArray());
	}

注:以上代码是放在类StrRemove中,所有StrRemove s1 = new StrRemove()来准备调用方法


最终结果:

123
132
213
231
312
321
-------------------------------------
1223
1232
1322
2213
2231
2312
2321
3122
3221

算法优缺点

相对于递归实现的全排序算法稍微复杂些,但是深度不大。非递归实现的恰好免疫有重复序列的全排序,不会重复;例如代码中测试的1223序列,不会产生多余的全排序



另一实现:字符串全排列-递归实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值