STL源码——排列生成算法(next-permutation、pre-permutation)

排列生成算法有三种:序数法、字典序法、换位法。本文只讨论前两种较常用的方法。

一.序数法

所谓序数,指的是某个排列在这n的数的所有排列中按字典序排序的序数。已知一个排列,可以用康托展开求其序数。假设排列为《STL源码——排列生成算法(next-permutation、pre-permutation)》,这个排列对应的序数为《STL源码——排列生成算法(next-permutation、pre-permutation)》。其中《STL源码——排列生成算法(next-permutation、pre-permutation)》表示位于第i位右边比《STL源码——排列生成算法(next-permutation、pre-permutation)》小的元素的个数,《STL源码——排列生成算法(next-permutation、pre-permutation)》

排列组合意义上理解:设当前排列《STL源码——排列生成算法(next-permutation、pre-permutation)》,考察第《STL源码——排列生成算法(next-permutation、pre-permutation)》《STL源码——排列生成算法(next-permutation、pre-permutation)》,第《STL源码——排列生成算法(next-permutation、pre-permutation)》位比《STL源码——排列生成算法(next-permutation、pre-permutation)》小的排列的《STL源码——排列生成算法(next-permutation、pre-permutation)》一定比《STL源码——排列生成算法(next-permutation、pre-permutation)》小,第《STL源码——排列生成算法(next-permutation、pre-permutation)》位有《STL源码——排列生成算法(next-permutation、pre-permutation)》中情况,后面的《STL源码——排列生成算法(next-permutation、pre-permutation)》位的情况为《STL源码——排列生成算法(next-permutation、pre-permutation)》,所以在《STL源码——排列生成算法(next-permutation、pre-permutation)》位比排列《STL源码——排列生成算法(next-permutation、pre-permutation)》小的排列有《STL源码——排列生成算法(next-permutation、pre-permutation)》种情况。对每一位都进行考虑得到的就是以上的公式。代码如下:(数组的下标从《STL源码——排列生成算法(next-permutation、pre-permutation)》,与定义中相反,处理时换成《STL源码——排列生成算法(next-permutation、pre-permutation)》即可)

long cantor(int s[], int n){
    int res = 0;
    for(int i = 1; i < n; ++i){
        for(int j = i + 1; j <= n; ++j)
            if(s[j] < s[i])
                ++cnt;
        res += fac[n - i] * cnt;   //fac为预处理阶乘
    }
    return res + 1;
}

逆展开:已知排列序数,求排列。

设排列序数为《STL源码——排列生成算法(next-permutation、pre-permutation)》,则由原公式可得:《STL源码——排列生成算法(next-permutation、pre-permutation)》《STL源码——排列生成算法(next-permutation、pre-permutation)》,以此类推求解到《STL源码——排列生成算法(next-permutation、pre-permutation)》.

应用:康托展开可以用于状态压缩,使用序数来表示当前状态所构成的排列),大大节约空间。

二.字典序法

字典序法用于求解当前排列的下一个排列。假设当前排列为《STL源码——排列生成算法(next-permutation、pre-permutation)》,步骤如下:

1.求满足《STL源码——排列生成算法(next-permutation、pre-permutation)》《STL源码——排列生成算法(next-permutation、pre-permutation)》的最大值,设为《STL源码——排列生成算法(next-permutation、pre-permutation)》,即:《STL源码——排列生成算法(next-permutation、pre-permutation)》,则《STL源码——排列生成算法(next-permutation、pre-permutation)》之后是一个降序序列。

2.求满足《STL源码——排列生成算法(next-permutation、pre-permutation)》《STL源码——排列生成算法(next-permutation、pre-permutation)》的最大值,设为《STL源码——排列生成算法(next-permutation、pre-permutation)》,即:《STL源码——排列生成算法(next-permutation、pre-permutation)》,则《STL源码——排列生成算法(next-permutation、pre-permutation)》为大于《STL源码——排列生成算法(next-permutation、pre-permutation)》的最小值

3.《STL源码——排列生成算法(next-permutation、pre-permutation)》互换,将《STL源码——排列生成算法(next-permutation、pre-permutation)》中的元素逆转。即得到下一个排列。

上述过程简单说来,就是寻找一个降序结构(在尾部),记住这个子序列之前的那个元素的值《STL源码——排列生成算法(next-permutation、pre-permutation)》,然后在子序列中找最后一个比《STL源码——排列生成算法(next-permutation、pre-permutation)》大的值《STL源码——排列生成算法(next-permutation、pre-permutation)》《STL源码——排列生成算法(next-permutation、pre-permutation)》是大于《STL源码——排列生成算法(next-permutation、pre-permutation)》的最小值),交换《STL源码——排列生成算法(next-permutation、pre-permutation)》《STL源码——排列生成算法(next-permutation、pre-permutation)》,整个子序列仍然是降序的,再将这个降序结构逆转重排。

证明如下:

设当前排列《STL源码——排列生成算法(next-permutation、pre-permutation)》,按上述方法得到的下一个排列为《STL源码——排列生成算法(next-permutation、pre-permutation)》。按康托展开求解这两个排列的序数。

《STL源码——排列生成算法(next-permutation、pre-permutation)》

由于第《STL源码——排列生成算法(next-permutation、pre-permutation)》位以前二者相同,所以只考虑《STL源码——排列生成算法(next-permutation、pre-permutation)》位,为简单起见,把《STL源码——排列生成算法(next-permutation、pre-permutation)》当成第一位,设长度为《STL源码——排列生成算法(next-permutation、pre-permutation)》。则根据康托展开的公式,

《STL源码——排列生成算法(next-permutation、pre-permutation)》,由于《STL源码——排列生成算法(next-permutation、pre-permutation)》,可得:

《STL源码——排列生成算法(next-permutation、pre-permutation)》,化简得:《STL源码——排列生成算法(next-permutation、pre-permutation)》

又:《STL源码——排列生成算法(next-permutation、pre-permutation)》,所以《STL源码——排列生成算法(next-permutation、pre-permutation)》,即:《STL源码——排列生成算法(next-permutation、pre-permutation)》《STL源码——排列生成算法(next-permutation、pre-permutation)》的下一个排列。证明完毕。

三.STL next-permutaion

STL中的next-permutaion算法采用上面的字典序法生成下一个排列,代码如下:

template<class BidirectionalIterator>
bool next_permutation(BidirectionalIterator first, BidirectionalIterator last){
    if(first == last)
        return false;
    BidirectionalIterator i = first;
    ++i;
    if(i == last)      //只有一个元素
        return false;
    i = last;
    --i;               //最后一个元素
    for(;;){
        BidirectionalIterator ii = i;
        --i;
        if(*i < *ii){  //找到最后一组相邻元素:a[i] < a[i + 1],a[ii]起降序排列
            BidirectionalIterator j = last;
            while(!(*i < *(--j));  //找最后一个大于a[i]的元素,则a[j]为大于a[i]中的最小数
            iter_swap(i, j);       //交换i, j
            reverse(ii, last);     //将ii后面的元素逆向重排
            return true;
        }
        if(i == first){            //整个区间内元素是降序排列的
            reverse(first, last);  //全部逆向重排
            return false;          //当前排列已经是最大排列
        }
    }
}

pre-permutaion则是按照字典序法相反地进行。如下图所示:

《STL源码——排列生成算法(next-permutation、pre-permutation)》

源码如下:

template<class BidirectionalIterator>
bool pre_permutation(BidirectionalIterator first, BidirectionalIterator last){
    if(first == last)
        return false;
    BidirectionalIterator i = first;
    ++i;
    if(i == last)      //只有一个元素
        return false;
    i = last;
    --i;               //最后一个元素
    for(;;){
        BidirectionalIterator ii = i;
        --i;
        if(*i > *ii){  //找到最后一组相邻元素:a[i] > a[i + 1]
            BidirectionalIterator j = last;
            while(!(*--j < *i));  //找最后一个小于a[i]的元素,则a[j]为小于a[i]中的最大数
            iter_swap(i, j);       //交换i, j
            reverse(ii, last);     //将ii后面的元素逆向重排
            return true;
        }
        if(i == first){            //整个区间内元素是降序排列的
            reverse(first, last);  //全部逆向重排
            return false;          //当前排列已经是最大排列
        }
    }
}

点赞