校OJ 17086 字典序的全排列

博客探讨了如何使用常规方法求解字典序的下一个全排列,强调理解next_permutation的实现思路,并引用了算法竞赛入门经典中的相关章节作为参考。

显然,直接用STL中的next_permutation(p,p+n)可以很快速得出下一个排列(见算法竞赛入门经典(第二版) P187)


但这道题的重点是理解怎么求下一个排列的方法,


下面是常规方法求解思路


设P是一个排列串:P = s[1]s[2]...s[n] = s[1]...s[j]...s[k]...s[n]

1)从当前排列串的右端开始,找出第一个比右边数字小的数字的序号j
(j从左端开始计算),即 j=max{i|s[i]<s[i+1]};

2)在s[j]的右边的数字中,找出所有比s[j]大的数中最小的数字s[k],
即 k=max{i|s[i]>s[j]}
(对s[j]右边数来说,从右至左是递增的,因此k是所有大于s[j]的数字中序号最大者,
序号虽最大但s[k]是处于s[j]右边且比s[j]大的数中的最小者);

3)交换s[j]和s[k];

4)再将s[j+1]...s[k-1]s[k]s[k+1]...s[n]倒转得到排列
 P' = s[1]s[2]...s[j]s[n]...s[k+1]s[k]s[k-1]...s[j+1],
这里P'就是排列P的下一个排列。

简单来说,就是对于给定的一个数,首先从右边找到第一个相邻“有序对”(这个“有序对”
的定义是就是满足s[i]<s[i+1]最右边对)。
现假设下一个要找的数比这个数大,且中间没有一个数比前者大、比后者小。然后再重新从
右边起找出第一个比那个"有序对"的较小者要大的数,交换他们,再将那个较小数下标后面的
子数组反转。

总结一下:就是由后向前找替换数和替换点,然后由后向前找第一个比替换数大的数与替换数
交换,最后颠倒替换点后的所有数据。

刚开始不是很懂,现在大致了解了,但还有些细节不是很能理解,慢慢来吧~~

我的解释:
1.从后向前找相邻的“有序对”,即a[i]<a[i+1],记录p=i;
2.从p之后找比p大中的最小值下标q,
3.交换p,q值
4.把p之后的数倒置

#include <iostream>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <cstdlib>
#include <cstdio>
int n;
int a[20];
using namespace std;
bool next()
{
    int p;
    bool change=false;
    for(int i=n-2; i>=0; i--)       //第一步,找p点
    {
        if(a[i]<a[i+1])
        {
            p=i;
            change=true;
            break;
        }
    }
    //cout<<"p: "<<p<<endl;
    if(!change)          //如果没有交换,表示已经是最大值了,退出判断
    {
        //cout<<change<<endl;
        return change;
    }
    int cmax=a[p+1],q=p+1;
    for(int i=p+2; i<n; i++) //第二步,找q点
    {
        if(cmax>a[i]&&a[i]>a[p])
        {
            cmax=a[i];
            q=i;
        }
    }
    // cout<<"q: "<<q<<endl;
    int temp=a[p];        //第三步,交换p,q
    a[p]=a[q];
    a[q]=temp;
    for(int i=p+1,j=n-1; i<j; i++,j--)     //第四步,倒置p点之后的数
    {
        int temp=a[i];
        a[i]=a[j];
        a[j]=temp;
    }
    //cout<<change<<endl;
    return change;
}

int main()
{
    char s[20];
    int p=1;
    cin>>n;
    for(int i=0; i<n; i++)
    {
        cin>>s[i];
        a[i]=s[i]-'0';
    }
    sort(a,a+n);
    do
    {
        cout<<p<<" ";
        for(int i=0; i<n; i++)
            cout<<a[i];
        cout<<endl;
        p++;
    }
    while(next());
    return 0;

}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值