全排列

思想来自于《离散数学及其应用》,我只是结合自己的经历,再复述一遍,稍加改进。

先来个插叙,我刚开始学编程时我就想过要写这样一个列出一个序列的全排列的程序。当年,经过了“千辛万苦”,终于完成了功能,很高兴。

但过了这些年之后,再看当年的程序,惨不忍睹啊!不通用,效率低,可维护性差……我就想了,当年我这么二啊!

当然,有这样的感觉说明我进步了,谁没有二过?下面回归正题,说一下现学的我将要现卖的方法。

如果我们注意观察,会发现如果按字典序(这也是后边所有叙述的假设)一个序列的全排列中各个条目之间是有大小关系的,这就为我们枚举全排列提供了一个思路:给出一种排列,求出紧临这个排列的下一个排列。如果我们给出最小的那一种排列,重复调用上述方法,就可以得到全排列。

用通用的语言描述一下吧:

设给出的序列为a[1],a[2],……,a[n]。从后向前扫描,假设a[n]小于a[n+1],则交换两个元素,则可以得到下一个序列(正确性想想就能过去,不证明了);如果a[n]不小于a[n+1],继续向前找,这里又可以分出两种情况:1、一直找不到使得a[x]<a[x+1]的位置,则可判定序列递减,这就是最大的排列,算法可以结束了;2、如果能找到某一位置,使得a[x]<a[x+1]成立,则在序列a[x]…a[n]中找到不小于a[x]的元素里最小的那一个a[k],交换a[k]和a[x],再把a[k]…a[n]升序排列,就可以得到下一种排列了。

举个例子吧,序列为{a, b, c, d},找出一下3个序列。

从后向前扫描,第3个元素c小于第4个元素d,则交换两个元素,得到序列’abdc’。

再下一步,a[3]a[4]为递减,则看a[2]与a[3]的关系,a[2]<a[3],则在a[3]a[4]中找到比a[2]大的元素里最小的那个,就是a[4],交换两元素,现在的序列是’acdb’,现在把a[3]a[4]逆序,得到’acbd’。

……

《离散数学及其应用》中给出的伪代码如下:

 

procedure next permutation(a[1]a[2]…a[n]:{1,2, 3, … , n}的排列,不等于n, n-1, … 2, 1)

j:=n-1;

while a[j]>a[j+1]

         j:=j-1

{j是使得a[j]<a[j+1]的最大下标}

k:=n

while a[j]>a[k]

         k:=k-1

{a[k]是在a[j]右边大于a[j]的最小整数}

swap(a[j], a[k])

r:=n

s:=j+1

while r>s

begin

         swap(a[r],a[s])

         r:=r-1

         s:=s+1

end

{这把在第j位后边的排列尾部逆序}

 

 

伪码是用类pascal语言写的,我翻译成了对应的C/C++语言形式:

 

 

template<class T>

bool FullArray(T* a, unsigned size)
{
         int j; //使a[j]<a[j+1]的最大坐标
         for (j= size - 2 ; j >= 0 && a[j] > a[j+1]; j--)
         {       }
         //已经是最大的排列
         if(j< 0) return false;
         //查找比a[j]大的最小的那个数
         int k =size - 1;
         for(k =size - 1 ; a[j] > a[k] ; --k){}
         //交换元素
         auto tmp = a[j];
         a[j] = a[k];
         a[k] = tmp;
         //翻转a从j + 1到sizef - 1的序列
         int l =j + 1, r = size - 1;
         while(l< r)
         {
                   tmp = a[l];
                   a[l] = a[r];
                   a[r] = tmp;
                   ++l;
                   --r;
         }
         return true;
}

 

 

注:代码中用到了C++11标准新定义的关键字auto,在这里可以换成T。

 

 

可以用以下的方式进行测试:

 

       int a[]= {1, 2, 3, 4};

         do
         {
                   for(int i = 0 ; i <sizeof(a)/sizeof(a[0]); i++)
                  {
                            cout<<a[i];
                   }
                   cout<<endl;
         }while(FullArray(a,sizeof(a)/sizeof(a[0])));

 

 

 

但是,如果在序列中存在重复元素,此函数会出现错误。修改如下:

template<class T>
bool FullArray(T* a, unsigned size)
{
         int j; //使a[j]<a[j+1]的最大坐标
         for (j= size - 2 ; j >= 0 && a[j] >= a[j+1] ; j--)
         {       }
         //已经是最大的排列
         if(j< 0) return false;
         //查找比a[j]大的最小的那个数
         int k =size - 1;
         for(k =size - 1 ; a[j] >= a[k] ; --k){}
         //交换元素
         auto tmp = a[j];
         a[j] = a[k];
         a[k] = tmp;
         //翻转a从j + 1到sizef - 1的序列
         int l =j + 1, r = size - 1;
         while(l< r)
         {
                   tmp = a[l];
                   a[l] = a[r];
                   a[r] = tmp;
                   ++l;
                   --r;
         }
         return true;
}

 

这样可以应对所有情况。

 

如果序列中的元素不存在大小关系(如数学题中常的红球黄球等),则可以对应序列建立一个同样大小的整数数组,初始化为一个递增序列,再把这个序列传入函数。但也要注意在原始序列中有重复元素的处理。

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值