全排列算法超简单实例分析-非递归

1.简介

说起全排列,想必大家都不会陌生。而全排列的生成算法,也一直是各类 OJ 网站常见的题目,可见其经典程度有多高。为了快速掌握、复习算法思想,节省时间。现推出各种经典算法的实例分析。本次带来的是全排列生成算法。


2.非递归算法

在 C++ STL 中,通过包含 algorithm 头文件,可以使用 std::next_permutation  和 std::prev_permutation 算法。

这两个算法,输入一对迭代器 [first,last) 标记的范围。假设输入数组[1,2,3,4], std::next_permutation 求得该数组的下一个字典序排列即[1,2,4,3],并且返回ture.

因此,求一个序列的全排列,可以使用如下 C++ 代码:

#include<iostream>
#include<algorithm>
#include<vector>
int main()
{
    vector<int> vec{1,2,3};
    do
    {
	for(auto elem:vec)
            std::cout<<elem<<' ';
	std::cout<<std::endl;	
    }while(std::next_permutation(vec.begin(),vec.end()));
    return 0;
}
求得结果如下:

1 2 3 
1 3 2 
2 1 3 
2 3 1 
3 1 2 
3 2 1


通过这种方法求全排,关键点就是:给定一个序列,例如[1,3,2],如何找到它的下一个字典序排列?


从上述代码的运行结果可以看到:

①没有相邻逆序对的序列,在字典序中排在最前。例如[1,2,3]中,(1,2) 和 (2,3)都是顺序的。

②没有相邻顺序对的序列,在字典序中排在最后。例如[3,2,1]中,(3,2) 和 (2,1)都是逆序的。

利用这两个结论,可以帮我们找到指定序列的下一个字典序。

来看[1,3,2]这个序列。我们看到子序列[3,2]是逆序的,使用结论②可以得出这个子序列排在最后。因此,这是以1开头的最后一个序列。那么排在[1,3,2]之后的序列,必然是以2开头,并且接下来的子序列是顺序的。可以得出是[2,1,3]。


接着,形式化一下这种方法。有序列S = [n0,n1,n2,...n(i-1),ni,nj,...,nk],记序列L = [n0,n1,n2,...,n(i-1)],序列R = [nj,...,nk].假设ni < nj,并且子序列R是逆序的。

此时,R没有任何相邻数对是顺序的,由结论②可得序列S是以[n0,n1,...,ni]开头的最后一个序列。

序列[ni,nj,...,nk]还不是逆序,通过变化必然可以得到下一个字典序。所以,构造序列S的下一个字典序列时,必须得保持L不变然后加上[ni,nj,...,nk]的下一个字典序。 举例来说,当序列S = [1,4,3,5,2],L = [1,4],R=[5,2].序列[3,5,2]并不是逆序,所以还不能改变L,只能改变序列[3,5,2]来得到S的下一个字典序。


那么序列M = [ni,nj,...,nk]的下一个字典序什么呢?可以看到,这个序列是以 ni 开头的最后一个序列,那么下一字典序如何构造呢?下一个字典序的第一位,一定是大于ni的最小值。由于R逆序且ni < nj,所以这个值一定存在于序列R中。假设这个大于ni的最小值为nx,那么只要将剩下的数按顺序排在后面。就一定可以得到以 nx开头的第一个字典序。


这种按字典序求下一个排列的方法,已经由C++ STL 库函数 std::next_permutation实现

link(http://en.cppreference.com/w/cpp/algorithm/next_permutation)

代码如下:

template<class BidirIt>
bool next_permutation(BidirIt first, BidirIt last)
{
    if (first == last) return false;
    BidirIt i = last;
    if (first == --i) return false;
 
    while (true) {
        BidirIt i1, i2;
 
        i1 = i;
        if (*--i < *i1) {	 //从后边起,寻找第一个顺序对(i,i1)
            i2 = last;
            while (!(*i < *--i2))//从[il1,last)找到大于i的最小值i2
                ;
            std::iter_swap(i, i2);
            std::reverse(i1, last);//交换 i,i2 后,[i1,last)仍然是逆序,所以这里要 reverse 处理
            return true;
        }
        if (i == first) {
            std::reverse(first, last);
            return false;
        }
    }
}

在leetcode中,可以找到 permutation 这个问题进行练习。可以看到,当前运行最快的算法,是递归版的。看来,递归版的算法,确实值得研究。

3.总结

求一个序列的下一个字典序排列,首先,最重要的点就是记住顺序和逆序这两种序列的特点;然后,当一个序列包含逆序的子序列时,就可以很方便地构造下一个字典序排列。

在实现算法时,也要充分利用已有的信息。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值