java tire树_基于Tire树和最大概率法的中文分词功能的Java实现

对于分词系统的实现来说,主要应集中在两方面的考虑上:一是对语料库的组织,二是分词策略的制订。

1.   Tire树

Tire树,即字典树,是通过字串的公共前缀来对字串进行统计、排序及存储的一种树形结构。其具有如下三个性质:

1)      根节点不包含字符(或汉字),除根节点以外的每个节点只能包含一个字符(汉字)

2)      从根节点到任一节点的路径上的所有节点中的字符(汉字)按顺序排列的字符串(词组)就是该节点所对应的字符串(词组)

3)      每个节点的所有直接子节点包含的字符(汉字)各不相同

上述性质保证了从Tire树中查找任意字符串(词组)所需要比较的次数尽可能最少,以达到快速搜索语料库的目的。

如下图所示的是一个由词组集生成的Tire树的子树:

b9810d688aafbbefafd75cf9fc6ee78e.png

可见,从子树的根节点“一”开始,任意一条路径都能组成一个以“一”开头的词组。而在实际应用中,需要给每个节点附上一些数据属性,如词频,因而可以用这些属性来区别某条路径上的字串是否是一个词组。如,节点“上”的词频为-1,那么“一上”就不是一个词组。

如下的代码是Tire树的Java实现:

package chn.seg;

import java.util.HashMap;

import java.util.Map;

public class TireNode {

private String character;

private int frequency = -1;

private double antilog = -1;

private Map children;

public String getCharacter() {

return character;

}

public void setCharacter(String character) {

this.character = character;

}

public int getFrequency() {

return frequency;

}

public void setFrequency(int frequency) {

this.frequency = frequency;

}

public double getAntilog() {

return antilog;

}

public void setAntilog(double antilog) {

this.antilog = antilog;

}

public void addChild(TireNode node) {

if (children == null) {

children = new HashMap();

}

if (!children.containsKey(node.getCharacter())) {

children.put(node.getCharacter(), node);

}

}

public TireNode getChild(String ch) {

if (children == null || !children.containsKey(ch)) {

return null;

}

return children.get(ch);

}

public void removeChild(String ch) {

if (children == null || !children.containsKey(ch)) {

return;

}

children.remove(ch);

}

}

2.   最大概率法(动态规划)

最大概率法是中文分词策略中的一种方法。相较于最大匹配法等策略而言,最大概率法更加准确,同时其实现也更为复杂。

基于动态规划的最大概率法的核心思想是:对于任意一个语句,首先按语句中词组的出现顺序列出所有在语料库中出现过的词组;将上述词组集中的每一个词作为一个顶点,加上开始与结束顶点,按构成语句的顺序组织成有向图;再为有向图中每两个直接相连的顶点间的路径赋上权值,如A→B,则AB间的路径权值为B的费用(若B为结束顶点,则权值为0);此时原问题就转化成了单源最短路径问题,通过动态规划解出最优解即可。

如句子“今天下雨”,按顺序在语料库中存在的词组及其费用如下:

今,a

今天,b

天,c

天下,d

下,e

下雨,f

雨,g

则可以生成如下的加权有向图:

1b3f2e619ab1d02695359b358af568aa.png

显而易见,从“Start”到“End”的单源路径最优解就是“今天下雨”这个句子的分词结果。

那么,作为权值的费用如何计算呢?对于最大概率法来说,要求的是词组集在语料库中出现的概率之乘积最大。对应单源最短路径问题的费用来说,

费用 = log( 总词频 / 某一词组词频 )

通过上述公式就可以把“最大”问题化为“最小”问题,“乘积”问题化为“求和”问题进行求解了。

如下的代码是基于动态规划的最大概率法的Java实现:

package chn.seg;

import java.io.BufferedReader;

import java.io.File;

import java.io.FileInputStream;

import java.io.IOException;

import java.io.InputStreamReader;

import java.util.ArrayList;

import java.util.List;

public class ChnSeq {

private TireNode tire = null;

public void init() throws IOException, ClassNotFoundException {

File file = new File("data" + File.separator + "dict.txt");

if (!file.isFile()) {

System.err.println("语料库不存在!终止程序!");

System.exit(0);

}

BufferedReader in = new BufferedReader(

new InputStreamReader(new FileInputStream(file), "utf-8"));

String line = in.readLine();

int totalFreq = Integer.parseInt(line);

tire = new TireNode();

while ((line = in.readLine()) != null) {

String[] segs = line.split(" ");

String word = segs[0];

int freq = Integer.parseInt(segs[1]);

TireNode root = tire;

for (int i = 0; i < word.length(); i++) {

String c = "" + word.charAt(i);

TireNode node = root.getChild(c);

if (node == null) {

node = new TireNode();

node.setCharacter(c);

root.addChild(node);

}

root = node;

}

root.setFrequency(freq);

root.setAntilog(Math.log((double)totalFreq / freq));

}

in.close();

}

public TireNode getTire() {

return tire;

}

public TireNode getNodeByWord(String word) {

if (tire == null) {

System.err.println("需要先初始化ChnSeq对象!");

return null;

}

TireNode node = tire;

for (int i = 0; i < word.length(); i++) {

String ch = word.charAt(i) + "";

if (node == null) {

break;

} else {

node = node.getChild(ch);

}

}

return node;

}

private class Segment {

public String word;

public String endChar;

public String lastChar;

public double cost;

public final static String START_SIGN = "<< STARTING >>";

public final static String END_SIGN = "<< ENDING >>";

}

private List preSegment(String sentence) {

List segs = new ArrayList();

Segment terminal = new Segment();

terminal.word = Segment.START_SIGN;

terminal.endChar = Segment.START_SIGN;

terminal.lastChar = null;

segs.add(terminal);

for (int i = 0; i < sentence.length(); i++) {

for (int j = i + 1; j <= sentence.length(); j++) {

String word = sentence.substring(i, j);

TireNode tnode = this.getNodeByWord(word);

if (tnode == null) {

break;

}

if (tnode.getFrequency() <= 0) {

continue;

}

Segment seg = new Segment();

seg.word = word;

seg.endChar = word.substring(word.length() - 1, word.length());

if (i == 0) {

seg.lastChar = Segment.START_SIGN;

} else {

seg.lastChar = sentence.substring(i - 1, i);

}

seg.cost = tnode.getAntilog();

segs.add(seg);

}

}

terminal = new Segment();

terminal.word = Segment.END_SIGN;

terminal.endChar = Segment.END_SIGN;

terminal.lastChar = sentence.substring(sentence.length() - 1, sentence.length());

segs.add(terminal);

return segs;

}

private String[] dynamicSegment(List segs) {

final double INFINITE = 9999999;

if (segs == null || segs.size() == 0) {

return null;

}

int n = segs.size();

double[][] costs = new double[n][n];

for (int i = 0; i < n; i++) {

for (int j = 0; j < n; j++) {

costs[i][j] = INFINITE;

}

}

for (int i = 0; i < n; i++) {

String endChar = segs.get(i).endChar;

for (int j = 0; j < n; j++) {

String lastChar = segs.get(j).lastChar;

if (lastChar != null && lastChar.equals(endChar)) {

costs[i][j] = segs.get(j).cost;

}

}

}

int sp = 0; // starting point

int fp = n - 1; // finishing point

double[] dist = new double[n];

List> sPaths = new ArrayList>();

List list = new ArrayList();

for (int i = 0; i < n; i++) {

dist[i] = costs[sp][i];

if (sp != i) {

list.add(i);

}

if (dist[i] < INFINITE) {

List spa = new ArrayList();

sPaths.add(spa);

} else {

sPaths.add(null);

}

}

while (!list.isEmpty()) {

Integer minIdx = list.get(0);

for (int i: list) {

if (dist[i] < dist[minIdx]) {

minIdx = i;

}

}

list.remove(minIdx);

for (int i = 0; i < n; i++) {

if (dist[i] > dist[minIdx] + costs[minIdx][i]) {

dist[i] = dist[minIdx] + costs[minIdx][i];

List tmp = new ArrayList(sPaths.get(minIdx));

tmp.add(minIdx);

sPaths.set(i, tmp);

}

}

}

String[] result = new String[sPaths.get(fp).size()];

for (int i = 0; i < sPaths.get(fp).size(); i++) {

result[i] = segs.get(sPaths.get(fp).get(i)).word;

}

return result;

}

public String[] segment(String sentence) {

return dynamicSegment(preSegment(sentence));

}

}

3.   测试代码

package chn.seg;

import java.io.IOException;

public class Main {

public static void main(String[] args) throws ClassNotFoundException, IOException {

ChnSeq cs = new ChnSeq();

cs.init();

String sentence = "生活的决定权也一直都在自己手上";

String[] segs = cs.segment(sentence);

for (String s: segs) {

System.out.print(s + "\t");

}

}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 给定一棵 $n$ 个节点的二叉,每个节点上有一个整数。求二叉中某个子内的最大和最小数的异或和。 输入格式 第一行包含整数 $n$。 以下 $n$ 行每行描述二叉的一个节点,格式为 ID val lson rson,其中 ID 表示节点编号(范围 1∼n),val 是节点上的整数,lson 和 rson 分别表示该节点的左儿子和右儿子的编号。若节点没有左儿子或右儿子,则对应位置为 0。 输出格式 一个整数,表示异或和。 数据范围 1≤n≤10^5,−10^9≤val≤10^9 输入样例1: 5 1 1 2 3 2 2 4 5 3 3 0 0 4 4 0 0 5 5 0 0 输出样例1: 7 输入样例2: 9 1 1 2 3 2 2 4 5 3 3 6 7 4 4 8 9 5 5 0 0 6 6 0 0 7 7 0 0 8 8 0 0 9 9 0 0 输出样例2: 8 算法 (Trie ,后缀数组,分块) $O(n log n + n \log^2 mod)$ C++ 代码 用Trie实现 ``` #include<iostream> #include<cstring> #include<algorithm> #include<cstdio> #include<stdlib.h> #include<time.h> using namespace std; #define f(a,b,c) for(a=b;a<=c;a++) #define g(a,b,c) for(a=b;a>=c;a--) #define ll long long const ll INF=2e9; const int N=1e5+7,M=1e6+7; inline int read(){ int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9') {if(ch=='-') f=-1; ch=getchar();} while(ch>='0'&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();} return x*f; } int e[M],ne[M],h[N],idx; void add(int a,int b){e[idx]=b;ne[idx]=h[a];h[a]=idx++;} int fa[N],dep[N],sz[N],son[N],wson[N],dfn[N],dnt,ndfn[N],tp[N]; struct Trie{ int son[2],cnt; }tr[N*32]; int rt[N]; int cnt; void update(int u,int k){ int p=rt[u],q=rt[cnt++]; int i,j; rt[u]=q,tr[q]=tr[p]; f(i,30,0){ tr[q].cnt=tr[p].cnt+1; j=(k>>i)&1; if(tr[p].son[j]==0){ tr[q].son[0]=tr[p].son[0],tr[q].son[1]=tr[p].son[1]; tr[q].son[j]=cnt++,tr[tr[q].son[j]]=(Trie){0,0}; } p=tr[p].son[j],q=tr[q].son[j]; } tr[q].cnt=tr[p].cnt+1; } int query(int u,int v,int k){ int p=rt[u],q=rt[v],res=0; int i,j; f(i,30,0){ j=(k>>i)&1; if(tr[tr[q].son[j^1]].cnt>tr[tr[p].son[j^1]].cnt) res=res|(1<<i),q=tr[q].son[j^1],p=tr[p].son[j^1]; else q=tr[q].son[j],p=tr[p].son[j]; } return res; } void dfs1(int u,int la){ dep[u]=dep[la]+1,fa[u]=la,sz[u]=1; int i,v,maxn=-1; for(i=h[u];~i;i=ne[i]){ v=e[i]; if(v==la) continue; dfs1(v,u); sz[u]+=sz[v]; if(sz[v]>maxn) maxn=sz[v],son[u]=v; } } void dfs2(int u){ int i,v; dfn[u]=++dnt,ndfn[dnt]=u; if(son[u]) wson[son[u]]=dfn[son[u]],tp[son[u]]=tp[u],dfs2(son[u]); else return; for(i=h[u];~i;i=ne[i]){ v=e[i]; if(v==fa[u]||v==son[u]) continue; wson[v]=dfn[v],tp[v]=v,dfs2(v); } } int find(int u,int v){ int f1=tp[u],f2=tp[v]; int ans=0; while(f1!=f2){ if(dep[f1]<dep[f2]) swap(u,v),swap(f1,f2); ans=max(ans,query(wson[f1],wson[u],v)); u=fa[f1],f1=tp[u]; } if(u==v) return ans; if(dep[u]<dep[v]) swap(u,v); return max(ans,query(wson[son[v]],wson[u],v)); } int main(){ int n=read(); memset(h,-1,sizeof h); int i,a,b,c,ans1=0x3f3f3f3f,ans2=-0x3f3f3f3f; f(i,1,n){ a=read(),b=read(),c=read(); if(c){ add(a,c),add(c,a); add(b,c),add(c,b); } else{ add(a,b),add(b,a); } } dfs1(1,0),wson[1]=dfn[1],tp[1]=1,dfs2(1); rt[0]=cnt++,tr[rt[0]]=(Trie){0,0}; f(i,1,n){ a=ndfn[i]; rt[a]=rt[fa[a]]; update(a,read()); } f(i,1,n){ a=ndfn[i]; ans1=min(ans1,query(rt[1],rt[a],read())); ans2=max(ans2,query(rt[1],rt[a],read())); } cout<<ans1+ans2; return 0; } ``` '''
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值