单词游戏的穷举算法
2010-05-25 14:15
169 查看
本文的重点在于一个算法的实现,使用VBS实现的好处,主要是显得比较精简。它和一个大数拆成若干个小数的集合有异曲同工之妙。
相信大家都玩过单词游戏,这类游戏通常是给你一大堆字母,让你去组装单词,单词越多越长战斗力就越强。
为了这个助手,首先得准备一个字典文件;其次就是一个核心算法问题,因为要字母的任意组合估计还是要用穷举法(穷举法虽然不是有效率的算法,但它是一个能解决问题的有效方法),利用好穷举法并且根据单词规则的有效性,也许仍能做到有效率。
第一步,我们需要利用字典特性,因为它都是由字母组成,为减少单词串长度,我们可以根据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…
在单词前后都加上竖杠的好处在于方便匹配,例如某一个单词是另一个单词的前缀时,通过“|单词|”的方式可以进行唯一定位。
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
在上图(图002.png)中,①处判断是否符合单词,并且不重复时并入单词串;②处是在递归调用前判定是否符合单词规则,如果它不是一个有效单词的前缀,再往上拼字母就没有意义。
|acknowledge|angel|wedge|legend|endow|ego|dock|gaol|gene|
本文发表于《软件报》2008年50期
相信大家都玩过单词游戏,这类游戏通常是给你一大堆字母,让你去组装单词,单词越多越长战斗力就越强。
组词游戏引出的话题
虽然这是锻炼人的“单词能力”,不过对每个人是否真实有效,倒是另外一个问题。就会想如果有个辅助工具,估计可以让自己的“战斗力”超强。为了这个助手,首先得准备一个字典文件;其次就是一个核心算法问题,因为要字母的任意组合估计还是要用穷举法(穷举法虽然不是有效率的算法,但它是一个能解决问题的有效方法),利用好穷举法并且根据单词规则的有效性,也许仍能做到有效率。
利用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
在上图(图002.png)中,①处判断是否符合单词,并且不重复时并入单词串;②处是在递归调用前判定是否符合单词规则,如果它不是一个有效单词的前缀,再往上拼字母就没有意义。
测试结果有效
这样我们输入acknowledge后就可以看到结果了:|acknowledge|angel|wedge|legend|endow|ego|dock|gaol|gene|
本文发表于《软件报》2008年50期
相关文章推荐
- 猜单词的游戏
- “单词竞猜游戏之管理员端”补充版
- 洛谷P1278 单词游戏
- 洛谷P1278 单词游戏
- 游戏英语单词
- 网易之小易参与了一个记单词的小游戏。游戏开始系统提供了m个不同的单词,小易记忆一段时间之后需要在纸上写出他记住的单词。小易一共写出了n个他能记住的单词, 如果小易写出的单词是在系统提供的,将获得
- “单词竞猜游戏之管理员端”补充版
- php猜单词游戏
- Boggle单词游戏求解
- 单词游戏
- 吴昊品游戏核心算法 Round 17(补遗篇) —— 单词游戏(后篇)
- 7.9 单词游戏 搜索
- 关于单词游戏软件的单词存库问题
- 游戏单词
- 字谜游戏,寻找字符矩阵中行、列、对角线方向的包含的所有单词
- Yii Framework 开发教程(4) Hangman 猜单词游戏实例
- 英语单词拼写游戏开发纪录
- 单词游戏
- 日文游戏常见日语单词及读音
- java实现单词搜索迷宫游戏