中科院分词系统分析ICTCLAS
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
// 这里放入优先级队列的是前向节点和长度,相当于是路径,而不 是长度的值的列表,与后面表达的意思不同。
for(i=0;i<m_nValueKind;i++)
...{ //
如果起点>0,即判断起点是不是第一个节点。
if(nPreNode>0)//Push the weight and the pre node
的代码和分析帖在下面,分析都写在注释里了。
在具体开始之前,我先明确一个东西,在中科院的论文里称求解多个最优路径问题为 N 最 短路径问题(N-Shortest Paths),如果你 google 你会发现没有多少有用的结果,其实不然。不 知道是不是作者不了解国际上对该问题的讨论,这个问题应该称为 k shortest path(即 K 最 短路径问题)。这个问题也已经有了不错的解法,David Eppstein 分别在 1994 年和 1997 年已 经给出了大约复杂度为 O(m + n log n + kn)的解法。而中科院论文里面的解法的复杂度还是 比较高的:O(n*N*k)。(两个复杂度的字母含义不同,定义请看原论文)。所以,如果可能, 再次实现 ICTCLAS 的算法的朋友可以考虑抛开中科院的求 k shortest path 的解法,而使用国 际上比较流行的解法。
CQueue 元素有一个权重 eWeight,这个权重如果不为 0(或者说互相之间不等),那么 CQueue 此时的含义是按照权重由小到大排序的优先级队列。
如果 CQueue 的所有元素的 eWeight 都相等,(在 ICTCLAS 代码里就是都为 0),此时 CQueue 就演变为 FILO 的 Stack,栈。
m_pWeight[nCurNode-1][i]=eWeight;
else if(m_pWeight[nCurNode-1][i]<eWeight)//Next queue
循环。
...{ // //
否则,如果起点到原点的长度小于当前边的长度 递增索引值,换到下一套选择值去。如果到达了最大索引值就退出
//
while(i<m_nValueKind&&queWork.Pop(&nPreNode,&nIndex,&eWeight)!
Fra Baidu bibliotek
=-1)
...{//Set the current node weight and parent
//
从以长度为优先级的队列中,提取第一个(最短的)记
录。
//
将该记录的起点给 nPreNode,索引给 nIndex,长度给
// 换到下一条边。 pEdgeList=pEdgeList->next;
}//end while
//Now get the result queue which sort as weight.
//Set the current node information
// 将起点到原点的长度,对于每个索引值都初始化为无穷。
i++;//Go next queue and record next weight
//
既然这里有是否会大于最大索引值的判断,何必在 while 条件里
面加那个条件呢?
if(i==m_nValueKind)//Get the last position
break; // 将起点到原点的长度,下一个索引值(i+1),设为队列中元素的长度。
接下来我把 NShortPath 中的最主要的三个函数
int Output(int **nResult,bool bBest,int *npCount); int ShortPath(); void GetPaths(unsigned int nNode,unsigned int nIndex,int **nResult=0,bool bBest=false);
// 因此这个数值的变化规律是初始位无穷大,第一次赋值 为最小值,然后逐渐增大。
} // 将(起点,索引值)压入起点的父节点的队列中去
m_pParent[nCurNode-1][i].Push(nPreNode,nIndex);
}
}//end for
return 1; }
//bBest=true: only get one best result and ignore others
因此这个 CQueue 才会有 Push 和 Pop 两种插入和删除元素的命名。呵呵,挂着羊头卖的是 狗肉,还是两只狗。对于 C#、C++、Java 来说,类库里面都有现成的优先级队列和栈的实 现,而且可以用
List<T> 重载小于号(C++)、重载 CompareTo()(C#,Java) List.Sort()
ICTCLAS 的命名好像没有正统的学过数据结构一样,对于数据结构的命名非常富有想象力, 完全没有按照数据结构上大家公认的术语命名,所以给代码的读者带来很大的迷惑性。所以 我们在看名字的时候一定要抛开名字看实现,看本质,看他们到底是个啥。呵呵。
首先就是 CQueue 的问题,CQueue 虽然叫 Queue,但是它不是 FIFO 的 Queue。那它是什么 呢?CQueue 是优先级队列 Priority Queue 和 Stack 的杂交。但是没有半点 FIFO 的 Queue 的 概念在里面。
infomation
...{
// 起点不是第一个节点。
// 判断起点到原点的总长度在索引值为 i 的时候是不是无穷大。
//
如果无穷大了,就说明前一个点已经
无法到达了,说明没有更多到前面节点的路径了
北京即刻搜索博客 www.jike.bj.cn
北京即刻搜索博客 www.jike.bj.cn
// 也不必继续向优先级队列中放入点了。 if(m_pWeight[nPreNode-1][i]==INFINITE_VALUE)
北京即刻搜索博客 www.jike.bj.cn
两天我开始看 ICTCLAS 的实现代码了,和吕震宇的感觉完全一样,代码真的是糟糕透顶, 呵呵,非常同情吕震宇和 Sinboy 能够那么认真地把那些代码读完。有了你们辛苦、认真的 分析工作,让我更容易的读懂 ICTCLAS 的代码了,谢谢了。阅读过程中注意到了他们分析 中有些地方有点小错误。
eWeight=m_apCost->GetElement(-1,nCurNode,0,&pEdgeList);//Get all the
edges
while(pEdgeList!=0 && pEdgeList->col==nCurNode)
...{ // nPreNode 是当前边的起点
nPreNode=pEdgeList->row; // eWeight 是当前边的长度
北京即刻搜索博客 www.jike.bj.cn
北京即刻搜索博客 www.jike.bj.cn
eWeight
// 如果起点到原点的长度为无穷。(这在第一次循环的时候显然是无穷) // 就将这个长度设为最短边的长度。
if(m_pWeight[nCurNode-1][i]==INFINITE_VALUE)
// 在这里面的 i 表达的是长度的值的索引,并不代表不同的路径,同一个 i
可能对应多个路径。
// 这个循环过后,m_pWeight[nCurNode-1][] 为可能存在的前 m_nValueKind
个长度值。
// 并且把前 m_nValueKind 个路径压入 m_nParent 对应的队列中。
北京即刻搜索博客 www.jike.bj.cn
北京即刻搜索博客 www.jike.bj.cn
// 循环从 1 开始,即从第二个节点开始。遍历所有节点。
for(;nCurNode<m_nVertex;nCurNode++)
...{
CQueue queWork; // 有 CNShortPath 调用的上下文可知,入口的 m_apCost 为列优先排序的 CDynamicArray。 // 换句话说就是:
break; // 将起点,索引值 i,和终点到原点的总长度压入优先级队列。 queWork.Push(nPreNode,i,eWeight +m_pWeight[nPreNode-1][i]); } else ...{ // 起点为第一个节点。 // 将起点,索引值 i,和当前边的长度压入优先级队列 queWork.Push(nPreNode,i,eWeight); break; } }//end for
BTW: 问一下,吕震宇,你有什么比较可爱点的称呼么?呵呵,我这么直呼大名在中文的习 惯里似乎不太礼貌。:)
int CNShortPath::ShortPath() ...{
unsigned int nCurNode=1,nPreNode,i,nIndex; ELEMENT_TYPE eWeight; PARRAY_CHAIN pEdgeList;
for(i=0;i<m_nValueKind;i++)
...{
m_pWeight[nCurNode-1][i]=INFINITE_VALUE;
}
//memset((void *),(int),sizeof(ELEMENT_TYPE)*);
//init the weight
i=0;
//
进行循环,索引值小于想要的索引值时,并且优先级队列不为空。
m_pWeight[nCurNode-1][i]=eWeight;
}else...{ // 如果起点到原点的长度 == 队列中的长度, 那么只向
当前节点,当前索引的父节点中插入一个配对。
//
从小到大。
// 如果起点到原点的长度 > 队列中的长度? // 这是不可能出现的,因为这个数值在队列中是有序的。
对于 DynamicArray,我们也完全可以用 List<>.Sort()的方式来实现,对于 C++来说,我们需 要定义 2 个 functor,分别是起点优先比较和终点优先比较。对于 Java 和 C#也有类似的定义 比较函数的办法。因此这个 DynamicArray(),可以扔掉了。没必要用这么一个奇怪的东西。
作者为了能够让以后调用的时候方便,对于起点和终点进行排序(或者说维护了顺序)。对 起点排序,就是代码中所谓的 RowFirst,对于终点进行排序就是 ColumnFirst。
北京即刻搜索博客 www.jike.bj.cn
北京即刻搜索博客 www.jike.bj.cn
那为何作者叫 DynamicArray 呢?其实也不难想象,首先是因为邻接表实际上就是边的一个 列表,也可以看为数组。但是边的数量是在变化的,而不是最开始就可以知道的。因此这个 数组是动态的。于是就叫动态数组了。。。。汗。
来代替优先级队列实现和并且具有和作者一样的 Iterator 的功能。那个 CQueue 完全可以省 略掉。
然后是 DynamicArray。动态数组?非也。这个是用来表示稀疏图的邻接表,每一个元素表 示的是图上的一条边。对于非稀疏的图往往喜欢用 NxN 的数组来表示 N 个节点的连接关系。 而对于稀疏图来说,无疑会浪费大量的空间,于是往往采用记录邻接两点的边的方式来记录 图。
// list<Edge> m_apCost;
// list.sort(m_apCost, less_column_first()); // 下面这行代码是将该列的边的起始链表元素赋予 pEdgeList,以方便遍历。 // 算法上的含义是取得图上终点为 nCurNode 的所有边,并将第一条边放入 pEdgeList 进行对所有边的遍历。
// 我们保留一个为 m_nValueKind 大小的数组,记录这些可能的配 对,而不仅仅是保留最小的。
// 下面这个循环就是将所有可能的组合放到优先级队列中,然后将 来可以从优先级队列中选取前 m_nValueKind。
// 这里循环范围限定到 m_nValueKind 主要是考虑以后所需要的不 会超过这么多个值。
eWeight=pEdgeList->value;//Get the value of edges // 对于 dijkstra 算法来说,我们需要知道当前节点(终点)的通过
不同的前方的点到原点的距离
// 并且从中知道最短的路径,然后我们会更新当前节点的父节点和 当前节点到原点的距离。
// 在这个修改后的多最短路径算法中,我们将(当前节点的父节点, 当前节点通过该父节点到原点的距离)视为一个配对
for(i=0;i<m_nValueKind;i++)
...{ //
如果起点>0,即判断起点是不是第一个节点。
if(nPreNode>0)//Push the weight and the pre node
的代码和分析帖在下面,分析都写在注释里了。
在具体开始之前,我先明确一个东西,在中科院的论文里称求解多个最优路径问题为 N 最 短路径问题(N-Shortest Paths),如果你 google 你会发现没有多少有用的结果,其实不然。不 知道是不是作者不了解国际上对该问题的讨论,这个问题应该称为 k shortest path(即 K 最 短路径问题)。这个问题也已经有了不错的解法,David Eppstein 分别在 1994 年和 1997 年已 经给出了大约复杂度为 O(m + n log n + kn)的解法。而中科院论文里面的解法的复杂度还是 比较高的:O(n*N*k)。(两个复杂度的字母含义不同,定义请看原论文)。所以,如果可能, 再次实现 ICTCLAS 的算法的朋友可以考虑抛开中科院的求 k shortest path 的解法,而使用国 际上比较流行的解法。
CQueue 元素有一个权重 eWeight,这个权重如果不为 0(或者说互相之间不等),那么 CQueue 此时的含义是按照权重由小到大排序的优先级队列。
如果 CQueue 的所有元素的 eWeight 都相等,(在 ICTCLAS 代码里就是都为 0),此时 CQueue 就演变为 FILO 的 Stack,栈。
m_pWeight[nCurNode-1][i]=eWeight;
else if(m_pWeight[nCurNode-1][i]<eWeight)//Next queue
循环。
...{ // //
否则,如果起点到原点的长度小于当前边的长度 递增索引值,换到下一套选择值去。如果到达了最大索引值就退出
//
while(i<m_nValueKind&&queWork.Pop(&nPreNode,&nIndex,&eWeight)!
Fra Baidu bibliotek
=-1)
...{//Set the current node weight and parent
//
从以长度为优先级的队列中,提取第一个(最短的)记
录。
//
将该记录的起点给 nPreNode,索引给 nIndex,长度给
// 换到下一条边。 pEdgeList=pEdgeList->next;
}//end while
//Now get the result queue which sort as weight.
//Set the current node information
// 将起点到原点的长度,对于每个索引值都初始化为无穷。
i++;//Go next queue and record next weight
//
既然这里有是否会大于最大索引值的判断,何必在 while 条件里
面加那个条件呢?
if(i==m_nValueKind)//Get the last position
break; // 将起点到原点的长度,下一个索引值(i+1),设为队列中元素的长度。
接下来我把 NShortPath 中的最主要的三个函数
int Output(int **nResult,bool bBest,int *npCount); int ShortPath(); void GetPaths(unsigned int nNode,unsigned int nIndex,int **nResult=0,bool bBest=false);
// 因此这个数值的变化规律是初始位无穷大,第一次赋值 为最小值,然后逐渐增大。
} // 将(起点,索引值)压入起点的父节点的队列中去
m_pParent[nCurNode-1][i].Push(nPreNode,nIndex);
}
}//end for
return 1; }
//bBest=true: only get one best result and ignore others
因此这个 CQueue 才会有 Push 和 Pop 两种插入和删除元素的命名。呵呵,挂着羊头卖的是 狗肉,还是两只狗。对于 C#、C++、Java 来说,类库里面都有现成的优先级队列和栈的实 现,而且可以用
List<T> 重载小于号(C++)、重载 CompareTo()(C#,Java) List.Sort()
ICTCLAS 的命名好像没有正统的学过数据结构一样,对于数据结构的命名非常富有想象力, 完全没有按照数据结构上大家公认的术语命名,所以给代码的读者带来很大的迷惑性。所以 我们在看名字的时候一定要抛开名字看实现,看本质,看他们到底是个啥。呵呵。
首先就是 CQueue 的问题,CQueue 虽然叫 Queue,但是它不是 FIFO 的 Queue。那它是什么 呢?CQueue 是优先级队列 Priority Queue 和 Stack 的杂交。但是没有半点 FIFO 的 Queue 的 概念在里面。
infomation
...{
// 起点不是第一个节点。
// 判断起点到原点的总长度在索引值为 i 的时候是不是无穷大。
//
如果无穷大了,就说明前一个点已经
无法到达了,说明没有更多到前面节点的路径了
北京即刻搜索博客 www.jike.bj.cn
北京即刻搜索博客 www.jike.bj.cn
// 也不必继续向优先级队列中放入点了。 if(m_pWeight[nPreNode-1][i]==INFINITE_VALUE)
北京即刻搜索博客 www.jike.bj.cn
两天我开始看 ICTCLAS 的实现代码了,和吕震宇的感觉完全一样,代码真的是糟糕透顶, 呵呵,非常同情吕震宇和 Sinboy 能够那么认真地把那些代码读完。有了你们辛苦、认真的 分析工作,让我更容易的读懂 ICTCLAS 的代码了,谢谢了。阅读过程中注意到了他们分析 中有些地方有点小错误。
eWeight=m_apCost->GetElement(-1,nCurNode,0,&pEdgeList);//Get all the
edges
while(pEdgeList!=0 && pEdgeList->col==nCurNode)
...{ // nPreNode 是当前边的起点
nPreNode=pEdgeList->row; // eWeight 是当前边的长度
北京即刻搜索博客 www.jike.bj.cn
北京即刻搜索博客 www.jike.bj.cn
eWeight
// 如果起点到原点的长度为无穷。(这在第一次循环的时候显然是无穷) // 就将这个长度设为最短边的长度。
if(m_pWeight[nCurNode-1][i]==INFINITE_VALUE)
// 在这里面的 i 表达的是长度的值的索引,并不代表不同的路径,同一个 i
可能对应多个路径。
// 这个循环过后,m_pWeight[nCurNode-1][] 为可能存在的前 m_nValueKind
个长度值。
// 并且把前 m_nValueKind 个路径压入 m_nParent 对应的队列中。
北京即刻搜索博客 www.jike.bj.cn
北京即刻搜索博客 www.jike.bj.cn
// 循环从 1 开始,即从第二个节点开始。遍历所有节点。
for(;nCurNode<m_nVertex;nCurNode++)
...{
CQueue queWork; // 有 CNShortPath 调用的上下文可知,入口的 m_apCost 为列优先排序的 CDynamicArray。 // 换句话说就是:
break; // 将起点,索引值 i,和终点到原点的总长度压入优先级队列。 queWork.Push(nPreNode,i,eWeight +m_pWeight[nPreNode-1][i]); } else ...{ // 起点为第一个节点。 // 将起点,索引值 i,和当前边的长度压入优先级队列 queWork.Push(nPreNode,i,eWeight); break; } }//end for
BTW: 问一下,吕震宇,你有什么比较可爱点的称呼么?呵呵,我这么直呼大名在中文的习 惯里似乎不太礼貌。:)
int CNShortPath::ShortPath() ...{
unsigned int nCurNode=1,nPreNode,i,nIndex; ELEMENT_TYPE eWeight; PARRAY_CHAIN pEdgeList;
for(i=0;i<m_nValueKind;i++)
...{
m_pWeight[nCurNode-1][i]=INFINITE_VALUE;
}
//memset((void *),(int),sizeof(ELEMENT_TYPE)*);
//init the weight
i=0;
//
进行循环,索引值小于想要的索引值时,并且优先级队列不为空。
m_pWeight[nCurNode-1][i]=eWeight;
}else...{ // 如果起点到原点的长度 == 队列中的长度, 那么只向
当前节点,当前索引的父节点中插入一个配对。
//
从小到大。
// 如果起点到原点的长度 > 队列中的长度? // 这是不可能出现的,因为这个数值在队列中是有序的。
对于 DynamicArray,我们也完全可以用 List<>.Sort()的方式来实现,对于 C++来说,我们需 要定义 2 个 functor,分别是起点优先比较和终点优先比较。对于 Java 和 C#也有类似的定义 比较函数的办法。因此这个 DynamicArray(),可以扔掉了。没必要用这么一个奇怪的东西。
作者为了能够让以后调用的时候方便,对于起点和终点进行排序(或者说维护了顺序)。对 起点排序,就是代码中所谓的 RowFirst,对于终点进行排序就是 ColumnFirst。
北京即刻搜索博客 www.jike.bj.cn
北京即刻搜索博客 www.jike.bj.cn
那为何作者叫 DynamicArray 呢?其实也不难想象,首先是因为邻接表实际上就是边的一个 列表,也可以看为数组。但是边的数量是在变化的,而不是最开始就可以知道的。因此这个 数组是动态的。于是就叫动态数组了。。。。汗。
来代替优先级队列实现和并且具有和作者一样的 Iterator 的功能。那个 CQueue 完全可以省 略掉。
然后是 DynamicArray。动态数组?非也。这个是用来表示稀疏图的邻接表,每一个元素表 示的是图上的一条边。对于非稀疏的图往往喜欢用 NxN 的数组来表示 N 个节点的连接关系。 而对于稀疏图来说,无疑会浪费大量的空间,于是往往采用记录邻接两点的边的方式来记录 图。
// list<Edge> m_apCost;
// list.sort(m_apCost, less_column_first()); // 下面这行代码是将该列的边的起始链表元素赋予 pEdgeList,以方便遍历。 // 算法上的含义是取得图上终点为 nCurNode 的所有边,并将第一条边放入 pEdgeList 进行对所有边的遍历。
// 我们保留一个为 m_nValueKind 大小的数组,记录这些可能的配 对,而不仅仅是保留最小的。
// 下面这个循环就是将所有可能的组合放到优先级队列中,然后将 来可以从优先级队列中选取前 m_nValueKind。
// 这里循环范围限定到 m_nValueKind 主要是考虑以后所需要的不 会超过这么多个值。
eWeight=pEdgeList->value;//Get the value of edges // 对于 dijkstra 算法来说,我们需要知道当前节点(终点)的通过
不同的前方的点到原点的距离
// 并且从中知道最短的路径,然后我们会更新当前节点的父节点和 当前节点到原点的距离。
// 在这个修改后的多最短路径算法中,我们将(当前节点的父节点, 当前节点通过该父节点到原点的距离)视为一个配对