一、问题描述
在数据加密和数据压缩中常需要对特殊的字符串进行编码。给定的字母表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的文本文件提供。文件的第1行是一个正整数k,表示接下来有k行。在接下来的k行中,每行给出一个字符串。
• 结果输出:将计算结果输出到文件output.txt。文件有k行,每行对应一个字符串的编码。
•
输入文件示例 输出文件示例
input.txt output.txt
2 1
a 2
b
二、题目分析及算法设计思路
三、代码实现
#include<iostream>
#include<string>
#include<fstream>
#include<math.h>
using namespace std;
int f(int i, int k) {//i为首字母,长度为k的字符串数量
int num = 0;
if (k == 1) return 1;
for (int j = i + 1; j <= 26; j++) {
num += f(j, k - 1);
}
return num;
}
int g(int k) {//长度为k的字符串的总数
int num = 0;
for (int i = 1; i <= 26; i++) {
num += f(i, k);
}
return num;
}
bool ad(string str) {//判断是否是升序字符串,若是返回true
int len = str.length();
for (int i = 0; i < len-1; i++) {
if (str[i] >= str[i + 1]) {
return true;
}
}
return false;
}
int function(string str) {
if (ad(str)) {
return 0;
}
int sum = 0;
int len = str.length();//记录每个字符串的长度
for (int k = 1; k < len; k++) {//位数小于len的字符串的总数
sum = sum + g(k);
}
int first = str[0] - 'a' + 1; //位数相同,首字母比这个数小的总和
for (int i = 1; i < first; i++) {
sum = sum + f(i, len);
}
for (int j = 1; j < len; j++) {
int loc = str[j] - 'a' + 1;
for (int i = str[j - 1] - 'a' + 2; i < loc; i++) {
sum = sum + f(i, len - j);
}
}
return sum + 1;
}
int main() {
ifstream in("d://input.txt");
ofstream out("d://output.txt");
string str;
int i;
in >> i;0
while (i--) {
in >> str;
if (function(str) == 0) {
out << "ERROR!" << endl;
}
else out << function(str) << endl;
}
in.close();
out.close();
return 0;
}
四、测试展示
测试一:
input.txt:
output.txt:
测试二:
input.txt
output.txt
五、代码分析
- 构建f(i,k)函数用来求第i个字符为首字母,长度为k的升序字符串个数,g(k)函数来求出长度为k的字符串的总数。用len记录字符串的长度,sum记录这个字符串前面的字符串总个数,变量loc记录首字母之后的第j个位置。先让sum累加位数(即字母个数)小于len的字符串总数,然后考虑累加相同位上的字符串总数,用j表示首字母之后的第j个位置不同,用变量loc记录首字母之后的第j个位置,小于从比上一个位置的字母大一个的字母开始循环累加字符串个数到sum中。最后sum+1就得到了该字符串的字典序。
- 算法时间复杂度分析:
设字符串的长度为n。
分析函数f(i, k),它是一个递归函数,需要递归调用k次,并在每次调用中进行一个长度为26的循环。因此,它的时间复杂度为O(26^k)。接下来函数g(k)它需要调用26次函数f(i, k),因此它的时间复杂度为O(26*26^k) = O(26^(k+1))。因为k为常数值,所以函数f和函数g均可写成O(1)。
最后分析函数function(str),它有三个嵌套的循环。第一个循环需要计算所有长度小于len的字符串的数量,它的时间复杂度为O(n26^n);第二个循环需要计算所有位数相同但首字母比当前字符串小的字符串数量,它的时间复杂度为O(n26^(n-1));第三个循环需要计算除首字母外其他字符按字典序排列比当前字符串小的字符串数量,它的时间复杂度也为O(n26^(n-1))。
T(n)=O(n26^n)+O(n26^(n-1))+O(n26^(n-1))=O(n26^n).
因此,函数function(str)的总时间复杂度为O(n26^n)。
综上:时间复杂度为T(n)=O(n26^n)。
六、总结与体会
- 总结:在计算机算法设计与分析中,递归技术是十分有用的。使用递归技术往往使函数的定义和算法的描述更加便捷且易于理解,有些数据结构本身就具有递归特性,特别适合用递归的形式来描述。而有些问题虽然本身并没有明显的递归结构,但用递归技术来求解,可以使设计出的算法易于分析和简洁。
- 体会:递归问题的难点在于确定函数代表的具体含义和列出递归函数的表达式,每次分析递归问题时总是要冥思苦想,想不出一个适合的表达式,需要我在日后多加练习。