重新把《编程珠玑》读了一遍,以前并没有仔细研究最后一章的生成随机文本,昨天仔细读了一下,感悟颇深,想记录一下自己的感悟,顺便理清一下思路。
言归正传,要通过读取一个文档来生成一个随机的文档,作者使用的方法是根据k连单词的后一个单词的出现概率来选取下一个单词。作者在书中用的方法是读取之后,对数组进行排序,那么前k个单词相同的子串一定是相邻的,然后通过二分查找,找到第一个子串,通过随机选取的方法选一个子串输出。但是在习题中,作者给出了更快的方法,用哈希表来代替排序和查找的过程。之所以被称作“马尔科夫链算法”是因为每个状态表示一个k连字母,并且从一个状态到另一个状态的概率是不变的。
读取完文本之后,将读取的数据放入到一个哈希表中,值得注意的是,作者给出了常见的哈希冲突的解决方法:通过维护一个大数组来解决冲突,而不是一般数据结构书中介绍的使用链表。代码如下:
for(i=0;i<=nword-k;i++)
{
j=hash(word[i]);
next[i]=bin[j];
bin[j]=i;
}
查找hash表时,bin[k]=i1,next[i1]=i2,next[i2]=i3······一直下去就可以找出哈希值同是k的所有项。
在输出时,通过查找这个hash表,并通过概率的判断来决定下一个单词的输出,如果同一个单词多次出现,那么会通过多次的判断,来增加被选中的概率。通过判断“哨兵”来判断是否已经结束,“哨兵”是由k个0构成,放在输入文本的最后。
#include <stdio.h>
#include<stdlib.h>
#include "string.h"
char inputchars[4300000];
#define MAXWORDS 800000
char *word[MAXWORDS];
int nword = 0;
int k = 2;
int next[MAXWORDS];
#define NHASH 499979
int bin[NHASH];
#define MULT 31
int hash(char * w)
{
unsigned int h=0;
int n;
for(n=k;n>0;w++)
{
h=h*MULT+*w;
if(*w==0)
n--;
}
return h%NHASH;
}
int wordncmp(char *p,char *q) //判断前K个单词是否相同
{
int n=k;
for(;*p==*q;p++,q++)
if(*p==0 && --n==0)
return 0;
return *p-*q;
}
char *skip(char *p, int n) //跳过n个单词
{
for ( ; n > 0; p++)
if (*p == 0)
n--;
return p;
}
int main()
{
int i,j,wordleft;
char *phrase,*p;
word[0]=inputchars;
while(scanf("%s",word[nword])!=EOF)
{
word[nword+1]=word[nword]+strlen(word[nword])+1;
nword++;
}
for(i=0;i<k;i++)
word[nword][i]=0;
memset(bin,-1,NHASH);
for(i=0;i<=nword-k;i++)
{
j=hash(word[i]);
next[i]=bin[j];
bin[j]=i;
}
for(i=0;i<k;i++)
printf("%s ",word[i]);
phrase=inputchars;
for(wordleft=1000;wordleft>0;wordleft--)
{
i=0;
for(j=bin[hash(phrase)];j>0;j=next[j])
{
if((wordncmp(phrase,word[j])==0) && (rand()%(++i)==0))
p=word[j];
}
if(strlen(skip(p,k))==0)
break;
printf("%s ",skip(p,k));
phrase=skip(p,1);
}
return 0;
}
这段程序对输出的文档做出了词数的限制,并且在检查到哨兵就跳出循环,输出结束。