字典序问题

字典序问题描述:

在数据加密和数据压缩中常需要对特殊的字符串进行编码。给定的字母表A 由26 个小写英文字母组成A={a,b,…,z}。该字母表产生的升序字符串是指字符串中字母按照从左到右出现的次序与字母在字母表中出现的次序相同,且每个字符最多出现1 次。例如,a,b,ab,bc,xyz 等字符串都是升序字符串。现在对字母表A 产生的所有长度不超过6 的升序字符串按照字典序排列并编码如下。1 2 … 26 27 28 …a b … z ab ac …对于任意长度不超过6 的升序字符串,迅速计算出它在上述字典中的编码。«算法设计:对于给定的长度不超过6 的升序字符串,计算出它在上述字典中的编码。«数据输入:输入数据由文件名为input.txt的文本文件提供。文件的第一行是一个正整数k,表示接下来共有k行。接下来的k 行中,每行给出一个字符串。«结果输出:将计算结果输出到文件output.txt中。文件共有k 行,每行对应于一个字符串的编码。

#include<stdio.h>
#include<math.h>

double f(int a);  //阶乘
double c(int m,int n);  //组合


int main()
{
 FILE *fp,*fp1;
 int p;
 double res;
 char a[10];
 int len;

    if((fp=fopen("D:\\Microsoft Visual Studio\\Microsoft Visual Studio\\MyProjects\\字典序\\字典序input.txt", "r"))==NULL)
 {
  printf("can not open the file 字典序input.txt!\n");
  exit(1);
 }

 if((fp1=fopen("D:\\Microsoft Visual Studio\\Microsoft Visual Studio\\MyProjects\\字典序\\字典序output.txt", "w"))==NULL)
 {
     printf("can not open the file input.txt!\n");
    exit(1);
 }
 
 while(fscanf(fp,"%s",a)!=EOF)
 {
  double sum=0,s=0;
  len=strlen(a);
    
  if(len>6)     //出现长度大于6
  continue;
  p=1;
  for(int i=1;i
   if(a[i]<=a[i 1])
   {
    p=0;
      break;  //出现降序
   }
      if(p=0) continue;


   for(i=0;i<len;i++)
    if(('A'<=a[i])&&(a[i]<='Z'))    //出现大写字母的情况处理
      
     a[i]=a[i]-'A' +'a';
    
    for(int j=1;j<=len;j ++)
     sum =c(26,j);
    for(int n=len;n>0;n--)
     s =c(26-a[n-1] 'a'-1,len 1-n);
    res=sum-s;
   
   fprintf(fp1,"%.0lf ",res);
 }
  return 1;
  fclose(fp);
  fclose(fp1);
}

double f(int n)  //求阶乘的具体实现
                                               
 double s=1;
 for(int i=1;i<=n;i )
  s*=i;
 return s;
}
double c(int m,int n)   //求组合的具体实现
{
 double sum;
 if(m>n)
  sum=f(m)/(f(n)*f(m-n));
 else sum=0;
 return sum;
}

参考:http://blog.sina.com.cn/s/blog_94ff8704010117e6.html

 

具体的稍后奉上。

 

思想:排列组合问题

考察一般情况下长度不超过k的升序字符串

设以第i个字符打头的长度k的升序字符串个数位f(i,k),长度k的升序字符串总个数为g(k),则

g(k)=累加f(i,k),i从1到26

f(i,1)=1 g(1)=累加f(i,1),i从1到26

f(i,2)=累加f(j,1)=26-i,j从i+1到26 g(2)=累加f(i,2)=累加(26-i),i从1到26

一般情况,有:

f(i,k)=累加f(j,k-1),j从i+1到26

g(k)=累加f(i,k)=累加i(累加j,f(j,k-1)),i从1到26,j从i+1到26

举例:对于字符串cfkn,首先有g(3),g(2),g(1) 长度小于4的所有串的个数

其次有f(2,4),f(1,4) 首位小于第一个字母的4长度串的个数

再次f(4,3),f(5,3) 以d,e开头的长度为3的串的个数

再其次f(7,2),f(8,2),f(9,2),f(10,2) 以g,h,i,j开头的长度为2的串的个数

最后f(5,1),f(4,1),f(3,1),f(2,1),f(1,1) 以l,m,n开头的长度为1的串的个数

把以上相加后还需要加1,求出本字符串的序

#include<iostream>
#include<string>
using namespace std;
//计算以i开始的长度为k的所有数的个数,前提是升序
int f(int i,int k)
{
int sum=0;
if(k==1) return 1;
else {
for(int j=i+1;j<=26;j++)
{
sum+=f(j,k-1);
}
}
return sum;
}
//计算长度为k的所有组合的个数,前提是升序
int g(int k)
{
int sum=0;
for(int i=1;i<=26;i++)
{
sum+=f(i,k);
}
return sum;
}
//转换成数字
int change(char c)
{
return c-'a'+1;
}
//给出一个字符串返回一个序号
int order(string s)
{
int k=s.size();
int sum=0,temp=0;
//获取1~k-1长度的子字符串数
for(int i=1;i<k;i++)
{
sum+=g(i);
}

//小于第一个字母的长度为k的所有组合个数
for(int i=1;i<change(s[0]);i++)
{
sum+=f(i,k);
}

//以第一个字母作为开始的字符串组合个数
for(int i=1,temp=change(s[0]);i<s.size();i++)
{
int t=change(s[i]); //获取此时的字母并转化成数字
int len=s.size()-i; //获取此时的长度
for(int j=temp+1;j<t;j++) //获取比此时数字小,长度是此时的长度的所有组合的个数
sum+=f(j,len);
temp=t; //获取当前字符值,方便下一次从此项+1项开始累加
}
return sum+1;
}
int main()
{
string s;
cin>>s;
cout<<order(s)<<endl;
system("pause");
return 0;
}

总结:本程序最难的就是对每一位的字符计算以小于其的字符开头但大于前面字符的的所有字符串个数,所以通过从前一项加1到比本项小的所有头,带着当前长度累加。还不能遗漏最后加1,获取当前位。还要注意读取字符串的首位并不是高位,需要转换。

编程的关键代码:

for(int i=1,temp=change(s[0]);i<s.size();i++)
{
int t=change(s[i]); //获取此时的字母并转化成数字
int len=s.size()-i; //获取此时的长度
for(int j=temp+1;j<t;j++) //获取比此时数字小,长度是此时的长度的所有组合的个数
sum+=f(j,len);
temp=t;

}

http://hi.baidu.com/hk2305621/item/36778b5156849eded48bacc7

http://blog.csdn.net/wbgxx333/article/details/10052279

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值