问题描述
今天做的一道实验题,刚刚写完实验报告,就把自己实验报告中的内容在这里再大概说一下(可能会有较多报告中的截图,懒~)
问题分析
我们要求字符必须是升序排列的,所以如果给定字符串长度为3,且开头字母是a,所以我们应该决定出后两个元素,且后两个元素应该比a大,所以后两个元素的组合数应该是C(25,2)
因为比a大的字母有25个,所以应该从25个中选出2个作为后两个字母填入指定位置。
所以我们应该首先写一个求组合数的函数,在这里我写了两个,一个是求一个组合数,另一个是求一组连续的组合数之和。在内部我们用了比较特殊的方法,可以减小时间复杂度并且可以防止溢出,以后做排列组合的题可以参考一下,具体代码往下看。
再写一个sums的函数,这个函数求出的是,对于在当前字符串中每一个字符,比这个字符小的其他字符如果在这个位置上,会有多少种情况,我知道这么说很TM晦涩,因为我自己都不知道怎么说,所以我们举个例子说明:
例如我们的字符串是"cgy",那么我们就要先求出,比c小的字符作为字符串的第一位有多少种情况,即“a…”和"b…"的情况。
我们的另一个求连续组合数的情况是计算所有比当前字符串长度小的情况。
刚刚我们算了长度为3的情况,接下来是计算长度为1和2的情况,这就是我们一会代码要展示的第二个求组合数的函数。
其他说明
为了充分理解这个问题,我写了一个单独的函数,可以生成任意数目的随机满足要求的字符串且长度不大于6,然后在主函数中加入计算时间和频率的变量,计算在不同数据量的情况下所得到的结果时间上的差异。
代码
首先是生成随机满足要求字符串的代码,我们可以自己指定生成的数量。
#include <iostream>
#include <fstream>
using namespace std;
ofstream file("dic1M.txt");
int num=100;
void func(string s)//生成递增字符串
{
if(!num) return;
file<<s<<endl;num--;
if(s.length()>=6) return;//当字符串大于等于6的时候直接返回
char c=s[s.length()-1];
for(int i=c+1;i<='z';i++)//从最后一个字母起开始枚举
{
func(s+char(i));
}
}
int main()
{
file<<num<<endl;
for(int i='a';i<='z';i++)
{
func(string(1,char(i)));
}
cout<<"dic create sucefuly!"<<endl;
file.close();
return 0;
}
接着是我们的主函数:
#include <iostream>
#include <fstream>
#include <windows.h>
#include <math.h>
using namespace std;
LARGE_INTEGER frequency;//时间对象
double dff,begin_,_end,time;//时钟频率,起始时间,结束时间,时间差
int Comb(int n,int m)//组合数
{
int res=1;
for(int i=1;i<=m;i++)
{
res=res*(n-i+1)/i;//2个优点,1减少复杂度,2防溢出
}
return res;
}
//Combination 函数返回一个上面为up,下面为down的一个组合数数值。 如Combination(5,8) 返回C58的值.
int Comb_continuous(int n,int m)//计算一组连续的组合数之和
{
int sum=0,num=1;
for(int i=1;i<=m;i++)
{
num=num*n/i;
n--;
sum+=num;
}
return sum;
}
int sums(int len,string str)
{//计算
int sum=0;
int index=0;
for(int i=0;i<len;i++)//枚举每一个字母
{
for(int j=index;j<str[i]-'a';j++)//计算比str[i]小的元素进行枚举
{
sum+=Comb(26-j-1,len-i-1);//26-j-1代表除去j剩下的元素个数(共26),len-i-1表示剩下的长度
}
index=str[i]-'a'+1;//从前一个字母的后面一个开始
}
return sum;
}
int main(){
QueryPerformanceFrequency(&frequency);//获得时钟频率
dff=(double)frequency.QuadPart;//取得频率
//按照算法设计的要求
ifstream in("dic1M.txt");
ofstream out("dicOut1M.txt");
string str;
int num,sum;
in>>num;//字符串数目
QueryPerformanceCounter(&frequency);
begin_=frequency.QuadPart;//获得初始值
for(int i=0;i<num;i++)
{
in>>str;
int len = str.length();//取字符串长度
sum=Comb_continuous(26,len-1)+sums(len,str)+1;
out<<sum<<endl;
}
QueryPerformanceCounter(&frequency);
_end=frequency.QuadPart;//获得终止值
time=(_end-begin_)/dff;//差值除以频率得到时间
out<<"规模为"<<num<<" "<<"spendTime="<<time*1000<<"ms"<<endl;
in.close();
out.close();
cout<<"dicOrder sucessfully!"<<endl;
system("pause");
return 0;
}
其中这个Comb就是计算单独组合数的函数,而Comb_continuous就是第二个组合数函数,计算比当前字符串长度短的所有可能字符串数目。例如我们的字符串长度为4,则这个函数可以计算出C(26,1)+C(25,2)+C(24,3)
sums则是计算当前长度下的排在输入字符串前面的字符串数,需要对每一个字符注意计算,然后累加。
其余部分就是计算时间和频率来评估数据量的部分,大家了解一下即可,