单词游戏的穷举算法

本文的重点在于一个算法的实现,使用VBS实现的好处,主要是显得比较精简。它和一个大数拆成若干个小数的集合有异曲同工之妙。

 

相信大家都玩过单词游戏,这类游戏通常是给你一大堆字母,让你去组装单词,单词越多越长战斗力就越强。

clip_image002

组词游戏引出的话题

虽然这是锻炼人的“单词能力”,不过对每个人是否真实有效,倒是另外一个问题。就会想如果有个辅助工具,估计可以让自己的“战斗力”超强。

为了这个助手,首先得准备一个字典文件;其次就是一个核心算法问题,因为要字母的任意组合估计还是要用穷举法(穷举法虽然不是有效率的算法,但它是一个能解决问题的有效方法),利用好穷举法并且根据单词规则的有效性,也许仍能做到有效率。

利用26个字母完成字典的简单索引效果

好吧,我们首先考虑一下,如果把字典文件读入到内存,并有效检索。根据字符串操作就会想到在字符串中进行定位是个好方法(可以实现类似数据库的索引效果),本文算法实现基于VBS(VBS在PC平台上的方便性不容置疑),因此就需要用到instr函数。另外字符串处理还有一个要素,单个字符串不能太长,否则性能很差。

第一步,我们需要利用字典特性,因为它都是由字母组成,为减少单词串长度,我们可以根据A-Z分成26个组来存储,即分别表示不同字母的单词串(如果单词有更多,我们还可以把它准备成26*26个字串),本文例举的字典文件cet6.txt中,每行一个单词,我们就依照这个规则把它读入内存:

Set objFSO= CreateObject("Scripting.FileSystemObject")

Set lf_wordfile = objFSO.OpenTextFile("cet6.txt",1,FALSE,FALSE)

For i=0 To 26 '2008-09-24 根据需要初始化每一个字串

gs_word(i) = "|"

Next

Do While Not lf_wordfile.AtEndOfLine

ls_text = lf_wordfile.ReadLine

ll_id = Asc(Lcase(Left(ls_text,1)) ) - Asc("a")

gs_word(ll_id) = gs_word(ll_id)+ls_text+"|"

Loop

上述完成对所有单词的读取,并且拼装成类似于以下的格式,如A开头的将是:

|abnormal|abolish|abrupt|absurd|abundance|academy|accessory…

在单词前后都加上竖杠的好处在于方便匹配,例如某一个单词是另一个单词的前缀时,通过“|单词|”的方式可以进行唯一定位。

有效地中断全部穷举,提高算法效率

现在我们要基于26个字串进行所有可能单词组合的匹配,要注意这个穷举由选定任意个数的单词(由于单字母的单词我们不作考虑,所以下文处理都是以最短2个字母为基础),在实现时就需要考虑单词长度本身是不确定的。假定给定的单词acknowledge,那么任意抽取的长度可以是2到11个字母的长度,因此它的穷举个数起码是大于11*10*9*8*7*6*5*4*3*2,如果真的全部遍历是几千万数量级的话,那会有多少开销?来看看笔者最终算法是如何实现的吧:

1、 因为单词我们确定是2个字母,所以首先部署双循环,即抽取2个字母组成单词前缀;

2、 判断此前缀是否符合单词规则,即判断“|前缀”是否在相应的字串中存在(因为我们已经确定了26个串,因此由前缀的第一个字母我们就可以直接使用instr( gs_word(i), “|前缀”)的方法来进行判定是否符合;

3、 把单词前缀和所使用余下的字符串+空格(这个空格是算法的核心点)进行递归处理,并传入参数which代表前缀字符所在串的序号;

4、 在递归处理的过程中,依据传入串的长度进行循环,把前缀拼接当前字符,如果当前字符为空,则取得一个字串(至此完成了一个抽取的字符串的组合,空格的作用也在于此),此时采用instr( gs_word(which), “|单词|”)来判断是否合法单词,如果是且不重复,则加入到单词串中(依旧使用instr)来判断;

5、 如果当前字符不为空,则和前缀拼接,判断它是否符合单词规则,如果是单词前缀,则用新的前缀、余下的字符(注意,此时空格仍将继续传入)、which(字典串序号),转4继续处理。

在所有的递归调用完成之后我们就得到全局单词串,里面就是我们找到的所有符合要求的单词。此算法中我们巧妙地传入了空格作为单词结束的判断,并且在每一次递归调用前判断是否符合单词规则来提高了效率(截断了无效的递归调用)。

算法要精准,避免无效调用

由于算法还是涉及了递归,因此在字母组合中,要根据单词的特效,进行有效组断。下面来看看这个递归过程中的内容:

Sub PrcRunWords( ByVal as_head, ByVal as_inword, ByVal which )

Dim ilen,li

Dim ls_newword, ls_char, ls_newhead, ls_ret

ilen = Len(as_inword )

For li=1 To ilen Step 1

ls_char = Mid(as_inword, li, 1 )

li_count = li_count + 1

If ls_char = " " Then '表示到了本单词的结尾

ls_ret = as_head

If Len(ls_ret)>0 Then '如果是单词则加入到单词串中

If InStr( gs_Word(which), "|"&ls_ret&"|" )>0 Then

If InStr(gs_allword, "|"&ls_ret&"|" )=0 Then

gs_allword = gs_allWord & ls_ret & "|"

End if

End if

End if

Exit for

Else '如果符合单词前缀,用新前缀与余下的字串继续处理

ls_newhead = as_head + ls_char

If InStr( gs_word(ll_id), "|"+ls_newhead)>0 Then

ls_newword = Left(as_inword, li-1) & Mid(as_inword, li+1 ) '新词

PrcRunWords ls_newhead, ls_newword, which

End if

End if

Next

End Sub

clip_image004

在上图(图002.png)中,①处判断是否符合单词,并且不重复时并入单词串;②处是在递归调用前判定是否符合单词规则,如果它不是一个有效单词的前缀,再往上拼字母就没有意义。

测试结果有效

这样我们输入acknowledge后就可以看到结果了

|acknowledge|angel|wedge|legend|endow|ego|dock|gaol|gene|

 

本文发表于《软件报》2008年50期

转载于:https://www.cnblogs.com/hzspa/archive/2010/05/25/1743580.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值