字典序问题
问题描述
在数据加密和数据压缩中常需要对特殊的字符串进行编码.给定的字母表A由26个小写英文字母组成,即A={a,b,… ,z}A=\lbrace a,b,\dotso,z\rbraceA={a,b,…,z}.该字母表产生的长序字符串是指定字符串中字母从左到右出现的次序与字母在字母表中出现的次序相同,且每个字符最多出现1次.例如,a,b,ab,bc,xyz,等字符串是升序字符串.现在对字母表A产生的所有长度不超过6的升序字符串按照字典排列编码如下:
123…262728…abc…zabac…
1\quad 2\quad 3\dotso26\quad27\quad 28\dotso \\
a\quad b\quad c\dotso z\quad ab\quad ac\dotso
123…262728…abc…zabac…
对于任意长度不超过6的升序字符串,迅速计算出它在上述字典中的编码。
- 方法1: 首先可以简单的知道,要计算以一个确定的首字母字母i在确定的层数l中,要判断共有多少个这样的升序字符串,就要对第二个字母,进行遍历,从i+1遍历到26,得到其对应的确定层数l-1对应的升序字符串,再配合上第一个确定的首字母i,即得到我们所要求的字符串。
例如:对以bbb开头的有3层的字符串,要计算其总个数。第二个字母一定要比bbb大,那么可能是c,d,…c,d,\dotsoc,d,…,把这里面的每个可能字母都进行遍历,得到其对应的2层字符串的个数,然后进行累加求和,就可以得到所求的总个数。
因此可以得到递推关系式,若首字母i在确定的层数l中共有f(i,l)f(i,l)f(i,l)个升序字符串,那么f(i,l)=∑j=i+126f(j,l−1)f(i,l)=\sum\limits_{j=i+1}^{26}f(j,l-1)f(i,l)=j=i+1∑26f(j,l−1),又因为显然对于任意的f(i,1)=1f(i,1)=1f(i,1)=1,因此,可以进行递归求解f(i,l)f(i,l)f(i,l)
既然能够确定f(i,l)f(i,l)f(i,l)的值,那么要求任意一个字符串的次序,就只要计算出,在它之前共有多少个,再+1就是所要求的数目。设其深度为depthdepthdepth,然后其字母分别为a0,a1,… ,adepth−1a_0,a_1,\dotso,a_{depth-1}a0,a1,…,adepth−1,那么显然比depthdepthdepth小的层数都在该字符串之前,因此要加上∑l=1i<depth∑i=1i≤26f(i,l)\sum\limits_{l=1}^{i\lt depth}\sum\limits_{i=1}^{i\leq26}f(i,l)l=1∑i<depthi=1∑i≤26f(i,l);同时还有首字母比a0a_0a0小的,层数也为depthdepthdepth的字符串,其数目为∑i=1i<a0f(i,depth)\sum\limits_{i=1}^{i\lt a_0}f(i,depth)i=1∑i<a0f(i,depth);最后对于首字母为a0a_0a0的深度为depthdepthdepth的字符串,还要进行排序,其数目为∑i=1i<depth∑j=ai−1+1j<aif(j,depth−i)\sum\limits_{i=1}^{i\lt depth}\sum\limits_{j=a_{i-1}+1}^{j\lt a_i}f(j,depth-i)i=1∑i<depthj=ai−1+1∑j<aif(j,depth−i)
故总数目sum=∑l=1i<depth∑i=1i≤26f(i,l)+∑i=1i<a0f(i,depth)+∑i=1i<depth∑j=ai−1+1j<aif(j,depth−i)sum=\sum\limits_{l=1}^{i\lt depth}\sum\limits_{i=1}^{i\leq26}f(i,l)+\sum\limits_{i=1}^{i\lt a_0}f(i,depth)+\sum\limits_{i=1}^{i\lt depth}\sum\limits_{j=a_{i-1}+1}^{j\lt a_i}f(j,depth-i)sum=l=1∑i<depthi=1∑i≤26f(i,l)+i=1∑i<a0f(i,depth)+i=1∑i<depthj=ai−1+1∑j<aif(j,depth−i)
程序代码如下
#include<iostream>
#include<fstream>
#define LENGTH_MAX 7
using namespace std;
int get_num(int charOrder, int depth)
{
int num = 0;
if (depth > 1)
{
for (int i = charOrder + 1; i <= 26; i++)
num += get_num(i, depth - 1);
}
else if (1 == depth)
return 1;
else if (0 == depth)
return 0;
return num;
}
int get_sum(char* ch, int depth)
{
int charOrder = ch[0] - 'a' + 1;
int sum = 0;
for (int i = 1; i < charOrder; i++)
for (int j = 0; j < depth; j++)
sum += get_num(i, depth - j);
for (int i = 1; i < depth; i++)
{
int l = ch[i] - 'a' + 1;
int lastl = ch[i - 1] - 'a' + 1;
for (int j = lastl + 1; j < l; j++)
{
sum += get_num(j, depth - i);
}
}
for (int i = charOrder; i <= 26; i++)
for(int j = 1; j <depth; j++)
sum += get_num(i, depth - j);
return sum + 1;
}
int main()
{
ifstream fp_read("input.txt");
ofstream fp_write("output.txt");
if (fp_read.fail() || fp_write.fail())
{
cout << "打开文件错误!" << endl;
exit(0);
}
int num = 0;
fp_read >> num;
cout << num << endl;
for (int i = 0; i < num; i++)
{
char input[LENGTH_MAX];
fp_read >> input;
cout << input << " : ";
int depth = strlen(input);
int sum = get_sum(input, depth);
fp_write << sum << endl;
cout << sum << endl;
}
return 0;
}
- 方法2:
也可以用组合的方法来想这个问题,当你从字母表中的26个字母中,任取depthdepthdepth个互不相同的字符,那么自然会对应唯一确定的一个升序字符串。因此要求长度为depthdepthdepth的字符串个数,只要求出从26个字母中取depthdepthdepth个互不相同的字符的种类就可以了,也就是C26depthC_{26}^{depth}C26depth,然后对于首字母相同的同一长度的两个字符串,设其字母为a0,a1,… ,adepth−1a_0,a_1,\dotso,a_{depth-1}a0,a1,…,adepth−1,其数字大小的差值就是除去首字母的字符串的差值,若首字母仍相同,继续分解;若首字母不同,则可以拆解为首字母相同的,比如说amn与bkz,他们的差值就等于(bkz−abc)−(amn−abc)=(bkz−bcd)+C262−(amn−abc)(bkz-abc)-(amn-abc)=(bkz-bcd)+C_{26}^2-(amn-abc)(bkz−abc)−(amn−abc)=(bkz−bcd)+C262−(amn−abc)再继续拆解
实现的代码如下
#include<iostream>
#include<fstream>
#define LENGTH_MAX 7
using namespace std;
int get_c(int m, int n)
{
if (n == m || 0 == m || 0 == n)return 1;
if (1 == n)return m;
return (get_c(m - 1, n - 1) + get_c(m, n - 1));
}
int get_sum(char* str,int depth)
{
int sum = 0;
if (1 == depth)
return (str[0] - 'a' + 1);
else if(depth > 1)
{
for (int i = 0; i < depth; i++)
{
int lastCh = 0;
if (i != 0)
{
sum += get_c(i, 26);
lastCh = str[i - 1] - 'a' + 1;
}
sum += get_c(depth - i, 26 - lastCh) - get_c(depth - i, 26 - (str[i] - 'a'));
}
}
return sum + 1;
}
int main()
{
ifstream fp_read("input.txt");
ofstream fp_write("output.txt");
if (fp_read.fail() || fp_write.fail())
{
cout << "打开文件错误!" << endl;
exit(0);
}
int num = 0;
fp_read >> num;
cout << num << endl;
for (int i = 0; i < num; i++)
{
char input[LENGTH_MAX];
fp_read >> input;
cout << input << " : ";
int depth = strlen(input);
int sum = get_sum(input, depth);
fp_write << sum << endl;
cout << sum << endl;
}
return 0;
}
本文探讨了字典序问题,特别是在数据加密和压缩中的应用。介绍了如何对以特定字母开始且长度不超过6的升序字符串进行编码。提出了两种方法:一是通过递推关系计算字符串个数并求解其在字典中的位置;二是利用组合数学,计算不同长度的升序字符串的数量,并处理相同首字母的情况。文章提供了相应的程序代码实现。

1358

被折叠的 条评论
为什么被折叠?



