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

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

一.序数法

所谓序数,指的是某个排列在这n的数的所有排列中按字典序排序的序数。已知一个排列,可以用康托展开求其序数。假设排列为,这个排列对应的序数为。其中表示位于第i位右边比小的元素的个数,

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

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;
}

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

设排列序数为,则由原公式可得:,以此类推求解到.

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

二.字典序法

字典序法用于求解当前排列的下一个排列。假设当前排列为,步骤如下:

1.求满足的最大值,设为,即:,则之后是一个降序序列。

2.求满足的最大值,设为,即:,则为大于的最小值

3.互换,将中的元素逆转。即得到下一个排列。

上述过程简单说来,就是寻找一个降序结构(在尾部),记住这个子序列之前的那个元素的值,然后在子序列中找最后一个比大的值是大于的最小值),交换,整个子序列仍然是降序的,再将这个降序结构逆转重排。

证明如下:

设当前排列,按上述方法得到的下一个排列为。按康托展开求解这两个排列的序数。

STL源码——排列生成算法(next-permutation、pre-permutation)_第1张图片

由于第位以前二者相同,所以只考虑位,为简单起见,把当成第一位,设长度为。则根据康托展开的公式,

,由于,可得:

,化简得:

又:,所以,即:的下一个排列。证明完毕。

三.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)_第2张图片

源码如下:

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;          //当前排列已经是最大排列
        }
    }
}

你可能感兴趣的:(算法,STL)