关于字典序法实现全排列

输出若干字符或数码的全排列,是计算机编程语言应用中的一个非常经典的问题。全排列问题既有递归的解法,也有非递归的解法。本文主要对全排列的非递归解法-也就是字典序法作一些简单的说明。

字典序法的基本思路

字典序法的基本原理是对于特定的一些字符或者数码,不同的排列之间可以类比数字进行大小的比较。例如,考虑字母 ‘a’ 到 ‘d’ 生成的排列,我们规定字母从 ‘a’ 到 ‘d’ 依次递增,且排列的最左边为最高位,那么 ‘dcba’ 就大于’abcd’。这和我们比较两个相同位数的十进制整数是类似的,都是从最高位开始比较,若其中一个数在在该位上的数码(0~9)更大,那么这个数就更大;若相同,则往右比较下一位。
这样所有的排列就有了大小次序关系。如果可以找到一种方法,对任一排列,都可找到其他所有排列中比它大的最小的排列,那么我们就可以从最小的排列出发,依次找出所有的排列。字典序法就是这样的一种方法。

字典序法的步骤

接下来先描述一下字典序法的基本步骤,然后再来探讨一下为什么字典序法的步骤是这样的。

问题:假设有n个字符(或者0到9中的数码),c1,c2,…,cn,输出其所有的排列。

方法步骤:
(1) 找到所有排列中最小的,即c1c2…cn(这里我们规定c1<c2<···cn),输出这一排列;
(2) 从右往左搜索,找到第一个ci,使其满足 ci>ci+1>···cn 且ci>ci-1(特殊情况下ci是最后一个字符,此时只需要满足ci>ci-1);
(3) 从ci,ci+1,…,cn中找到最小的比ci-1大的ck,将ck与ci-1交换位置,再对ck之后的字符按从大到小进行排序,输出此时的排列;
(4) 反复执行(2)和(3),直到找不到符合(2)中要求的字符,结束。

字典序法的原理

网上有许多资料对字典序的步骤作了详细的描述,但很少有对其原理作进一步的说明。这里简单地讨论一下字典序法各个步骤背后的原理。
我们知道,对于字符或者数码的全排列,组成每一排列的字符或者数码都是相同的,不同的仅仅是它们在排列中的位置。对于某一排列,我们要找到其他排列中比它大的最小的排列,本质上是对这一排列的某些字符或或者数码进行位置的调整。注意是某些而不是全部,这是因为我们要使原排列的大小作最小的增加。
再次类比十进制整数,如果我们希望通过改变一个整数不同位上数码的位置来使它产生最小的增大,我们肯定要尽量减少高位数码的变动,而调整低位数码的位置。因此,实际上调整位置的数码仅限于后m位,m是小于n的正整数。
那么,这个m应该取多少呢?自然,m应该越小越好,这样高位的变动就越小。为什么步骤(2)中要找这样一个“极大值点” ci 呢?这是因为后m位不能是严格递减序列,否则仅仅通过调整这m位字符或者数码的位置,原排列的大小只会变小而不会增大。所以要找到最小的m使得后m个字符或者数码是非严格递减的,ci-1,ci,…,cn即满足这一要求,这就是步骤(2)的目的。
找到了这后m个字符或者数码,那么又该如何调整呢?显然,我们首先要保证这个m各字符的最高位,也就是 ci-1 的所在的位发生最小的改变(增大)。由于仅仅是位置上的调整,我们自然要从 ci 到 cn 中选择最小的但又比 ci-1 大的字符或者数码与ci-1进行交换。之后,我们还要使得后(m-1)位最小。那么,接下来的工作就是对后(m-1)位进行从小到大的排序。这些就是步骤(3)的任务。
之后,反复执行步骤(2)和(3),直到步骤(2)无法进行,我们就遍历了所有的排列。

最后提供一段字典序法实现数码1~m(m<=9)全排列的简单代码,供大家参考。

#include <iostream>
void PermNR(int m)
{   
    //建立全排列存储数组
	int* arr=new int[m];
	//数组初始化,从1到m升序排列
	for(int i=0;i<m;++i)
		arr[i]=i+1;
	//采用字典序方法生成1到m的全排列
	//输出全升序排列
	for(int i=0;i<m;++i)
		std::cout<<arr[i];
	std::cout<<std::endl;
	//建立标志变量
	int tar;
	int cur;
	int temp;
	while(true)
	{
		cur=m-1;
		//从数组最右端往左找到第一个极大值点
		while(cur>=1&&arr[cur-1]>arr[cur])
			--cur;
		if(cur>=1)
		{
			tar=cur;
			while(tar<m&&arr[tar]>arr[cur-1])
				++tar;
			--tar;
			//将极大值点前一个元素值和极大值点起最小的大于它的值作交换
			temp=arr[tar];
			arr[tar]=arr[cur-1];
			arr[cur-1]=temp;
		    //从极大值点位置开始往右按从小到大排序
			for(int i=cur;i<m-1;++i)
				for(int j=cur;j<m+cur-1-i;++j)
					if(arr[j+1]<arr[j])
					{
						temp=arr[j+1];
						arr[j+1]=arr[j];
						arr[j]=temp;
					}
			//输出每一排列结果
			for(int i=0;i<m;++i)
				std::cout<<arr[i];
			std::cout<<std::endl;
		}
		else
			break;
	}

}
	
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值