生成文本:
《编程珠玑》上面最后的一小节讲的是如何生成随机文本的马尔可夫方法,
大家如果想对马尔可夫链有个初步的了解,可以看看吴军的《数学之美》P52上关于这部分的简略介绍,说的简单点就是在一个状态序列中,t时刻的状态一般情况下是与前面所有的状态均有关系,但是为了近似,简化运算,我们大致上认为仅与前面k个状态相关,用概率公式标示就是p(s(t)|s(0),s(1),.......s(t-1))==p(s(t)|s(t-1),...s(t-k+1));
同时需要注意的一点就是对于当前的状态s,向下一个各个状态所有可能的取值的概率是不相同的,比如说如果我们统计在词典中每个字母的下一个字母出现的频率的话,会发现不是所有的26个字母的出现的频率都是一样的,也就是说这里有个状态转移概率的问题,而我们选择的是转移概率最大的取值作为下一个状态的可能结果.
如果我上面说的不是很明白的话,我们可以看一个由字母生成的随机文本上的马尔可夫过程,首先输入一个文本,然后我们统计每个字母(相当于当前状态s)的下一个字母出现的频率分布,比如说a后面出现10次c,5次d。。。。。这个时候当生成随机文本的时候我们就需要根据上面的状态转移概率(具体的为频率)中最大的一个来确定该选取哪个作为下一个状态的取值,当然我们需要一个初始状态s0,至于怎么选,看自己爱好吧
ps:作者并没有我说的选择出现的频率最大的一个作为下一个可能的状态,而是随机的选取了一个,这个在算法当中有体现
好了说了这么多,我们进入作者真正的算法当中,体会大师的思想去,在这之前我需要说明的是大师的算法当中对于每个状态的限定为一个单词,而不是上面我写的例子中的字母,这样处理是有好处的,毕竟要是使用字母的话,生成的一个状态序列都不一定是单词。
下面是代码片段:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
const int k=2;//k代表的是几阶马尔可夫链
char inputchars[500000];//存储输入的用于生成随机文本的字符串
char* word[10000];//后缀数组,用于排序
const int nword=0;
int wordcmp(const char*,const char*);
int sortcmp(const void*,const void*);
void creatrandtext();
char* skip(char* ,int);
int main(void)
{
word[0]=inputchars;
while(scanf("%s",word[nword])!=EOF)
{
word[nword+1]=word[nword]+strlen(word[nword])+1;
nword++;
}
//上面的最后的加1是为了存储'\0'作为每个单词的结束符
//同时将word[0]赋值为inputchars,这样输入的字符都会存储在inputchars数组当中
//这样其实当最后输入结束的话,word[i]指向的是第i+1个单词在inputchars数组中
//的首地址
for(int i=0;i<k;i++)
word[nword][i]='\0';
//在word以及inputchars的最后加上k个'\0',这样比较函数就不会运行到最后
qsort(word,nword,sizeof(word[0]),sortcmp);
creatrandtext();
return 0;
}
//仅比较两个字符序列中的前k个单词是否相等
int wordcmp(const char* p,const char* q)
{
int n=k;
if(p==NULL||q==NULL)
exit(1);
for(;*p==*q;p++,q++)
{
if(*p=='\0'&&--n==0)
return 0;
}
return *p-*q;
}
int sortcmp(const void* p,const void* q)
{
return wordcmp(char*(p),char*(q));
}
void creatrandtext()
{
char* phrase=inputchars;//初始状态
int l,u,m;
char* p;
for(int wordleft=10000;wordleft>0;wordleft--)
{
l=-1;
u=nword;
while(l+1!=u)
{
m=l+(u-l)>>1;
if(wordcmp(word[m],phrase)<0)
l=m;
else
u=m;
}
for(int i=0;wordcmp(word[u],phrase)==0;i++)
{
if(rand()%(i+1)==0)
p=word[u+i];
}
phrase=skip(p,1);//需要注意的是这里选择phrase作为当前状态,那么为什么选择
//从第二个单词开始的位置呢?
//这是因为马尔可夫链的关系,当随着状态的推进,当前状态的前k个也在不断的向前
//推进
if(strlen(skip(phrase,k-1))==0)
break;
printf("%s ",skip(phrase,k-1));
}
}
char* skip(char* p,int k)
{
if(p==NULL||k<0)
exit(1);
int count==0;
while(true)
{
if(*p=='\0')//这里肯定会结束的,还记得我们在前面曾经输入完成之后又输入了k
//个'\0'么?
count++;
if(count==k)
break;
p++;
}
return p;
}
//由上面的代码片段当中我们可以看到作者并没有根据出现的频率来选取下一个可能的状态,而是采用随机选取的方法,这个在二分查找的下一行可以看出来,所以这是个具有相同转换概率的马尔可夫链,同时二分查找用来查找第一次出现的位置,而不是别的什么任意的位置
作者在最后给出了处理字符串常用的几种数据结构:
散列:在上一篇开始用到
平衡树:在C++STL中用到,至今没看见源码,下一步准备看看侯捷的《源码剖析》
后缀数组:这两次都用到了
希望对大家有帮助