• 全部
  • 问答

一道竞赛题,害死我的三道题之一

LeeMaRS 2002-06-10 10:53:36
请看以下排列
abc
acb
bac
bca
cab
cba
这些排列都是按字典顺序生成的.我们给它们按顺序编号:
abc 0
acb 1
bac 2
bca 3
cab 4
cba 5

以上只是这个问题的一种情况.要知道,这个排列最大是由a-z共26个字母不重复的组成的.长度则是给定的.比如n=3的时候,就是我刚才说的情况.
好了,在我从键盘输入N后,使用a-z共26个字母的前N个组成不含重复字母的排列.请看以下问题:
(1)现在给你一个排列.请你求出他的序号.
(2)给你序号,请你求出这个排列.

----------------------------------------
饮恨第一题.请大家帮助解决,不然我是死不瞑目.....
...全文
16 点赞 收藏 37
写回复
37 条回复
切换为时间正序
当前发帖距今超过3年,不再开放新的回复
发表回复
atlantis13579 2002-06-20
接着上面那篇
该算法的原理分析:

初始化字母表(体现在上面算法1,2步)
n个字母的全排列的数目为n!
因此,把所有字母按字典序全排列,可分为n段
以前25!个以a开头,(25!+1)到2*25!的以b开头....
因此通过n除25!可知道第一个字母(体现在上面算法3,4步,和p)
知道第一个字母后,剩下的串肯定不含该字母,把它从字母表中除去(体现在上面算法5步)
同样,以该字母开头的单词中还可分段,由它的偏移决定(体现在上面算法中的r)
重复该步骤以决定整个排列(体现在上面算法6,7步)

算法的实质就是:
把小于n!的数表示成a(n-1)*(n-1)!+a(n-2)*(n-2)!+....+a1的形式,其中ai<=i;
回复
atlantis13579 2002-06-20
只讲算法,不写程序了:)

给出n

1.a[26]={a,b,.....,z}(下标从0开始算);
2.m=26
3.用(m-1)!整除n,所得的商为p,余数为r(即n=p*(m-1)!+r)
4.输出a[p];
5.把数组a中p+1到m的数移前一位(a[p+1]=a[p+2],.....);
6.m=m-1;n=r;
7.如果m>0,重复3
8.至此,已输出了这个字符串
回复
zgy231552 2002-06-19
有道理..
回复
zyzyis 2002-06-19
就是个全排列问题嘛
回复
sclzmbie 2002-06-19
HASH ?
回复
eion 2002-06-18


算法都有现成的了,你自己编写一些什么+,- ×,%的代码替代不就OK了?


回复
sarage 2002-06-18
我想是一个权值问题。
回复
SimonDW 2002-06-17
排列:
char(position / p(n-1,n-1)+1)
char((position % p(n-1,n-1))/p(n-2,n-2)+1)
回复
SimonDW 2002-06-17
排列:
char(position / p(n-1,n-1))
char((position % p(n-1,n-1))/p(n-2,n-2))
...............................
序号:
(asc(str[0])-1)*p(n-1,n-1)+(asc(str[1])-1)*p(n-2,n-2)+.......

可能有错……
仅供参考……
回复
myc 2002-06-17
gz
回复
LeeMaRS 2002-06-16
to imagex(明月双) :
第二问的序号最长有27位 你这样一个一个生成 肯定超时.
回复
imagex 2002-06-15
#include "stdio.h"
#include "iostream.h"

void func(int n,int * a);

void main(void)
{
int arr[]={1,2,3,4};

func(4,arr);
getchar();
}

//求n个数的全排列
//
void func(int n,int * a) //an array a[n]
{
static int Num =0;
int *i=new int[n];
int c=0,j;
i[c]=-1;
while(i[0]<n)
{
if(++i[c]<n)
{
for(j=0; j<c ;j++)
if( i[j] == i[c])
break;

if(j==c)
{
if(c+1<n)
{
++c;
i[c]=-1;
// continue;
}
else
{
cout.width(2);
cout<<Num++<< " ";
for(int j=0;j<n;j++)
cout<<a[i[j]]<<",";
cout<<endl;
}
}
}
else
c--;
}

}
回复
fsht_aa 2002-06-14
GZ
回复
LeeMaRS 2002-06-14
顺便再提醒一下.第二个问题的序号最长可以有27位数字.
回复
widewave 2002-06-14
仔细观察一下,那些数据是分段的.
分段来求,OK?
回复
eion 2002-06-13
运行结果为:

Source arrange is : 5 1 3 4 2
at site: 99
site = 99 is arr: 5 1 3 4 2
回复
eion 2002-06-13
注意算法的时间复杂度大概为 O(n^2)————特别低的

代码提取出来就是

#include <stdio.h>
int factorial(int n) //计算 阶乘 n!
{
int res = 1;
for(int i=2;i<=n;i++) res *=i;
return res;
}
int Reverse(int n, int *parr) // arr[0]逆序数
{
int cnt = 0;
for(int i=1;i<n;i++) if( parr[i]<parr[0] ) cnt++;
return cnt;
}
int array_site(int n, int* parr) // 求位置
{
if( n==1 ) return 0;
//else
return Reverse(n,parr)*factorial(n-1) + array_site(n-1,parr+1);
}
void swapn(int n,int* parr)
{
int tmp = parr[n];
for(int i=n;i>0;i--) parr[i] = parr[i-1];
parr[0] = tmp;
}

void re_array_site(int site, int pos, int* parr,int n) //知道位置求排列
{
if( pos==n ) return;

int mod = factorial(n-pos-1); //
int rev = site/mod; // 求 当前的逆序数
int newsite = site%mod; // 求 剩余数列的位置
swapn(rev,parr+pos); // 将 第rev个数提到最前面, re_array_site(newsite,pos+1,parr,n);
}

void main()
{
int n=5;
int arr[N] = {5,1,3,4,2};
int site = array_site(n,arr);
printf("Source arrange is : \t");;
for( int i=0;i<n;i++) printf("%3d ",arr[i]);
printf("\nat site: %d\n",site);

for( i=0;i<n;i++) arr[i] = i+1; // 重置排列为自然排列

printf(" site = %d is arr: \t",site);
re_array_site(site,0,arr,n);
for( i=0;i<n;i++) printf("%3d ",arr[i]);
printf("\n");
}
回复
eion 2002-06-13
不对,看我的分析:

他们应该与数的表示有关

在小学,我们学过十进制;
在计算机课程中,我们学过二进制,十六进制及N进制

还有一种计数方式,就是任意一个自然数数可以表示为:

N = a1*1! + a2*2! + a3*3! + ... + an*n!

其中 a1,a2,...,an 为自然数,且满足:
a1 <= 1;
a2 <= 2;
a3 <= 3;
...
an <= n;

而排列好像就是用了这种计数方式
任意给定一种顺序: a1 a2 ... an, 我们称之为自然顺序,现在有一个排列
ap1 ap2 ... apn, 其中p1,p2,...,pn为1,2,...n的一个排列,那么首先对ap1
显然,ap1,a1 ,a2, ... , ap1-1, ap1+1, ... , an这个排列所在地位置为:
(ap1-1)*(n-1)!【为什么?自己慢慢看】,ap1-1表示ap1后比ap1小的数的个数,
也就是逆序数

现在,去掉ap1, 剩下的ap2,ap3,...,apn是a1,a2,...,ap1-1,ap1+1,...,an
的一个排列,那么,ap2, a1, a2,... ap1-1,ap1+1,...,ap2-1,ap2+1,...,an
所在地位置就是在ap1,a1 ,a2, ... , ap1-1, ap1+1, ... , an后的位置为:
如果ap1>ap2,位置为 (ap2-1)*(n-2)!,也就是ap2逆序数的个数
如果ap1<ap2,位置为 (ap2-2)*(n-2)!,也是 ap2逆序数的个数

所以,位置就是
sigma( i=0, i<n, T(api)*(n-i)! )
sigma( i=0, i<n, ... ) 表示对所有满足 0<= i < n 条件求和
T(api)表示api的逆序数

所以算法就是:

#include <stdio.h>
int factorial(int n) //计算 阶乘 n!
{
int res = 1;
for(int i=2;i<=n;i++) res *=i;
return res;
}

// ##############+. Descripted By eion(那个谁)[2002-6-13 10:40:23] .+##########
// | 函数名 : Reverse ( )
// | 描述 : 计算 parr[0] 后比parr[0]小的个数,即parr[0]的逆序数
// | 注意不是排列的逆序数
// +---------------------------------------------------------------------------+
// | 返回类型 : int 逆序数的个数
// + 参数 :
// | [ int ] - n 计算的长度
// | [ int ] - *parr 排列
int Reverse(int n, int *parr) // arr[0]逆序数
// +---------------------------------------------------------------------------+
{
int cnt = 0;
for(int i=1;i<n;i++) if( parr[i]<parr[0] ) cnt++;
return cnt;
}

// ##############+. Descripted By eion(那个谁)[2002-6-13 10:43:05] .+##########
// | 函数名 : array_site ( )
// | 描述 : 求排列的位置,自然序列从 0 开始
// +---------------------------------------------------------------------------+
// | 返回类型 : int
// + 参数 :
// | [ int ] - n 排列的个数
// | [ int* ] - parr 排列
int array_site(int n, int* parr) // 求位置
// +---------------------------------------------------------------------------+
{
if( n==1 ) return 0;
//else
return Reverse(n,parr)*factorial(n-1) + array_site(n-1,parr+1);
}

// ##############+. Descripted By eion(那个谁)[2002-6-13 10:44:45] .+##########
// | 函数名 : swapn ( )
// | 描述 : 将第n个数提到最数组前面去,其他顺序不变
// +---------------------------------------------------------------------------+
// | 返回类型 : void
// + 参数 :
// | [ int* ] - parr 位置排列
// | [ int ] - n 位置个数
void swapn(int n,int* parr)
// +---------------------------------------------------------------------------+
{
int tmp = parr[n];
for(int i=n;i>0;i--) parr[i] = parr[i-1];
parr[0] = tmp;
}

// ##############+. Descripted By eion(那个谁)[2002-6-13 10:44:55] .+##########
// | 函数名 : re_array_site ( )
// | 描述 : 将位置为site的排列存储在parr数组上,长度为n,pos为调用时位置控制,设定为0
// +---------------------------------------------------------------------------+
// | 返回类型 : void
// + 参数 :
// | [ int ] - site 在所以排列中的位置
// | [ int ] - pos 排列中的起始位置
// | [ int* ] - parr 排列存放地
// | [ int ] - n 维数
void re_array_site(int site, int pos, int* parr,int n) //知道位置求排列
// +---------------------------------------------------------------------------+
{
if( pos==n ) return;

int mod = factorial(n-pos-1); //
int rev = site/mod; // 求 当前的逆序数
int newsite = site%mod; // 求 剩余数列的位置
swapn(rev,parr+pos); // 将 第rev个数提到最前面,造成pos位置的逆序数为 rev
re_array_site(newsite,pos+1,parr,n);
}

void main()
{
int n=5;
int arr[N] = {5,1,3,4,2};
int site = array_site(n,arr);
printf("Source arrange is : \t");;
for( int i=0;i<n;i++) printf("%3d ",arr[i]);
printf("\nat site: %d\n",site);

for( i=0;i<n;i++) arr[i] = i+1; // 重置排列为自然排列

printf(" site = %d is arr: \t",site);
re_array_site(site,0,arr,n);
for( i=0;i<n;i++) printf("%3d ",arr[i]);
printf("\n");
}

回复
zilingzhang 2002-06-13
由于n!大于(n-1)(n-1)!+(n-2)(n-2)!+......+
所以可知 i * (n-1)!应该是小于序号最接近序号的,(n-1)!已知,所以i可知,以此类推可知整个i序列!

举例如下:
给了abcd4个数,给了序号9
首先由(n-1)! = 6 (9 > 6)得到第一个为序号排列为2的 b 9-6 = 3剩下acd
(n-1)! = 2 3 > 2 得到第二个序号排列为2的 c 3-2 = 1 a d
(n-1)! = 1 1 = 1得到第三个序号排列为1的 a
此时该序列应为 b c a d
对应序号 9
回复
qxp 2002-06-13
eion真牛亚
回复
相关推荐
发帖
数据结构与算法
创建于2007-08-27

3.2w+

社区成员

数据结构与算法相关内容讨论专区
申请成为版主
帖子事件
创建了帖子
2002-06-10 10:53
社区公告
暂无公告