① 请问什么是百度算法
随着搜索经济的崛起,人们开始越加关注全球各大搜索引擎的性能、技术和日流量。作为企业,会根据搜索引擎的知名度以及日流量来选择是否要投放广告等;作为普通网民,会根据搜索引擎的性能和技术来选择自己喜欢的引擎查找资料;作为技术人员,会把有代表性的搜索引擎作为研究对象. 搜索引擎经济的崛起,又一次向人们证明了网络所蕴藏的巨大商机。网络离开了搜索将只剩下空洞杂乱的数据,以及大量等待去费力挖掘的金矿。
但是,如何设计一个高效的搜索引擎?我们可以以bd所采取的技术手段来探讨如何设计一个实用的搜索引擎.搜索引擎涉及到许多技术点,比如查询处理,排序算法,页面抓取算法,CACHE机制,ANTI-SPAM等等.这些技术细节,作为商业公司的搜索引擎服务提供商比如bd,GOOGLE等是不会公之于众的.我们可以将现有的搜索引擎看作一个黑盒,通过向黑盒提交输入,判断黑盒返回的输出大致判断黑盒里面不为人知的技术细节.
查询处理与分词是一个中文搜索引擎必不可少的工作,而bd作为一个典型的中文搜索引擎一直强调其”中文处理”方面具有其它搜索引擎所不具有的关键技术和优势.那么我们就来看看bd到底采用了哪些所谓的核心技术.
我们分两个部分来讲述:查询处理/中文分词.
一. 查询处理
用户向搜索引擎提交查询,搜索引擎一般在接受到用户查询后要做一些处理,然后在索引数据库里面提取相关的信息.那么bd在接受到用户查询后做了些什么工作呢?
1. 假设用户提交了不只一个查询串,比如”信息检索 理论 工具”.那么搜索引擎首先做的是根据分隔符比如空格,标点符号,将查询串分割成若干子查询串,比如上面的查询就会被解析为:<信息检索,理论,工具>三个子字符串;这个道理简单,我们接着往下看.
2. 假设提交的查询有重复的内容,搜索引擎怎么处理呢?比如查询”理论 工具 理论”,bd是将重复的字符串当作只出现过一次,也就是处理成等价的”理论 工具”,而GOOGLE显然是没有进行归并,而是将重复查询子串的权重增大进行处理.那么是如何得出这个结论的呢?我们可以将”理论 工具”提交给bd,返回341,000篇文档,大致看看第一页的返回内容.OK.继续,我们提交查询”理论 工具 理论”,在看看返回结果,仍然是那么多返回文档,当然这个不能说明太多问题,那看看第一页返回结果的排序,看出来了吗?顺序完全没有变化,而GOOGLE则排序有些变动,这说明bd是将重复的查询归并成一个处理的,而且字符串之间的先后出现顺序基本不予考虑(GOOGLE是考虑了这个顺序关系的).
3. 假设提交的中文查询包含英文单词,搜索引擎是怎么处理的?比如查询”电影BT下载”,bd的方法是将中文字符串中的英文当作一个整体保留,并以此为断点将中文切分开,这样上述的查询就切为<电影,BT,下载>,不论中间的英文是否一个字典里能查到的单词也好,还是随机的字符也好,都会当作一个整体来对待.至于为什么,你用查询” 电影dfdfdf下载”看看结果就知道了.当然如果查询中包含数字,也是如此办理.
到目前为止,一切很简单,也很清楚,bd怎么处理用户查询的呢?归纳如下:首先根据分割符号将查询分开,然后看看是否有重复的字符串,如果有,就抛弃多余的,只保留一个,接着判断是否有英文或者数字,如果有的话,把英文或者数字当作一个整体保留并把前后的中文切开
接着该干什么呢?该考虑分词的问题了.
二. 中文分词
首先,讲讲bd的分词时机或者条件问题,是否是个中文字符串bd就拿来切一下呢?非也,要想被bd的分词程序荣幸的切割一下也是要讲条件的,哪能是个字符串就切割啊?你当bd是卖锯条的么?
那么什么样的字符串才满足被切割的条件呢?简单说来,如果字符串只包含小于等于3个中文字符的话,那就保留不动,当字符串长度大于4个中文字符的时候,bd的分词程序才出马大干快上,把这个字符串肢解掉.
怎么证明呢?我们向bd提交”电影下载”,看看返回结果中标为红字的地方,不难看出来,查询已经被切割成<电影,下载>两个单词了,说明分词程序已经开工了,如果是比4个中文字符更长的字符串,那分词程序就更不客气了,一定大卸八块而后快.我们来看看三个字符的情况,提交查询”当然择”,看起来这个查询不伦不类,那是因为我希望看到这个字符串被切分为<当然,择>,返回结果365篇相关页面,翻到最后一页,发现标红的关键字都是”当然择”连续出现的情况,好像没有切分,但是还不确定,那么再提交人工分好的查询”当然 择”看看,返回结果1,090,000篇,基本上可以确定没有进行分词了,当然另外一种解释是:对于三个字符先切分,然后将切分后的结果当作一个短语查询,这样看到的效果和没有切分是相似的.但是我倾向于判断bd对于少于3个字符的串没有切分,奥卡姆不是说了么”如无必要,勿增实体”,干吗做无用功呢.那么如果没有切分,会有一个随之而来的问题,怎么从索引库里面提取未切分的字符串呢?这牵扯到索引的问题,我觉得bd应该采取了两套索引机制,一种是按照单词索引,一种是按照N-GRAM索引,至于索引的具体问题,以后在详细论述.
下面我们看看bd是采取的何种分词算法,现在分词算法已经算是比较成熟了,有简单的有复杂的,比如正向最大匹配,反向最大匹配,双向最大匹配,语言模型方法,最短路径算法等等,有兴趣的可以用GOOGLE去搜索一下以增加理解.这里就不展开说了.但是要记住一点的是:判断一个分词系统好不好,关键看两点,一个是消除歧义能力;一个是词典未登录词的识别比如人名,地名,机构名等.
那么bd用的是什么方法?我的判断是用双向最大匹配算法.至于怎么推理得出的,让我们一步步来看.当然,这里首先有个假设,bd不会采取比较复杂的算法,因为考虑到速度问题.
我们提交一个查询”毛泽东北京华烟云”,又一个不知所云的查询,尽管不知所云但是自有它的道理,我想看看bd的分词是如何消歧以及是否有词典未登录词的识别的功能,如果是正向最大匹配算法的话,那么输出应该是:”毛泽东/北京/华/烟云”,如果是反向最大匹配算法的话,那么输出应该是:”毛/泽/东北/京华烟云”,我们看看bd的分词结果:”毛泽东/北/京华烟云”,一个很奇怪的输出,跟我们的期望相差较多,但是从中我们可以获得如下信息:bd分词可以识别人名,也可以识别”京华烟云”,这说明有词典未登录词的识别的功能,我们可以假设分词过程分为两个阶段:第一阶段,先查找一个特殊词典,这个词典包含一些人名,部分地名以及一些普通词典没有的新词,这样首先将”毛泽东”解析出来,剩下了字符串”北京华烟云”,而”北/京华烟云”,可以看作是反向最大匹配的分词结果.这样基本说得通.为了证明这一点,我们提交查询”发毛泽东北”,我们期望两种分词结果,一个是正向最大匹配<发毛,泽,东北>,一个是上述假设的结果<发,毛泽东,北>,事实上bd输出是第二种情况,这样基本能确定bd分词采取了至少两个词典,一个是普通词典,一个是专用词典().而且是专用词典先切分,然后将剩余的片断交由普通词典来切分.
继续测验,提交查询”古巴比伦理”,如果是正向最大匹配,那么结果应该是<古巴比伦,理>,如果是反向最大匹配,那么结果应该是<古巴,比,伦理>,事实上bd的分词结果是<古巴比伦,理>,从这个例子看,好像用了正向最大匹配算法;此外还有一些例子表明好像是使用正向最大匹配的;但是且慢,我们看这个查询”北京华烟云”,正向最大匹配期望的结果是<北京,华,烟云>,而反向最大匹配期望的结果是<北,京华烟云>,事实上bd输出的是后者,这说明可能采用的反向最大匹配;从这点我们可以猜测bd采用的是双向最大匹配分词算法,如果正向和反向匹配分词结果一致当然好办,直接输出即可;但是如果两者不一致,正向匹配一种结果,反向匹配一种结果,此时该如何是好呢?从上面两个例子看,在这种情况下,bd采取最短路径方法,也就是切分的片断越少越好,比如<古巴,比,伦理>和<古巴比伦,理>相比选择后者,<北京,华,烟云>和<北,京华烟云>相比选择后者.还有类似的一些例子,这样基本可以解释这些输出结果.
② 什么叫正向最大匹配算法,反向最大匹配算法
分词算法里的吧
比如 我是一个好人
由于 词语很多,所以分词中先设定一个可能的,最长的词组的词数
比如说,我认定最长的词组是3个字,那在比对中,会将句子3个字为始进行比对
正向匹配算法好象是从左到右 反向区域算法是从右到左,具体忘记了
以 “我是一个好人” 为例
正向的顺序为
我是一
我是
我 ===> 得到一个词
是一个
是一
是 ===>得到一个词
一个好
一个===> 得到一个词
好人===>得到一个词
结果 我、是、一个、好人
反向算法
个好人
好人==> 好人
是一个
一个==> 一个
我是
是==> 是
我==> 我
结果 我、是、一个、好人
③ java web 怎么用solr
我们下载的Solr包后,进入Solr所在的目录,我们可以看到以下几个目录:build、client、dist、example、lib、site、src。下面分别对其进行介绍。
1) build:该目录是在ant build过程中生成的,其中包含了未被打包成jar或是war的class文件以及一些文档文件。
2) client:该目录包含了特定语言的Solr客户端API,使得使用其他语言的用户能通过HTTP用XML与Solr进行通话。现在该目录里面虽然包含javascript、python、ruby三个子目录,但是到目前为止只包含一部分的ruby的代码,其他语言仍是空的。另外,Solr的Java客户端称为SolrJ,其代码位于src/solrj目录下面。在之后的文章中我会详细介绍Solr客户端的使用。
3) dist:该目录包含build过程中产生的war和jar文件,以及相关的依赖文件。还记得上一篇文章中,我们在build 1.4版本的Solr源代码后需要部署example吗?其实就是将该目录下面的apache-solr-1.4.war部署到Jetty上面去,并重命名为solr.war。
4) example:这个目录实际上是Jetty的安装目录。其中包含了一些样例数据和一些Solr的配置。
其中一些子目录也比较重要,这里也对它们稍作介绍。
l example/etc:该目录包含了Jetty的配置,在这里我们可以将Jetty的默认端口从8983改为80端口。
l 将其中的8983端口换成80端口。注意更改端口后启动Jetty可能会提示你没有权限,你需要使用sudo java -jar start.jar来运行。
l example/multicore:该目录包含了在Solr的multicore中设置的多个home目录。在之后的文章中我会对其进行介绍。
l example/solr:该目录是一个包含了默认配置信息的Solr的home目录。
详见下面的“solr home说明”
l example/webapps:Jetty的webapps目录,该目录通常用来放置Java的Web应用程序。在Solr中,前面提到的solr.war文件就部署在这里。
5) lib:该目录包含了所有Solr的API所依赖的库文件。其中包括Lucene,Apache commons utilities和用来处理XML的Stax库。
6) site:该目录仅仅包含了Solr的官网的网页内容,以及一些教程的PDF文档。
7) src:该目录包含了Solr项目的整个源代码。这里对其各个子目录也做相应的介绍。
l src/java:该目录存放的是Solr使用Java编写的源代码。
l src/scripts:该目录存放的是配置Solr服务器的Unix BashShell脚本,在后面介绍多服务器配置中将会有重要的作用。
l src/solrj:前面提到过该目录存放的是Solr的Java版本的客户端代码。
l src/test:该目录存放的是测试程序的源代码和测试文件。
l src/webapp:该目录存放的是管理Solr的Web页面,包括Servlet和JSP文件,其构成了前面提到的WAR文件。管理Solr的JSP页面在web/admin目录下面,如果你有兴趣折腾Solr可以找到相应的JSP的页面对其进行设置
1.4.2 Solr home说明
所谓的Solr home目录实际上是一个运行的Solr实例所对应的配置和数据(Lucene索引)。在上一篇文章中我提到过在Solr的example/solr目录就是一个Solr用做示例的默认配置home目录。实际上example/multicore也是一个合法的Solr home目录,只不过是用来做mult-core设置的。那么我们来看看example/solr这个目录里面都有些什么。
example/solr目录下主要有以下一些目录和文件:
1) bin:如果你需要对Solr进行更高级的配置,该目录建议用来存放Solr的复制脚本。
2) conf :该目录下面包含了各种配置文件,下面列出了两个最为重要的配置文件。其余的.txt和.xml文件被这两个文件所引用,如用来对文本进行特殊的处理。
l conf/schema.xml:该文件是索引的schema,包含了域类型的定义以及相关联的analyzer链。
l conf/solrconfig.xml:该文件是Solr的主配置文件。
l conf/xslt:该目录包含了各种XSLT文件,能将Solr的查询响应转换成不同的格式,如:Atom/RSS等。
3) data:包含了Lucene的二进制索引文件。
4) lib:该目录是可选的。用来放置附加的Java JAR文件,Solr在启动时会自动加载该目录下的JAR文件。这就使得用户可以对Solr的发布版本(solr.war)进行扩展。如果你的扩展并不对Solr本身进行修改,那么就可以将你的修改部署到JAR文件中放到这里。
Solr是如何找到运行所需要的home目录的呢?
Solr首先检查名为solr.solr.home的Java系统属性,有几种不同的方式来设置该Java系统属性。一种不管你使用什么样的Java应用服务器或Servlet引擎都通用的方法是在调用Java的命令行中进行设置。所以,你可以在启动Jetty的时候显式地指定Solr的home目录java -Dsolr.solr.home=solr/ -jar start.jar。另一种通用的方法是使用JNDI,将home目录绑定到java:comp/env/solr/home。并向src/webapp/web/WEB-INF/web.xml添加以下一段代码:
1 <env-entry>
2 <env-entry-name>solr/home</env-entry-name>
3 <env-entry-value>solr/</env-entry-value>
4 <env-entry-type>java.lang.String</env-entry-type>
5 </env-entry>
实际上这段XML在web.xml文件中已经存在,你只需要把原来注释掉的xml取消注释,添加你所要指向的home目录即可。因为修改了web.xml文件,所以你需要运行antdist-war来重新打包之后再部署WAR文件。
最后,如果Solr的home目录既没有通过Java系统属性指定也没有通过JNDI指定,那么他将默认指向solr/。
在产品环境中,我们必须设置Solr的home目录而不是让其默认指向solr/。而且应该使用绝对路径,而不是相对路径,因为你有可能从不同的目录下面启动应用服务器。
注:Jetty 是一个开源的servlet容器,它为基于Java的web内容,例如JSP和servlet提供运行环境。Jetty是使用Java语言编写的,它的API以一组JAR包的形式发布。开发人员可以将Jetty容器实例化成一个对象,可以迅速为一些独立运行(stand-alone)的Java应用提供网络和web连接。
我们先从使用者的角度出发,最先看到的当然是servlet,因为Solr本身是个独立的网络应用程序,需要在Servlet容器中运行来提供服务,所以servlet是用户接触的最外层。我们看看org.apache.solr.servlet包。这个包很简单,只有两个类:SolrServlet和SolrUpdateServlet.我们很容易从类名中猜出这两个类的用途。
SolrServlet类继承HttpServlet类,只有四个方法:
· init()
· destroy()
· doGet()
· doPost()
SolrServlet类中除了普通的Java类对象(包括Servlet相关的)外,有四个Solr本身的类,还有一个Solr本身的异常。其中两个类和一个异常属于org.apache.solr.core包,两个类属于org.apache.solr.request包。属于core包的有:
· Config:
· SolrCore:
属于request包的有:
· SolrQueryResponse:
· QueryResponseWriter:
分析一下这个SolrServlet类。首先servlet会调用init()方法进行初始化:通过Context查找java:comp/env/solr/home来确定Solr的主目录(home),接着调用Config.setInstanceDir(home)方法设置这个实例的目录。然后通过SolrCore.getSolrCore()来获得一个SolrCore实例。destroy()方法将会在Servlet对象销毁时调用,仅仅调用core.close()关闭SolrCore实例。
当用户请求进来时doPost()简单地将任务交给doGet()完成,主要的任务由doGet()完成。分析一下doGet()方法:
1) 使用SolrCore和doGet()参数request生成一个SolrServletRequest对象(注意:这个SolrServletRequest类不是公开类,位于org.apache.solr.servlet包中,继承了SolrQueryRequestBase类,仅仅接受SolrCore和HttpServletRequest对象作为参数)
2) 然后SolrCore执行execute()方法(参数为SolrServletRequest和SolrQueryResponse)
由此可见,真正的处理核心是SolrCore的execute方法
④ 有没有利用斯坦福自然语言库处理英文信息的分词代码 java
众所周知,英文是以词为单位的,词和词之间是靠空格隔开,而中文是以字为单位,句子中所有的字连起来才能描述一个意思。例如,英文句子I am a student,用中文则为:“我是一个学生”。计算机可以很简单通过空格知道student是一个单词,但是不能很容易明白“学”、“生”两个字合起来才表示一个词。把中文的汉字序列切分成有意义的词,就是中文分词,有些人也称为切词。我是一个学生,分词的结果是:我 是 一个 学生。
中文分词技术属于自然语言处理技术范畴,对于一句话,人可以通过自己的知识来明白哪些是词,哪些不是词,但如何让计算机也能理解?其处理过程就是分词算法。
现有的分词算法可分为三大类:基于字符串匹配的分词方法、基于理解的分词方法和基于统计的分词方法。
1、基于字符串匹配的分词方法
这种方法又叫做机械分词方法,它是按照一定的策略将待分析的汉字串与一个“充分大的”机器词典中的词条进行配,若在词典中找到某个字符串,则匹配成功(识别出一个词)。按照扫描方向的不同,串匹配分词方法可以分为正向匹配和逆向匹配;按照不同长度优先匹配的情况,可以分为最大(最长)匹配和最小(最短)匹配;按照是否与词性标注过程相结合,又可以分为单纯分词方法和分词与标注相结合的一体化方法。常用的几种机械分词方法如下:
1)正向最大匹配法(由左到右的方向);
2)逆向最大匹配法(由右到左的方向);
3)最少切分(使每一句中切出的词数最小)。
还可以将上述各种方法相互组合,例如,可以将正向最大匹法和逆向最大匹法结合起来构成双向匹配法。由于汉语单字成词的特点,正向最小匹配和逆向最小匹配一般很少使用。一般说来,逆向匹配的切分精度略高于正向匹配,遇到的歧义现象也较少。统计结果表明,单纯使用正向最大匹配的错误率为1/169,单纯使用逆向最大匹配的错误率为1/245。但这种精度还远远不能满足实际的需要。实际使用的分词系统,都是把机械分词作为一种初分手段,还需通过利用各种其它的语言信息来进一步提高切分的准确率。
一种方法是改进扫描方式,称为特征扫描或标志切分,优先在待分析字符串中识别和切分出一些带有明显特征的词,以这些词作为断点,可将原字符串分为较小的串再来进机械分词,从而减少匹配的错误率。另一种方法是将分词和词类标注结合起来,利用丰富的词类信息对分词决策提供帮助,并且在标注过程中又反过来对分词结果进行检验、调整,从而极大地提高切分的准确率。
对于机械分词方法,可以建立一个一般的模型,在这方面有专业的学术论文,这里不做详细论述。
2、基于理解的分词方法
这种分词方法是通过让计算机模拟人对句子的理解,达到识别词的效果。其基本思想就是在分词的同时进行句法、语义分析,利用句法信息和语义信息来处理歧义现象。它通常包括三个部分:分词子系统、句法语义子系统、总控部分。在总控部分的协调下,分词子系统可以获得有关词、句子等的句法和语义信息来对分词歧义进行判断,即它模拟了人对句子的理解过程。这种分词方法需要使用大量的语言知识和信息。由于汉语语言知识的笼统、复杂性,难以将各种语言信息组织成机器可直接读取的形式,因此目前基于理解的分词系统还处在试验阶段。
3、基于统计的分词方法
从形式上看,词是稳定的字的组合,因此在上下文中,相邻的字同时出现的次数越多,就越有可能构成一个词。因此字与字相邻共现的频率或概率能够较好的反映成词的可信度。可以对语料中相邻共现的各个字的组合的频度进行统计,计算它们的互现信息。定义两个字的互现信息,计算两个汉字X、Y的相邻共现概率。互现信息体现了汉字之间结合关系的紧密程度。当紧密程度高于某一个阈值时,便可认为此字组可能构成了一个词。这种方法只需对语料中的字组频度进行统计,不需要切分词典,因而又叫做无词典分词法或统计取词方法。但这种方法也有一定的局限性,会经常抽出一些共现频度高、但并不是词的常用字组,例如“这一”、“之一”、“有的”、“我的”、“许多的”等,并且对常用词的识别精度差,时空开销大。实际应用的统计分词系统都要使用一部基本的分词词典(常用词词典)进行串匹配分词,同时使用统计方法识别一些新的词,即将串频统计和串匹配结合起来,既发挥匹配分词切分速度快、效率高的特点,又利用了无词典分词结合上下文识别生词、自动消除歧义的优点。
到底哪种分词算法的准确度更高,目前并无定论。对于任何一个成熟的分词系统来说,不可能单独依靠某一种算法来实现,都需要综合不同的算法。笔者了解,海量科技的分词算法就采用“复方分词法”,所谓复方,相当于用中中的复方概念,即用不同的才综合起来去医治疾病,同样,对于中文词的识别,需要多种算法来处理不同的问题。
⑤ seo问题,什么叫正向匹配什么叫逆向匹配举例说明
下面牛到家SEO介绍的分词算法中最简单的正向最大匹配和反向最大匹配。
这种两种方法都是机械分词方法,它是按照一定的策略将待分析的汉字串与一个”充分大的”机器词典中的词条进行配,若在词典中找到某个字符串,则匹配成功(识别出一个词)。
按照扫描方向的不同,串匹配分词方法可以分为正向匹配和逆向匹配;按照不同长度优先匹配的情况,可以分为最大(最长)匹配和最小(最短)匹配;按照是否与词性标注过程相结合,又可以分为单纯分词方法和分词与标注相结合的一体化方法。常用的几种机械分词方法如下:
1)正向最大匹配法(由左到右的方向);
2)逆向最大匹配法(由右到左的方向);
3)最少切分(使每一句中切出的词数最小)。
还可以将上述各种方法相互组合,例如,可以将正向最大匹配方法和逆向最大匹配方法结合起来构成双向匹配法。由于汉语单字成词的特点,正向最小匹配和逆向最小匹配一般很少使用。一般说来,逆向匹配的切分精度略高于正向匹配,遇到的歧义现象也较少。统计结果表明,单纯使用正向最大匹配的错误率为1/169,单纯使用逆向最大匹配的错误率为1/245。但这种精度还远远不能满足实际的需要。实际使用的分词系统,都是把机械分词作为一种初分手段,还需通过利用各种其它的语言信息来进一步提高切分的准确率。
一种方法是改进扫描方式,称为特征扫描或标志切分,优先在待分析字符串中识别和切分出一些带有明显特征的词,以这些词作为断点,可将原字符串分为较小的串再来进机械分词,从而减少匹配的错误率。另一种方法是将分词和词类标注结合起来,利用丰富的词
类信息对分词决策提供帮助,并且在标注过程中又反过来对分词结果进行检验、调整,从而极大地提高切分的准确率
定义比较抽象,举个例子来说明正向最大匹配和反向最大匹配。
例子:’今天来了许多新同事’
1.正向最大匹配方式,最大长度为5
今天来了许
今天来了
今天来
今天 ====》 得到一个词–今天
来了许多新
来了许多
来了许
来了
来 ====》 得到一个词–来
了许多新同
了许多新
了许多
了许
了 ====》 得到一个词–了
许多新同事
许多新同
许多新
许多 ====》得到一个词– 许多
新同事
新同
新 ====》得到一个词– 新
同事 ====》得到一个词– 同事
最后正向最大匹配的结果是:
/今天/来/了/许多/新/同事/
2.反向最大匹配方式,最大长度为5
许多新同事
多新同事
新同事
同事 ====》得到一个词– 同事
来了许多新
了许多新
许多新
多新
新 ====》得到一个词– 新
天来了许多
来了许多
了许多
许多 ====》得到一个词– 许多
今天来了
天来了
来了
了 ====》得到一个词– 了
今天来
天来
来 ====》得到一个词– 来
今天 ====》得到一个词– 今天
最后反向最大匹配的结果是:
/今天/来/了/许多/新/同事/
正向最大匹配和反向最大匹配的结果并不一定相同
例子:’我一个人吃饭’
1.正向最大匹配方式,最大长度为5
我一个人吃
我一个人
我一个
我一
我 ====》得到一个词– 我
一个人吃饭
一个人吃
一个人
一个 ====》得到一个词– 一个
人吃饭
人吃
人 ====》得到一个词– 人
吃饭 ====》得到一个词– 吃饭
最后正向最大匹配的结果是:
/我/一个/人/吃饭/
2.反向最大匹配方式,最大长度为5
一个人吃饭
个人吃饭
人吃饭
吃饭 ====》得到一个词– 吃饭
我一个人
一个人
个人 ====》得到一个词– 个人
我一
一 ====》得到一个词– 一
我 ====》得到一个词– 我
最后反向最大匹配的结果是:
/我/一/个人/吃饭/
这次两种方式的结果就不一致了。更多SEO知识请网络搜牛到家SEO
⑥ 中文分词中正向最大匹配算法的分词速度是多少准确率大概为多少
主要看你的词表结构了,最大词长的初始值,查词典的次数和匹配的次数,然后得出时间复杂度,原始hash算法复杂度没记错的话应该是2.89,11年看过一个文献,提出一种改进的算法时间复杂度是2.291……
另外,分词算法并不是原封不动的,比如有些搜索引擎的词表结构就采用tire树结构,这样不用设置最大词长,不过内存空间方面就要有取舍,甚至还有采用减少查典次数增加匹配次数的搜索引擎……
所以单纯的给你一个189.3m/M纯内存分词速度,但是这算法换个台更高配置的服务器却变成了497.6ms/M,这没有任何意义……
记得哪个文献上有人说,分词本身不是目的,而是后续处理过程的必要阶段,所以,除非你是研究算法的,否则单纯追求这东西的速度和准确率没什么太大意义
⑦ 定义五个数字,获取这五个数字中最大值,并输出最大值的绝对值。
本文是对整段话进行分词,首先将这段话根据标点符号断句,然后将每句话再进行分词输出。
?
package cn.nwsuaf.spilt;import java.io.BufferedReader;import java.io.FileReader;import java.io.IOException;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.Scanner;/** * 基于正逆双向最大匹配分词算法实现 * @author 刘永浪 * */public class WordSpilt { /** * 存储分词词典 */ private Map<String, Integer> map = new HashMap<String, Integer>(); /** * 最大切词长度,即为五个汉字 */ private int MAX_LENGTH = 5; /** * 构造方法中读取分词词典,并存储到map中 * * @throws IOException */ public WordSpilt() throws IOException { BufferedReader br = new BufferedReader(new FileReader("src/dict.txt")); String line = null; while ((line = br.readLine()) != null) { line = line.trim(); map.put(line, 0); } br.close(); } /** * 设置最大切词长度 * * @param max * 最大切词长度 */ public void setMaxLength(int max) { this.MAX_LENGTH = max; } /** * 获取当前最大切词长度,默认为5(5个汉字) * * @return 当前最大切词长度 */ public int getMaxLength() { return this.MAX_LENGTH; } /** * 最大匹配分词算法 * * @param spiltStr * 待切分的字符串 * @param leftToRight * 切分方向,true为从左向右,false为从右向左 * @return 切分的字符串 */ public List<String> spilt(String spiltStr, boolean leftToRight) { // 如果带切分字符串为空则返回空 if (spiltStr.isEmpty()) return null; // 储存正向匹配分割字符串 List<String> leftWords = new ArrayList<String>(); // 储存负向匹配分割字符串 List<String> rightWords = new ArrayList<String>(); // 用于取切分的字串 String word = null; // 取词的长度,初始化设置为最大值 int wordLength = MAX_LENGTH; // 分词操作中处于字串当前位置 int position = 0; // 已经处理字符串的长度 int length = 0; // 去掉字符串中多余的空格 spiltStr = spiltStr.trim().replaceAll("\\s+", ""); // 当待切分字符串没有被切分完时循环切分 while (length < spiltStr.length()) { // 如果还没切分的字符串长度小于最大值,让取词词长等于该词本身长度 if (spiltStr.length() - length < MAX_LENGTH) wordLength = spiltStr.length() - length; // 否则取默认值 else wordLength = MAX_LENGTH; // 如果是正向最大匹配,从spiltStr的position处开始切割 if (leftToRight) { position = length; word = spiltStr.substring(position, position + wordLength); } // 如果是逆向最大匹配,从spiltStr末尾开始切割 else { position = spiltStr.length() - length; word = spiltStr.substring(position - wordLength, position); } // 从当前位置开始切割指定长度的字符串 // word = spiltStr.substring(position, position + wordLength); // 如果分词词典里面没有切割出来的字符串,舍去一个字符 while (!map.containsKey(word)) { // 如果是单字,退出循环 if (word.length() == 1) { // 如果是字母或是数字要将连续的字母或者数字分在一起 if (word.matches("[a-zA-z0-9]")) { // 如果是正向匹配直接循环将后续连续字符加起来 if (leftToRight) { for (int i = spiltStr.indexOf(word, position) + 1; i < spiltStr .length(); i++) { if ((spiltStr.charAt(i) >= '0' && spiltStr .charAt(i) <= '9') || (spiltStr.charAt(i) >= 'A' && spiltStr .charAt(i) <= 'Z') || (spiltStr.charAt(i) >= 'a' && spiltStr .charAt(i) <= 'z')) { word += spiltStr.charAt(i); } else break; } } else { // 如果是逆向匹配,从当前位置之前的连续数字、字母字符加起来并翻转 for (int i = spiltStr.indexOf(word, position - 1) - 1; i >= 0; i--) { if ((spiltStr.charAt(i) >= '0' && spiltStr .charAt(i) <= '9') || (spiltStr.charAt(i) >= 'A' && spiltStr .charAt(i) <= 'Z') || (spiltStr.charAt(i) >= 'a' && spiltStr .charAt(i) <= 'z')) { word += spiltStr.charAt(i); if (i == 0) { StringBuffer sb = new StringBuffer(word); word = sb.reverse().toString(); } } else { // 翻转操作 StringBuffer sb = new StringBuffer(word); word = sb.reverse().toString(); break; } } } } break; } // 如果是正向最大匹配,舍去最后一个字符 if (leftToRight) word = word.substring(0, word.length() - 1); // 否则舍去第一个字符 else word = word.substring(1); } // 将切分出来的字符串存到指定的表中 if (leftToRight) leftWords.add(word); else rightWords.add(word); // 已处理字符串增加 length += word.length(); } // 如果是逆向最大匹配,要把表中的字符串调整为正向 if (!leftToRight) { for (int i = rightWords.size() - 1; i >= 0; i--) { leftWords.add(rightWords.get(i)); } } // 返回切分结果 return leftWords; } /** * 判断两个集合是否相等 * * @param list1 * 集合1 * @param list2 * 集合2 * @return 如果相等则返回true,否则为false */ public boolean isEqual(List<String> list1, List<String> list2) { if (list1.isEmpty() && list2.isEmpty()) return false; if (list1.size() != list2.size()) return false; for (int i = 0; i < list1.size(); i++) { if (!list1.get(i).equals(list2.get(i))) return false; } return true; } /** * 判别分词歧义函数 * * @param inputStr * 待切分字符串 * @return 分词结果 */ public List<String> resultWord(String inputStr) { // 分词结果 List<String> result = new ArrayList<String>(); // “左贪吃蛇”分词结果 List<String> resultLeft = new ArrayList<String>(); // “中贪吃蛇”(分歧部分)分词结果 List<String> resultMiddle = new ArrayList<String>(); // “右贪吃蛇”分词结果 List<String> resultRight = new ArrayList<String>(); // 正向最大匹配分词结果 List<String> left = new ArrayList<String>(); // 逆向最大匹配分词结果 List<String> right = new ArrayList<String>(); left = spilt(inputStr, true); /*System.out.println("正向分词结果:"); for (String string : left) { System.out.print(string + "/"); } System.out.println("\n逆向分词结果:");*/ right = spilt(inputStr, false); /*for (String string : right) { System.out.print(string + "/"); } System.out.println("\n双向分词结果:");*/ // 判断两头的分词拼接,是否已经在输入字符串的中间交汇,只要没有交汇,就不停循环 while (left.get(0).length() + right.get(right.size() - 1).length() < inputStr .length()) { // 如果正逆向分词结果相等,那么取正向结果跳出循环 if (isEqual(left, right)) { resultMiddle = left; break; } // 如果正反向分词结果不同,则取分词数量较少的那个,不用再循环 if (left.size() != right.size()) { resultMiddle = left.size() < right.size() ? left : right; break; } // 如果以上条件都不符合,那么实行“贪吃蛇”算法 // 让“左贪吃蛇”吃下正向分词结果的第一个词 resultLeft.add(left.get(0)); // 让“右贪吃蛇”吃下逆向分词结果的最后一个词 resultRight.add(right.get(right.size() - 1)); // 去掉被“贪吃蛇”吃掉的词语 inputStr = inputStr.substring(left.get(0).length()); inputStr = inputStr.substring(0, inputStr.length() - right.get(right.size() - 1).length()); // 清理之前正逆向分词结果,防止造成干扰 left.clear(); right.clear(); // 对没被吃掉的字符串重新开始分词 left = spilt(inputStr, true); right = spilt(inputStr, false); } // 循环结束,说明要么分词没有歧义了,要么"贪吃蛇"从两头吃到中间交汇了 // 如果是在中间交汇,交汇时的分词结果,还要进行以下判断: // 如果中间交汇有重叠了: // 正向第一个分词的长度 + 反向最后一个分词的长度 > 输入字符串总长度,就直接取正向的 if (left.get(0).length() + right.get(right.size() - 1).length() > inputStr .length()) resultMiddle = left; // 如果中间交汇,刚好吃完,没有重叠: // 正向第一个分词 + 反向最后一个分词的长度 = 输入字符串总长度,那么正反向一拼即可 if (left.get(0).length() + right.get(right.size() - 1).length() == inputStr .length()) { resultMiddle.add(left.get(0)); resultMiddle.add(right.get(right.size() - 1)); } // 将没有歧义的分词结果添加到最终结果result中 for (String string : resultLeft) { result.add(string); } for (String string : resultMiddle) { result.add(string); } // “右贪吃蛇”存储的分词要调整为正向 for (int i = resultRight.size() - 1; i >= 0; i--) { result.add(resultRight.get(i)); } return result; } /** * 将一段话分割成若干句话,分别进行分词 * * @param inputStr * 待分割的一段话 * @return 这段话的分词结果 */ public List<String> resultSpilt(String inputStr) { // 用于存储最终分词结果 List<String> result = new ArrayList<String>(); // 如果遇到标点就分割成若干句话 String regex = "[,。;!?]"; String[] st = inputStr.split(regex); // 将每一句话的分词结果添加到最终分词结果中 for (String stri : st) { List<String> list = resultWord(stri); result.addAll(list); } return result; } public static void main(String[] args) { // example:过来看房价贵不贵?乒乓球拍卖完了 Scanner input = new Scanner(System.in); String str = input.nextLine(); WordSpilt wordSpilt = null; try { wordSpilt = new WordSpilt(); } catch (IOException e) { e.printStackTrace(); } List<String> list = wordSpilt.resultWord(str); for (String string : list) { System.out.print(string + "/"); } }}
⑧ 求 JAVA 字符串匹配 完美算法
只需要实例化 类Matching 设置参数 并调用m.getIndex()方法就OK 请测试... public class Test18{
public static void main(String[] args){
Matching m = new Matching();
m.setOrgStr("ALSKLSHFKDLLS");
m.setSubStr("LS");
System.out.println(m.getIndex());
}
}
class Matching{
String orgStr ="";
String subStr ="";
public void setOrgStr(String orgStr){
this.orgStr = orgStr;
}
public void setSubStr(String subStr){
this.subStr = subStr;
}
public String getIndex(){
StringBuffer sb = new StringBuffer("{");
//根据关键字subStr来拆分字符串orgStr所得的字符串数组
String[] sub = orgStr.split(subStr);
int keyLength = subStr.length(); //关键字长度
int keySize = 0; //关键字个数
int subSize = sub.length; //子字符串个数
int subLength = 0; //子字符串长度
if(!orgStr.endsWith(subStr)){
keySize = subSize-1;
}else
keySize = subSize; int[] index = new int[keySize];//关键字下标数组
for(int i=0;i<keySize;i++){
subLength = sub[i].length();
if(i==0){
index[i]=subLength;
}else
index[i]=index[i-1]+subLength+keyLength;
}
if(keySize>0){
int l = keySize-1;
for(int i=0;i<l;i++){
sb.append(index[i]+",");
}
sb.append(index[l]);//最后一个关键字下标
}else{
sb.append("NULL");
}
sb.append("}");
return sb.toString();
}
}