??xml version="1.0" encoding="utf-8" standalone="yes"?>久久av无码专区亚洲av桃花岛,久久精品国产一区二区三区日韩,久久久久AV综合网成人http://www.shnenglu.com/lucency/archive/2008/12/11/69129.html季阳季阳Thu, 11 Dec 2008 01:58:00 GMThttp://www.shnenglu.com/lucency/archive/2008/12/11/69129.htmlhttp://www.shnenglu.com/lucency/comments/69129.htmlhttp://www.shnenglu.com/lucency/archive/2008/12/11/69129.html#Feedback0http://www.shnenglu.com/lucency/comments/commentRss/69129.htmlhttp://www.shnenglu.com/lucency/services/trackbacks/69129.htmlwindows上用mingw和msys~译global(?.7.3版本为准)

1.configure文g?455?467行注释掉.

2.修改libutil/path.c文g. 在include之后d:

#if defined(_WIN32) && !defined(__CYGWIN__)
#define mkdir(path,mode) mkdir(path)
#define link(one,two) (-1)
#endif

上述两个文g保存? configure, make, make install卛_.

用这U方式除了doc下的texi文g没法~译成功, 其他都没有问? ~译到doc的时? 直接C-cl束卛_.



季阳 2008-12-11 09:58 发表评论
]]>
All about Awk[译]http://www.shnenglu.com/lucency/archive/2008/12/10/69034.html季阳季阳Wed, 10 Dec 2008 03:11:00 GMThttp://www.shnenglu.com/lucency/archive/2008/12/10/69034.htmlhttp://www.shnenglu.com/lucency/comments/69034.htmlhttp://www.shnenglu.com/lucency/archive/2008/12/10/69034.html#Feedback0http://www.shnenglu.com/lucency/comments/commentRss/69034.htmlhttp://www.shnenglu.com/lucency/services/trackbacks/69034.html阅读全文

季阳 2008-12-10 11:11 发表评论
]]>
gdb基础http://www.shnenglu.com/lucency/archive/2008/08/18/59214.html季阳季阳Mon, 18 Aug 2008 05:31:00 GMThttp://www.shnenglu.com/lucency/archive/2008/08/18/59214.htmlhttp://www.shnenglu.com/lucency/comments/59214.htmlhttp://www.shnenglu.com/lucency/archive/2008/08/18/59214.html#Feedback1http://www.shnenglu.com/lucency/comments/commentRss/59214.htmlhttp://www.shnenglu.com/lucency/services/trackbacks/59214.htmlgdb

q篇文章基本上是摘自gdb手册Q除此之外就是加了实际的代码样例Q这样可以更清楚的看C些命令的执行效果。当Ӟq儿不会涉及到所有的gdb命oQ而只是一些常用的?/p>

我这儿用的gdb的版本是6.8。不q因为只是一些常用命令,因此在老版本上应该也没问题。操作系l是Windows XP?/p>

一.概述

调试器的目的在于让你查看E序q行时的内部状态;或者在E序崩溃的时候,查看E序异常的原因。它们可以在一下几个方面提供帮助:

1)启动E序Q按照你的意愿媄响程序的行ؓ

2)让程序在特定条g发生时停止运?/p>

3)当程序停止的时候,查程序正在做什?/p>

4)改变E序的某些东西,q样可以运行时修正bugQ然后l测试其他的问题?/p>

gdb支持多种语言的调试,不过最成熟的应该就是c/c++了。这儿也以c++E序Z来说明。如果想知道其他语言的支持情况,可以看gdb手册?/p>

?gdb的v?/h2>

gdb最常用的启动方式就是:

gdb program

或者在E序异常l止的时候这P

gdb program core

也或者,你可以attachC个已l运行的q程来调试它Q就像这?

gdb program PID

当然Q也可以在命令行讄gdb的启动参敎ͼ例如argsQ?

gdb —args gcc -O2 -c foo.c

q样可以调试gccQ同时将参数"-O2 -c foo.c"传给gcc?/p>

可以用gdb -help查看gdb的所有选项?/p>

要想中止gdb的运行,可以输入quit或者qQ也可以按ctrl-dl合键?/p>

?gdb命o

命o语法

gdb命o的Ş式ؓQcommand [arg1...argn]。每个命令单独一行。行的长度没有限Ӟ当然Q一般应该也不会有多长)?/p>

许多gdb的命令都会有~写的Ş式,例如break可以~写为b?/p>

如果直接按回车,gdb会执行刚刚被执行命o?/p>

如果命o中有#字符Q那么从#开始的字符都会被当成是注释。这个一般用在命令文件中。后面会有介l?/p>

命o补全

gdbh补全功能。功能键?lt;TAB>。例如breakQ在输入bre之后?lt;TAB> 键,gdb׃补全为break。如果只输入bQ然后按<TAB>Qgdb会响一壎ͼq说明有多个以b开始的命o。这U情况下再按一 ?lt;TAB>Qgdb׃把所有以b开始的命o输出出来。下面是此时的屏q截图:

如果只是想看一下以某个(?字符开始的命oQ可以按<META>?Q而不用按两次<TAB>。在? ?lt;META>键的电脑上,可以?lt;ESC>键代ѝ这个命令按h有点ȝQ比按两?lt;TAB>要麻烦多了,如果 不?lt;TAB>键被按坏Q我你还是按<TAB>吧?/p>

命o补全可以用于gdb命oQgdb子命令,E序中的W号名(例如函数名等Q?/p>

在调试c++E序Ӟ基本上肯定会遇到的问题就是重载函数。例如,在设|断点的时候,假设有两个名为overload的函敎ͼq时Z区分到底是那 个函敎ͼ需要加上函数参数类型(函数名加上参数列表作为逻辑上的一个词Q。ؓ了gdb能将参数列表两边的括号作个词的一部分Q需要用单引?函 数整个的括v来。看下图Q?/p>

获取帮助

启动gdb后,可以输入命ohelp得到gdb的命令列表。注意的是这时输出的每个条目都是一cd令。上个图Q?

得到命ocd后,可以用命?help class得到此类别的所有命令。上图:

上面的图中只昄了部分breakpoints命o?/p>

其他跟帮助相关的命o有:

a)help command

用于昄命ocommand的帮助信息?/p>

b)apropos args

q里的args可以使正则表辑ּ。显C所有匹配args的命令的要说明?/p>

c)complete args

昄所有以args开始的命o?/p>

另外q有两个很有用的命oQ?info ?show ?/p>

a)info

用于昄被调试程序的信息。例如传递到当前函数的参?info args;或者查看当前寄存器的?info registers;也可以查看断?info breakpoints。可以用help info查看info的说明?/p>

b)show

q个命o用于昄gdb的信息。也是gdb的一些属性倹{可以用help show查看帮助信息。这个命令通常应该是配合set用的Q用于设定gdb的属性)?/p>

好了Q现在对gdb应该有个大概的认识了Q下面我们就要拿几个例子来验证一些常用命令的效果。Let's go!

?CZ1

首先说明一点,要想高效的用gdb的功能,需要在~译E序的时候要加上-g选项Q这个选项会把调试信息加到可执行文件中?/p>

下面说一下示例文Ӟ包括三个Qgdb.hQ?gdb.cppQ?test.cpp

gdb.h
#ifndef _GDB_H
#define _GDB_H 1

class gdb
{
public:
explicit gdb(int v);
void overload(int one);
void overload(int one, int two);
void catch_ex(int ex); //exception
void loop();
private:
int value;
int array[10];
};

#endif

gdb.cpp
#include "gdb.h"
#include <iostream>
using namespace std;

gdb::gdb(int v)
{
value = v;
for(int i=0; i<10; i++)
{
array[i] = i;
}
}

void gdb::overload(int one)
{
cout<<"function overload with one parameter: "<<one<<endl;
}

void gdb::overload(int one, int two)
{
cout<<"function overload with two paremeters: "<<one<<" "<<two<<endl;
}

void gdb::loop()
{
int loop_array[10];
for(int i=0; i<10; i++)
{
loop_array[i] = i;
}

int v=3;
v=3;
v=4;
}

void gdb::catch_ex(int ex)
{
int e = ex;
try
{
if(e <= 0)
{
throw e;
}
else
{
cout<<"function catch_ex: "<<ex<<endl;
}
}
catch(int x)
{
cout<<"exception: "<<x<<endl;
}
}
test.cpp
#include "gdb.h"

int main()
{
gdb g(5);
g.overload(1);
g.overload(1, 2);
g.loop();

g.catch_ex(3);
g.catch_ex(-1);

return 0;
}

好了Q现在开始调试。首先启动gdbQ指定要调试的可执行文g。前面已l说q了Q可以简单地使用gdb program来启动。或者可以首先启动gdbQ然后用file命o指定要调试的文g。下面是仅启动gdb后的画面Q?

现在用file命o指定要调试的文gQ?/p>

然后可以用 run 或?r 命o来运行程序。在q行之前Q你可能需要ؓE序讑֮一些信息,q些信息有一下四U:

1)E序参数

可以用set args命o讑֮E序的参数。设定完后可以用show args查看讄的是否正。如果set args后面不带M参数Q则向程序传递的参数为空?/p>

2)环境

q儿的环境就是在pȝ/用户配置文g中设|的环境变量,像HOME, PATH之类?GDB提供了在调试的时候改变这些变量值的方式,q样当需要的时候就不用退出gdb来重新设|?GDB提供的命令有:

a) path directory Q- ?directory 加到环境变量PATH前面. 注意 对PATH的改变只对调试的E序有效, GDB使用的PATH不会有改?1

b) show paths Q- 昄PATH的倹{?/p>

c) show environment [varname] Q- 昄环境变量varname的|如果不指定varnameQ则昄所有环境变量的倹{?/p>

d) set environment varname [= value] Q- 讄环境变量varname的gؓvalue。这个改变只是对调试的程序生效。如果不提供valueQ则varname的值置为空?/p>

e) unset environment varname Q- 从环境中U除传递给E序的变量varname?/p>

3)工作目录

在启动gdb调试E序的时候,被调试的E序会从gdbl承工作目录。当然gdb也提供了命o来修改工作目录:

a) cd directory Q- ?directory 设ؓ新的工作目录?/p>

b) pwd Q- 昄当前工作目录?/p>

4)标准输入输出

q没扑ֈ在windows里面q个东西有啥用,现在也没有linux可用Q不好多说。有需要的自己看gdb手册吧。我单抄一下手册吧?/p>

在gdb中,可以run命o的输入输出重定向到文件或者其他终端。也可以通过tty命o讄被调试程序输入输出的讑֤。命令格式是Q?/p>

tty terminal 或?set inferior-tty terminal.

tty 是 set inferior-tty 的别名?/p>


咚咚咚咚Q下面正式开始!

上面我们已经启动了程? 也知道了如何q行E序。可是如果你直接执行run命o会发玎ͼE序直接q行l束了。如果你惛_某一行或者某个函数调用的地方Q或者当某个变量/表达式的值改变的时候,也或者在某些事g发生的时候-Q例如抛出异常、加载动态库Q或者创建子q程Q-的时候停止程序运行,那应该怎么办呢Q?/p>

有了gdbQ一切就都好办了:), 利用下面q三个强大的武器Q你可以L的停止程序。小心了Q大家小心了Q偶要祭三g宝物了,它们是:

断点

断点是指定一个位|,使得E序q行到这个位|的时候会停下来(当然Q还可以讄条g断点Q当q行到指定位|时Q只有满了讄的条ӞE序才会停下来)Q这样便于观察程序的内部状态。断点相关的命o主要有:

a)break location

在指定位|?location 处设|断点,q里?location 可以是函数名Q行P指o地址{(关于如何指定 location ,可以?a href="http://www.shnenglu.com/lucency/archive/2008/08/18/59214.html#location">q里Q?/p>

b)break

如果不指定Q何参敎ͼbreak会在选定?a href="http://www.shnenglu.com/lucency/archive/2008/08/18/59214.html#stack">栈的下一条指令处讄断点?/p>

c)break ... if cond

讄条g断点。每ơ到达断点的时候都会对表达?cond 求|只有当结果ؓ?的时候程序才会在q个断点停下来?/p>

d)tbreak args

讄一个只生效一ơ的断点。args跟break命o里的参数意义相同Q也是_可以为locationQؓI,或者条Ӟ?/p>

e)hbreak args

讄g断点?/p>

f)thbreak args

讄只生效一ơ的g断点?/p>

g)rbreak regex

在所有匹配正则表辑ּ regex 的函C讄断点?/p>

h)info breakpoints [n]

i)info break [n]

j)info watchpoints [n]

上面三个命o都是列出当前的断炏V观察点和捕捉点Q如果指定参数nQ则仅列出第n个的信息?/p>

来试验一把吧。首先用gdb启动E序Q假设我们想在test.cpp?g.overload(1) q一行添加一个断点,那就执行命oQb test.cpp:6,执行完后Q?/p>

q时可以用info break{命令查看断点信息:

然后我们q行E序Q看看有什么效果?/p>

看到了吧Q程序停在了断点所在的行。这时可以用where或者frame查看当前的栈帧信息,也可以用 print 查看一些变量或者表辑ּ的信息,或者info args查看参数信息Q等{?/p>

其他的命令大家可以自己尝试一下?/p>


有时候我们ƈ不确定要在哪里加断点Q例如当我们惛_某个变量被改变或者被诅R被写的时候让E序停下来,可能׃讉K变量的地Ҏ较多Q要x个地斚w加上断点比较ȝQ而且很可能有遗漏Q这时候我们就需要依赖另一个强大的命o了,也就是观察点?/p>

观察?/h3>

观察Ҏ一cȝD的断点Q如果针Ҏ个变量或者表辑ּ指定一个观察点Q那么当它们的D?写的时候,gdb会停止程序的执行。你不需要像讄断点旉h指定这个观察点在程序中的位|。观察点相关的命令有Q?/p>

a)watch expr [thread threadnum]

?expr 讄一个观察点。当 expr 的D改变的时候,gdb会停止程序的q行?/p>

如果指定了线E参数thread threadnum Q则 只有 在线E?threadnum 改变 expr 的值时Q程序才会停止?/p>

b)rwatch expr [thread threadnum]

?expr 讄一个读观察炏V当E序?expr 的值时Qgdb会停止程序的q行?/p>

c)awatch expr [thread threadnum]

?expr 讄一个访问观察点。当E序L者写 expr Ӟgdb会停止程序的q行?/p>

d)info watchpoints

昄所有的断点、观察点、捕捉点。跟info break 相同?/p>

下面再来看看观察点的使用?/p>

首先我们讄一个断点在g.loop()q一行,然后q行到这里。stepq入loop()函数。这一串命令的执行如下Q?/p>

q时我们已经q入loop()函数Q现在我们设|几个观察点。设|命令序列和讄完后的效果如:

可以看到?个停止点Q其中前两个是我们设|的断点Q后面三个是观察炏V分别ؓwatch, rwatch ?awatch。另外还有一个地方就是,x86上默认是g观察炏V?/p>

好了Q来q行一下试试?/p>

到达W一个观察点的时候程序停止运行,同时打印Z变量的旧值和新|以及观察点的位置?/p>

下面我们接着q行Q看看遇到后面的观察点的时候又会怎样?/p>

E序在第三个观察点停了下来(W二个观察点观察点)Q同h印出了变量的新旧值和观察点的位置。第二个观察点由于是读观察点Q而程序中没有读这个变量的地方Q因此运行的时候被跌了。ؓ了看一下读观察点的效果Q我们再讄一个读观察点:

i是@环内的P代器Q它会被复制lloop_array[i]变量Q也是会被诅R可以看刎ͼE序会停止运行,输出i的倹{注意:每次对i的读操作都会使得E序停止。下面是两次执行continue命o后的输出Q?/p>

观察点的内容差不多就q些了。另外有个需要注意的地方是: watch命o讄的观察点只有在变量或者表辑ּ的D改变得时候才会得程序停止运行,如果只是被写Q但是值没有改变,则程序不会停止?/strong>


上面已经讲了断点、观察点Q而对于某些情况,q两U停止点q不是最有效的方式。例如想在c++E序中跑出异常的时候停 止程序,q时候用断点׃够有效了Q因为程序中可能好多异常处理的地方,如果一个个讄断点Q那太ȝ了(当然如果只处理某几个异常Q用断点也无不可Q? 甚至用v来更灉|Q;而观察点更不可用了?/p>

q种情况需要用到捕捉点了?/p>

捕捉?/h3>

捕捉点也是一cȝD的断点Q它可以使得E序在某U事件发生时停止q行Q例如c++异常Q或者加载动态库、创建子q程{。设|捕捉点的命令是catch.

catch event

其中 event 可以是:

a)throw

c++抛出异常?/p>

b)catch

c++捕捉异常?/p>

c)exception

Ada异常?/p>

d)exception unhandled

E序中未处理的异常?/p>

e)assert

p|的Ada断言?/p>

f)exec

对exec的调用(只在HP-UX和GNU/Linux中可用)?/p>

g)fork

对fork的调用(只在HP-UX和GNU/Linux中可用)?/p>

h)vfork

对vfork的调用(只在HP-UX和GNU/Linux中可用)?/p>

i)load

动态加载共享库Q只在HP-UX中可用)?/p>

j)load libname

动态加载共享库 libname Q只在HP-UX中可用)?/p>

k)unload

卸蝲已加载的׃n库(只在HP-UX中可用)?/p>

l)unload libname

卸蝲已加载的׃n?libname Q只在HP-UX中可用)?/p>

q有一个设|只生效一ơ的捕捉点的命o是: tcatch event ?/p>

下面再看一下捕捉点的用?/p>

首先在vtest.cpp?g.catch_ex(-1); 讄一个断点,然后q行Q进入此函数?/p>

现在我们讄一个捕捉点。l运行:

可以看到E序在抛出异常的地方停止了?/p>


删除断点

当断点不再需要了Q那应该删除掉Q否则每ơ执行到断点的位|程序都要停下来Q会把h逼疯的。删除断点的命o有两个:clear和delete?/p>

a)clear [ location ]

如果不指?location Q则删除选择的栈帧中下一条要执行的指令上的Q何断炏V如果选择的是最内部的栈帧(也就是当前正执行的函数的栈Q,则clear会将刚刚使程序停止的断点被删除? 关于 location 的说明可以看q里?/p>

b)delete [breakpoints] [ range ... ]

删除指定范围 range 那的所有的断点、观察点、捕捉点。如果不指定参数 range Q则会删除所有的停止炏V这里的 range 指定的是断点~号区间。可以用info break查看断点信息?/p>


用断点

如果不想删除断点Q只是想暂时使它失效Q则可以使用disable命o。disable命o的Ş式如下:

a)disable [breakpoints] [ range ...]

使指定区?range 内的断点失效。如果不指定 range Q则所有的断点都失效?/p>

使断点生效的命o是enableQŞ式有Q?/p>

a)enable [breakpoints] [ range ...]

使指定区?range 内的断点或者所有断点生效?/p>

b)enable [breakpoints] once range...

使指定区间内的断点生效一ơ?/p>

c)enable [breakpoints] delete range...

使指定区间内的断点生效一ơ,然后删除?/p>


断点条g

断点条g使得只有在相应的条g满Ӟ断点才有效。这里的条g表达式跟E序所用语a的逻辑表达式的语法相同Q例如在c/c++语言里,可以?a==b 或?x&&yq种表达式?/p>

断点条g可以在设|断点的时候指定,也可以在断点讄后通过condition命o来设|或者改变?condition的Ş式ؓQ?/p>

a)condition bnum expression

讄表达?expression 为停止点 bnum 的条件?/p>

b)condition bnum

删除停止?bnum 的条件?/p>

q有一个命令,可以使得gdb忽略断点的条件一定的ơ数Q其形式为:

a)ingore bnum count


指定位置

许多gdb命o都接受一个用于指定程序位|的参数。位|的指定方式有下面几U:

a) linenum

当前源文件的行号?/p>

b) -offset

当前行前面,跟当前行间隔?offset 的行。当前行可以q样定Q用list命oQ打印出来的最后一行就是当前行Q或者对于断点命令,选定的栈帧中Q程序停止执行的位置是当前行?/p>

c) +offset

当前行后面,跟当前行间隔?offset 的行?/p>

d) filename:linenum

源文?filename 中的?linenum?/p>

e) function

当前源文件中的函?function?/p>

f) filename:function

源文?filename 中的函数 function?/p>

g) * address

指定E序地址 address。常用的 address 形式有:

expression Q- 当前语言中有效的表达式?/p>

funcaddr Q- 函数的地址。在c/c++中就是函数名?/p>

'filename'::funcaddr Q- 源文?filename 中的函数地址 funcaddr?/p>


后面懒得写了Q暂时先放一放。发现就是抄文档,内容多了也是个很累h的活。唉Q懒了,不行?..

?/h3>

待添?/p>


数据

待添?/p>


?CZ2

待添?/p>


?后记

q篇文章只是捡了GDB中最常用的一些东西,而且q只是最常用的东西中的一部分,有兴或者需要的可以直接看GDB的文档,可以?a >q里扑ֈ?/p>


1. 不知道是不是因ؓwindows和linuxpȝ的不同,用gdb启动E序后,执行show paths后输出:Executables and object file path: 。也是说输出的值是I的?/p>

季阳 2008-08-18 13:31 发表评论
]]>Suffix Trees[译]http://www.shnenglu.com/lucency/archive/2008/07/12/55944.html季阳季阳Sat, 12 Jul 2008 02:44:00 GMThttp://www.shnenglu.com/lucency/archive/2008/07/12/55944.htmlhttp://www.shnenglu.com/lucency/comments/55944.htmlhttp://www.shnenglu.com/lucency/archive/2008/07/12/55944.html#Feedback5http://www.shnenglu.com/lucency/comments/commentRss/55944.htmlhttp://www.shnenglu.com/lucency/services/trackbacks/55944.htmlq两天看了下后缀树的介绍,发现一文?讲得挺清楚的,q译了?希望对大家有帮助.原文章在q儿
希望原作者不要找我麻烦哈。如果有啥版权问题,我马上删掉。也因此Q?font color="#cc0000">此文暂时止转蝲。此中文版如果有版权,归本人所?

我英语本来就不咋圎ͼ有些地方肯定比较生硬或者有误,Ƣ迎指正?br>
Z贴个自己的代码,只是一个简单的实现Q没有考虑效率问题。如果想要更成熟E_的,可以看这?/a>?br>
再顺个便抱怨两句。文章中囄要是多点Q发布的时候还真是ȝQ要一个个插入才行。用ScribeFire发布好像也不能直接上传图片(也可能是我不会)?br>
Data Structures, Algorithms, & Applications in Java
Suffix Trees
Copyright 1999 Sartaj Sahni


你看见过q个字符串吗?(Have You Seen This String?)

在经典的子串查找问题?l定一个字W串S和模式P,查找P在S中是否存?例如,模式P = cat在字W串S1 =
The big cat ate the small catfish存在(两次),但是不在字符串S2 = Dogs for sale?

人类基因l计划的研究者者经常要基因数据?-其中包含C万计的基?-中查?font color="#3333ff">子串/模式
(q里?font color="#3333ff">子串
?font color="#3333ff">模式可以怺替代使用).每个基因由字W集{A,C,G,T}中的字符构成的序列或者说字符串表C?但是,基因库里的大部分字符串长度大U?font color="#3366ff">2000个字W?q有一些有C万计的字W?考虑到基因库的大和子串查找的频?一个尽可能快的用来从基因库的字W串中查扑֭串的法是很必要?

我们可以使用在标准的法教科书中描述的模式匹配算法在字符串S中查找模式P.q个法的复杂度?font color="#3366ff">O(|P| + |S|), q里?font color="#3366ff">|P|表示P的长?例如字符(letter)或数?digit)的个?.当考虑到模式P可能出现在S中的M位置?q个复杂度看h是很好的.因此,在得出P不在S中存在前,我们必须试S的每?font color="#3366ff">字符(letter)/数字(digit)(q里的术语字W和数字意义相同。译注:后面会统一译为字W?.更进一?在我们得出模式在字符串中出现?我们必须试模式的每个字W?因此,每个字符串查扄法消耗的旉是P和S长度的线性函?

当用l典的字W串匚w法在S中查扑֤个模?font color="#3366ff">P1,P2,...,Pk?消耗的旉?font color="#3366ff">O(|P1| + |P2| + ... + |Pk| + k|S|)(因ؓ查找Pi消耗的旉?font color="#3366ff">O(|Pi| + |S|)).我们马上要学习的后缀树结构能复杂度减少?font color="#3366ff">O(|P1| + |P2| + ... + |Pk| + |S|).此时的O(|S|)用于为S创徏后缀?查找每个模式只需要花费O(|Pi|)的时?在S的后~树创建完成之?.因此,一旦S的后~树创建完?查找每个模式需要的旉军_于模式的长度.

后缀?br>
字符串S?strong>后缀?/strong>实际上是S的所有非I后~的压~trie?因此我们有时后~树称为trie,它的子树称为subtrie.Q不清楚trie树的可以?a >q里
或?a target="_blank" >q里Q?br>
字符?/small>S=peeper的非I后~?font color="#3366ff">peeper,eeper,eper,per,er和r.因此字符串peeper的后~树是包含元素(q些元素也是关键?peeper,eeper,eper,per,er和r的压~trie?字符串peeper的字W集?font color="#3366ff">{e,p,r}.因此,压羃trie树的基数(radix)?.如果需?我们可以用映?font color="#3366ff">e -> 0, p -> 1, r -> 2字W串的字W{换ؓ数字.q种转换只有在我们用一U节点结?-每个节点有一个包含子节点指针的数l?-才需??展示了peeper后缀的压~trie?带有变信?.q棵压羃trie树也是字W串peeper的后~?


Figure 1 Compressed trie for the suffixes of peeper

׃信息节点(译注:也就是叶节点)D-I中的数据是peeper的后~,每个信息节点只需要保存它所表示的后~在字W串中的起始索引.当我们从1开始从左到右烦引peeper中的字符?信息节点D-I只需要保存烦?font color="#3366ff">6,2,3,5,1
?font color="#3366ff">4卛_.利用保存在信息节点中的烦?我们可以讉KS的后~.l果如图2所C?br>

Figure 2 Modified compressed trie for the suffixes of peeper


每个分支节点的第一个字D|C其ؓ根的subtrie中元?译注:应该是指子节?的引?我们可以用被引用的元素的首字W的索引代替元素引用.?展示了替换后的树.我们会用这U修改后的Ş式作为后~树的表示.


Figure 3 Suffix tree for peeper


如果后缀树的每条边用从分支节点到其子节点的字W??标注,可以更容易描q后~树的查找和构建算?标签的第一个字W用来确定要Ud哪一个子节点,剩余的字W表C跌的字W??.?展示的就是图3用这U方式画出后得到的后~?


Figure 4 A more humane drawing of a suffix tree

在后~树的更直观的L?从Q意根节点C息节点的路径上所有边的标{拼接在一起得到的是信息节点表示的后~.When the digit number for the root is not 1, the humane drawing of a suffix tree includes a head node with an edge to the former root. This edge is labeled with the digits that are skipped over.

后缀树的某个节点表示的字W串是由根节点到此节点的路径上的标签拼出来的.?中的节点A表示IZepsilon,节点C表示字符串pe,节点F表示字符串eper.

׃后缀树的关键字长度不?我们必须保证没有一个关键字是另一个的真前~(proper prefix).当字W串S的最后一个字W在S中只出现一?׃会出现S的其中一个后~是另一个后~的真前缀的情?在字W串peeper?最后的字符是r,q且只出C一?因此peeper的后~中就不会出现真前~的情?字符串data的最后字W是a,q且出现了两?因此,data有两个以a开始的后缀,ata和a.后缀a是ata的真前缀.

当字W串S的最后字W在S中出现多于一?必dS的所有后~后面附加一个新的字W?比如?),q样M一个后~都不会是其他后缀的前~.q有一U方法是,我们可以在S后面附加新的字符#,得到新的?#,然后创徏$#的后~?q样做之后得到的后缀树比直接在S的每个后~后面附加#会多一个后~#.

让我们查N个子串吧
但是Q首先要说明几个术语
?font color="#3366ff">n=|S|
表示要创建后~树的字符串的长度.我们从左到右依次lS的字W编?最左边的编号ؓ1.S[i]表示S的第i个字W?suffix(i)表示从字Wi开始的后缀S[i]...S[n],1<=i<=n.

关于查找
当在S中查找模式P?用到的一个基本的观察(observation)是P在S中出现当且仅当P是S的某个后~的前~.

假设P = P[1]...P[k] = S[i]...S[i+k-1].则P是suffix(i)的前~.既然suffix(i)在我们的压羃trie树中(也就是后~?,我们可以利用在压~trie树中查找关键字前~的策略来查找P.

让我们在字符?font color="#3366ff">S=peeper
中查找模?font color="#3366ff">P=per.假设我们已经为S创徏好了后缀??).查找从根l点A开?因ؓP[1]=p,我们着标签以p开始的?当顺着此边l箋?我们对边标签的剩余字W和P的后l字W做比较.׃标签的剩余字W跟P的字W相W?我们到达分支C.在到达C的过E中,我们已经使用了P的前两个字符.W三个字W是r,因此我们从节点C着以r开始的边l?׃q条Ҏ有其他字W?因此不需要其他比?到达信息节点I.q时,P中的字符已经用完?我们断定P在S?׃已经到达信息节点I,我们断定P实际上是S的后~.在实际的后缀树表CZ(而不是如?q种人性化的表C?,信息节点I包含索引4,q告诉我们模式P=per由S=peeper的第4个字W开?也就是说,P=suffix(4)).更进一?我们可以得到P在S中只出现一?如果一个模式在字符串中出现多次,查找会在分支节点中止,而不是信息节?(译注:例如查找pe,查找在节点C中止,说明它在S中出C两次--C有两个叶子节?

现在我们来查找模?font color="#3366ff">P=eeee.q是从根l点开?׃P的第一个字W是e,我们沿着以e开始的边到达B.下一个字W还是e,因此我们从B开始沿着以e开始的边l?在沿着q条边向下的q程?我们需要比较边标签的剩余字Wper和P的剩余字Wee.比较W一对字W?p,e)时无法匹?因此我们断定P不在S?

假设我们查找模式P=p.从根开?沿着以p开始的边往?在向下的q程?我们比较边的剩余字符(只有字符e)和模式的剩余字符.׃已经到P的结?我们断定P是以节点C为根的subtrie的所有关键字的前~.可以通过遍历以C为根的subtrie、访问subtrie的所有信息节Ҏ到P出现的所有位|。如果只需要P出现的一个位|,可以利用存储在节点C的第一部分的烦引。当沿着到节点X的边查找时模式结束,我们p已到达节点XQ查扑֜节点Xl束?br>
当查找模?font color="#3366ff">P=ropeQ利用P的第一个字Wr到达信息节点D。由于模式还没有l束Q我们必L据D的字W检查P的剩余字W。检查显CP不是D的关键字的前~Q因此P不在S=peeper中?br>
我们要做的最后一个查找是Ҏ式P=pepe。从?的根l点开始,我们沿着以p开始的边到达节点C。下一个未查的字符是p。因此,从C开始,我们希望沿着以p开始的边l。由于没有满个条件的边,我们断定pepe不在peeper中?br>

Other Nifty Things You Can Do with a Suffix Tree

一旦ؓ字符串S创徏好了后缀树,我们可以在O(|P|)旉内判断S是否包含P。这意味着如果士比亚的戏剧“|密Ƨ与׃?#8221;的内容创Z后缀树,我们可以非常快的判断文章中是否存在短语wherefore art thou。事实上Q只需话费比较18个字W的旉Q模式的长度Q。查找时间跟文章的长度无兟?br>
其他可以快速完成的有趣事情Q?br>
1.查找模式P的所有出现。这是通过对P查找后缀树实现。如果P臛_出现一ơ,查找会在信息节点或者分支节点中止。当中止于信息节ҎQP只出Cơ。如果中止于分支节点XQ模式出现的所有地方可以通过讉K以X为根的subtrie的信息节Ҏ得到。如果我们按照下面的方式Q这个访问操作可以在O(n)Qn是模式出现的ơ数Q时间内完成?br>
(a)
所有的信息节点按照节点所表示的后~的字典序链v来(q也是从左到x描信息节Ҏ遇到q些节点的顺序)。图4的信息节点会按照E,F,G,H,I,D的顺序链接v来?br>
(b)
在每个分支节点内Q保存以此节点ؓ根的subtrie的第一个和最后一个信息节点的引用。图4中节点A,B和C分别保存序对(E,D),(E,G)?H,I)。我们用序对(firstInformationNode, lastInformationNode)周游以firstInformationNode开始、以lastInformationNodel束的链。这个周怼得到模式P出现的所有位|。注意,当我们在分支节点中保存序?firstInformationNode, lastInformationNode)Ӟ׃需要再保存到subtrie中的信息节点的引用(也就是字DelementQ?br>
2.查找包含模式P的所有字W串。假设我们有一些字W串S1,S2,... SkQ我们想得到所有包含P的字W串。例如,基因库中包含C万计的字W串Q当一个研I员提交一个查询字W串Q我们就要报告所有包含此模式的字W串。ؓ了有效的执行q类查询Q就需要创Z个包含字W串S1$S2$...$Sk#的所有后~的压~trie树(也称为多字符串后~树)Q这里的$,#是两个不在字W串S1, S2, ..., Sk中出现的不同字符。在后缀树的每个Q分支)l点N中,保存所有的字符串Si的链表,其中Si是以N为根的subtrie中所有的信息节点表示的字W串的开始点位于其中的字W串Q译注:真拗口啊Q这儿的意思就是对某个信息节点LQ如果L表示的字W串从Si的某个位|开始,那就Si的引用放到L的父辈节点的链表中)?br>
3.查找S中出现次数至ؓm>1ơ的子串。这个查询可以按照下面的方式在O(|S|)旉内完成:
(a)周游后缀树,Ҏ个分支节点XQ保存从根节点到X节点的字W串的长度l和以X为根的subtrie中信息节点的数目m?br>(b)周游后缀树,讉K信息节点?gt;=m的分支节炏Vl最大的分支节点表示的就是出现次?gt;=m的最长子丌Ӏ?br>
注意步骤(a)只需要执行一ơ。完成后Q我们可以对需要的Lm执行步骤(b)。另外还要注意,当m=2是,不需要确定subtrie中信息节点的数目。在后缀树中Q每个分支节点之后有两个信息节点?br>
4.查找字符串S和T的最长公共子丌Ӏ这可以按照下面的方式在O(|S|+|T|)旉内完成:
(a)为S和T创徏多字W串后缀树(也就?font color="#3366ff">S$T#的后~树)
(b)周游后缀树,查找表示的字W串最长,q且以其为根的subtrie的信息节点表C的字符串中Q至有一个从S开始,另一个从T开始的字符丌Ӏ?br>
注意Q有个相关的查找S和T的最长公共子序列的问题用动态规划算法在O(|S|*|T|)旉内完成?br>
如何创徏你自q后缀?/font>
三个观察(observation)
Z更有效的创徏后缀树,我们为每个分支节Ҏ加字DlongestProperSuffix。表C非I字W串Y的分支节点的longestProperSuffix字段指向表示Y的最长真后缀的分支节点(Y的最长真后缀通过LY的首字符得到Q。根l点的longestProperSuffix未用?br>
?表示的是l图4加上最长真后缀指针后得刎ͼl常最长真后缀指针UCؓ后缀指针Q。后~指针用红色箭头表C。节点C表示字符串pe。pe的最长后~ep点B表示。因此C的后~指针指向B。e的最长后~是空丌Ӏ由于根l点A表示IZQ因此B的后~指针指向A?br>

Figure 5 Suffix tree of Figure 4 augmented with suffix pointers

观察1 如果S的后~树有一个表C字W串Y的分支节点,那么后缀树中也有一个表CY的最长后~Z的分支节炏V?br>证明 设PCY的分支节炏V由于P是分支节点,臛_有两个不同的字符x和yQ得S中有两个分别以Yx和Yy开始的后缀。因此,S有两个分别以Zx和Zy开始的后缀。因此,S的后~树中必然有一个对应Z的分支节炏V?br>
观察2 如果S的后~树有一个表CY的分支节点,那么树中对Y的每个后~都有一个对应的分支节点?br>证明 p?卛_得到?br>
注意?中有一个表Cpe的分支节炏V因此,树中一定有表示e和epsilon的分支节炏V?br>
在描q后~树的创徏法Ӟ有两个概念很有用Q?font color="#3366ff">last branch node
?font color="#3366ff">last branch index。后~suffix(i)的last branch node是表Csuffix(i)的信息节点的父节炏V在?中,suffix(1)...suffix(6)的last branch node分别是C,B,B,C,B和A。对L后缀suffix(i)Q其last branch index lastBranchIndex(i)是在suffix(i)的last branch node中,产生分支的字W的索引。在?中,lastBranchIndex(1)Q?,因ؓQsuffix(1)=peeperQsuffix(1)׃息节点H表示QH的父节点是CQC的分支是在suffix(1)的第三个字符产生的;suffix(1)的第三个字符是S[3]。可以验证一下,lastBranIndex[1:6] = [3,3,4,6,6,6]?br>
观察3 在S的后~树中Q?lastBranchIndex(i) <= lastBranchIndex(i+1), 1 <= i < n?br>证明作ؓl习?br>
Get Out That Hammer and Saw, and Start Building

Z创徏你自q后缀树,你必ȝ你自q字符串开始。我们用R = ababbabbaabbabb来阐q后~树的构徏q程。由于R的最后字Wb出现了不止一ơ,我们在R后面附加字符#Qؓ新的字符串S=R#创徏后缀树?br>
创徏{略
后缀树的创徏法从表C空串的根结点开始。根l点是一个分支节炏V创建后~树过E的M时候,都有一个分支节点被指定位活动节?active node)。这是插入下一个后~的v始节炏V用activeLength表示根结点对应的字符串的长度。开始时Q根节点是活动节点,activeLength=0。图6展示了开始时的状态,l色的节Ҏzd节点?br>


Figure 6 Initial configuration for suffix tree construction


随着处理的进行,我们会不断往树中d分支节点和信息节炏V新d的分支节点用品红色表C,新添加的信息节点用青色表C。后~指针为红艌Ӏ?br>
后缀按照suffix(1), suffix(2), ..., suffix(n)的顺序依ơ插入到树中。后~以这U顺序插入是通过从左到右扫描字符串S的方氏完成。用tree(i)表示插入后缀suffix(1), ..., suffix(i)之后形成的树Q?font color="#3366ff">lastBranchIndex(j, i)
表示tree(i)中后~suffix(j)的last branch index?font color="#3366ff">minDistance表示从活动节点到卛_插入的后~的last branch index的距ȝ下界Q译注:感觉q个东西在实现的时候没啥意义)。开始时QminDistance = 0q且lastBranchIndex(1,1) = 1。当插入suffix(i)Ӟ满条glastBranchIndex(i,i) >= i + activeLength + minDistance?br>
Z向tree(i)中插入suffix(i+1)Q我们必遵循如下步骤:
1.定lastBranchIndex(i+1, i+1)。ؓ了完成这点,我们从当前活动节点开始。新后缀的开始的activeLength个字W(也就是字WS[i+1], S[i+2], ..., S[i + activeLength]Q已知是跟当前活动节点表C的字符串相匚w的。因此,Z定lastBranchIndex(i+1,i+1)Q需要检新后缀的activeLength + 1, activeLength + 2, ...字符。这些字W用于确定tree(i)中进一步处理时要经q的路径Q此路径始于zd节点Q当lastBranchIndex(i+1,i+1)定时中止。根据已知的lastBranchIndex(i+1,i+1) >= i + 1 + activeLength + minDistanceQ这个过E可以简化,从而得到效率提升?br>
2.如果tree(i)中没有表C字W串S[i]...S[lastBranchIndex(i+1,i+1)-1]的节点XQ则创徏一个?br>
3.为suffix(i+1)d一个信息节炏V这个信息节ҎX的孩子,从XC息节点的边上的标{是S[lastBranchIndex(i+1, i+1)]...S[n]?br>
回到例子

我们从向?中的树tree(0)插入suffix(1)开始。根l点是活动节点,activeLength = minDistance = 0。suffix(1)的第一个字W是S[1]=a。从tree(0)的活动节点开始没有以a开始的边(事实上,此时zd节点没有M边)。因此,lastBranchIndex(1,1) = 1。我们创Z个新的信息节点和一条边Q边的标{是整个字符丌Ӏ图7展示了结果tree(1)。根l点依然是活动节点,activeLength和minDistance没有变化?br>


Figure 7 After the insertion of the suffix ababbabbaabbabb#



在我们的L中,信息节点的入边的标签标记为i+Qi表示标签在S中的开始位|,+表示标签一直到字符串的l尾。因此,在图7中,1+表示字符串S[1]...S[n]。图7也展CZ字符串S。新插入的后~用青色表C?br>
Z插入后缀suffix(2)Q我们再ơ从根结点开始检查后~的activeLength + 1 = 1, activeLength + 2 = 2, ...,字符。因为suffix(2)的第一个字W是S[2]=bQ活动节Ҏ有以S[2]=b开始的边,所以lastBranchIndex(2,2) = 2。因此我们创建新的信息节点和标记?+的边。结果如?所C。根l点依然是活动节点,activeLength和minDistance依旧没有变化?br>


Figure 8 After the insertion of the suffix babbabbaabbabb#


注意?是关于suffix(1)和suffix(2)的压~trie树tree(2)?br>
下一个后~suffix(3)开始于S[3]=a。由于tree(2)的活动节Ҏ一个以a开始的边,所以lastBranchIndex(3,3) > 3。ؓ了确定lastBranchIndex(3,3)Q必需要查看suffix(3)的更q字W。尤其是Q我们需要查看尽可能多的字符Q以便区分suffix(1)和suffix(3)。首先比较后~的第二个字符S[4]=b和边1+的第二个字符S[2]=b。由于S[4]=S[2]Q必dq一步的比较。下一步比较S[5]和S[3]。由于这两个字符不同Q我们可以确定lastBranchIndex(3,3)?。这Ӟ我们需要更新minDistance?.注意Q因为lastBranchIndex(3,3) = 5 = 3 + activeLength + minDistanceQ这是minDistance的最大可能倹{?br>
Z插入suffix(3)Q我们将tree(2)的边1+一分ؓ二。第一条边的标{是1,2;W一条边的标{是3+。这两个边之间插入新的分支节炏V另外,q要为新插入的后~d信息节点。结果如?所C。边标签1,2昄为字WS[1]S[2] = ab?br>


Figure 9 After the insertion of the suffix abbabbaabbabb#


tree(3)q没完成Q因没有定新加的分支节点的后缀指针。这个分支节点的最长后~是bQ但是对应b的分支节点不存在。别惊慌Q下一个要创徏的分支节点就是对应b的节炏V?br>
下一个要插入的后~是suffix(4)。这是刚插入的suffix(3)的最长后~。新后缀的插入过E由Ҏ当前zd节点的后~指针定新的zd节点开始。由于根l点没有后缀指针Q活动节Ҏ有变化。因此activeLength也没有变化。但是,我们必须更新minDistance以满lastBranchIndex(4,4) >= 4 + activeLength + minDistance。显Ӟ对于所有的i<= lastBranchIndex(i+1,i+1)。因此,lastBranchIndex(i+1,i+1) >= lastBranchIndex(i,i) >= i + activeLength + minDistance。ؓ了保证lastBranchIndex(i+1,i+1) >= i + 1 + activeLength + minDistanceQ我们必dminDistance减小1.

因ؓminDistance = 1Q我们从zd节点开始,沿着S[4]S[5]...指定的\径记录。我们不需要比较开始的minDistance个字W,因ؓ在minDistance+1之前的字W都已经保证是匹配的了。由于活动节点以S[4]=b开始的边的长度大于1,我们比较S[5]和边的第二个字符S[3]。因两个字符不同Q这条边也要一分ؓ二。第一条边的标{ؓ2,2=bQ第二条的标签?+。在两条边之间添加新的分支节点FQ还要ؓ新插入的后缀创徏信息节点G。G跟F之间通过标签?+的边q接。结果如图所C?br>


Figure 10 After the insertion of the suffix bbabbaabbabb#


现在我们要ؓ插入后缀suffix(3)时创建的分支节点D讄后缀指针。这个后~指针是新创建的分支节点F?br>
节点F表示的字W串b的最长后~是空丌Ӏ因此F的后~指针指向根结炏V图11是添加了后缀指针的结果?br>


Figure 11 Trie of Figure 10 with suffix pointers added


下一个要插入的是suffix(5)。由于suffix(5)是刚插入的后~suffix(4)的最长后~Q我们从zd节点的后~指针开始。但是当前作为活动节点的根结Ҏ有后~指针。因此,zd节点不变。ؓ了保持lastBranchIndex, activeLength, minDistance以及插入的后缀的烦?5)之间的关p,需要将minDistance减少1.因此QminDistance变ؓ0.

因ؓactiveLength=0Q我们需要从suffix(5)的首字符S[5]开始检查。活动节Ҏ一条以S[5]=b开始的辏V我们沿着q条Ҏ较后~字符和标{֭W。由于所有的字符都匹配,我们到达节点F。现在F成ؓzd节点Q在查后~字符的过E中Q遇到的分支节点都成为新的活动节点)QactiveLength=1。我们从当前zd节点的某条边开始l比较。由于下一个要比较的后~字符是S[6]=aQ我们用以a开始的边(如果q样的边不存在,新后~的lastBranchIndex是activeLength+1Q。这条边的标{是3+。当比较到新后缀的字WS[10]=a和边的字WS[7]=bӞ比较l束。因此,lastBranchIndex(5,5) = 10. minDistance讄为允许的最大|也就是lastBranchIndex(5,5) - (index of suffix to be inserted) - activeLength = 10 - 5 - 1 = 4.?br>
Z插入suffix(5)Q我们将节点F和C之间的边分裂Z。分裂出现在?F,C)的splitDigit=5的字W位|?br>


Figure 12 After the insertion of the suffix babbaabbabb#

后面几个后缀的插入过E跟前面几个完全一P׃译了,感兴的可以看看原文Q我只把图脓在这儿,如果上面的部分看明白了,那么只看下面的几张图也能明白是怎么插入的?/font>


Figure 13 After the insertion of the suffix abbaabbabb#


Figure 14 After the insertion of the suffix bbaabbabb#


Figure 15 After the insertion of the suffix baabbabb#


Figure 16 After the insertion of the suffix aabbabb#


Figure 17 After the insertion of the suffix abbabb#


Figure 18 After the insertion of the suffix bbabb#


Figure 19 After the insertion of the suffix babb#


Figure 20 After the insertion of the suffix abb#


Figure 21 After the insertion of the suffix bb#


复杂度分?/big>
用r表示字符串S的字W集的大,n表示S的长度(也就是后~的数目)?/nQlastbranchindex(i,i)>
Z插入suffix(i)Q我?/nQlastbranchindex(i,i)>
(a)沿着zd节点的后~指针Q除非活动节Ҏ根结点)?/nQlastbranchindex(i,i)>
(b)在已创徏的后~树中向下UdQ直到经q了minDistance个字W?/nQlastbranchindex(i,i)>
(c)然后依次比较后缀和边的字W,直到定了lastBranchIndex(i,i)为止?/nQlastbranchindex(i,i)>
(d)最后插入新的信息节点和可能的分支节炏V?/nQlastbranchindex(i,i)>

(a)部分消耗的ȝ旉是O(n)Q一共有n此插入)

(b) 部分中,在后~树中往下移动时Q不需要做比较。每ơ移动到下分支节点需要O(1)旉。另外,每次Ud都会使minDistance减少1.׃开始时 minDistance?,q且永远不会于0,(b)消耗的旉是O(n + nơ插入操作中minDistance增加的L).

(c) 部分中,定lastBranchIndex(i,i) ?i + activeLength + minDistance是否相等需要的旉是O(1)。这只有在minDistance = 0或者位于suffix(i)的activeLength + minDistance + 1位置的字Wx跟活动节点的合适的边的minDistance + 1位置的字W不同的时候才满。当lastBranchIndex(i,i) != i + activeLength + minDistanceӞlastBranchIndex(i,i) > i + activeLength + minDistanceQlastBranchIndex(i,i)的值通过对后~字符和边的字W之间做一pd的比较来定。每q行一ơ这U比 较,minDistance?.本算法中Q只有在q种情景下,minDistance才会增加。由于minDistance的每ơ递增都是S的新的位|? Q也是此位|开始的字符q未比较q的位置Q的字符和边的字W相{的情Ş的结果,因此在nơ插入中QminDistance增加的L是O(n)?/nQlastbranchindex(i,i)>

每次插入Ӟ(d)消耗的旉是O(r)Q因为我们需要初始化要创建的分支节点的O(r)个字Dc因此步?d)消耗的ȝ旉是O(nr)?/nQlastbranchindex(i,i)>

因此Q创建后~树消耗的ȝ旉是O(nr)。如果假定字W集的大r是常敎ͼ法的复杂度是O(n)?/nQlastbranchindex(i,i)>

只有在字W集的大r很小的情况下Q才推荐每个分支节点有r个指向子节点的字Dc当字符集很大的时候(可能会跟n一样大Q这时上q算法的复杂度是O(n^2)Q,使用哈希?/a>能够得到O(n)复杂度的法。空间复杂度有O(nr)变ؓO(n)?/nQlastbranchindex(i,i)>

q里有一个分ȝ法实现的后缀树,旉和空间复杂度都是O(n)Q即使字W集的大是O(n)Q?/nQlastbranchindex(i,i)>


References and Selected Readings


  1. Department of Energy's Web site for the human genomics project
  2. Biocomputing Hypertext Coursebook.


Linear time algorithms to search for a single pattern in a given string can be found in most algorithm's texts. See, for example, the texts:
  1. Computer Algorithms, by E. Horowitz, S. Sahni, and S. Rajasekeran, Computer Science Press, New York, 1998.
  2. Introduction to Algorithms, by T. Cormen, C. Leiserson, and R. Rivest, McGraw-Hill Book Company, New York, 1992.


For more on suffix tree construction, see the papers:
  1. ``A space economical suffix tree construction algorithm,'' by E. McCreight, Journal of the ACM, 23, 2, 1976, 262-272.
  2. ``On-line construction of suffix trees,'' by E. Ukkonen, Algorithmica, 14, 3, 1995, 249-260.
  3. Fast string searching with suffix trees,'' by M. Nelson, Dr. Dobb's Journal, August 1996.
  4. Optimal suffix tree construction with large alphabets, by M. Farach, IEEE Symposium on the Foundations of Computer Science, 1997.


You can download C++ code to construct a suffix tree from http://www.ddj.com/ftp/1996/1996.08/suffix.zip. This code, developed by M. Nelson, is described in paper 3 above.


季阳 2008-07-12 10:44 发表评论
]]>
C++~码风格指南http://www.shnenglu.com/lucency/archive/2008/05/23/50829.html季阳季阳Fri, 23 May 2008 01:09:00 GMThttp://www.shnenglu.com/lucency/archive/2008/05/23/50829.htmlhttp://www.shnenglu.com/lucency/comments/50829.htmlhttp://www.shnenglu.com/lucency/archive/2008/05/23/50829.html#Feedback0http://www.shnenglu.com/lucency/comments/commentRss/50829.htmlhttp://www.shnenglu.com/lucency/services/trackbacks/50829.htmlC++~码风格指南

C++ Programming Style Guidelines


季阳 2008-05-23 09:09 发表评论
]]>
ecb symboldef VS. si context windowhttp://www.shnenglu.com/lucency/archive/2008/04/21/47726.html季阳季阳Mon, 21 Apr 2008 05:02:00 GMThttp://www.shnenglu.com/lucency/archive/2008/04/21/47726.htmlhttp://www.shnenglu.com/lucency/comments/47726.htmlhttp://www.shnenglu.com/lucency/archive/2008/04/21/47726.html#Feedback0http://www.shnenglu.com/lucency/comments/commentRss/47726.htmlhttp://www.shnenglu.com/lucency/services/trackbacks/47726.htmlZoundryDocument

一直觉得source insight中显C变量和函数定义的context window很好,昨天H然发现原来ecb(Emacs Code Browser)中也有个cM的东?当layout换成left_symboldef?左下H口是?啥也不说?贴个囑֐.


q个是我Ҏ原先的layout left_symboldef改的.可以看到左边那一?上面是函数啥的列?下面是W号定义H口.有两点不爽的是符号定义窗口没法单独放在下?主要是放在左边的?函数原型比较长的没法完整显C?二是不能像si那样双击跌{.不过也不错了,跌{用cscope也容?

ps:我的ecb函数列表中没法显C参数类?哪位如果知道ȝ说一下哈,谢了!



季阳 2008-04-21 13:02 发表评论
]]>
关于和~冲区的理解http://www.shnenglu.com/lucency/archive/2008/04/07/46419.html季阳季阳Mon, 07 Apr 2008 04:48:00 GMThttp://www.shnenglu.com/lucency/archive/2008/04/07/46419.htmlhttp://www.shnenglu.com/lucency/comments/46419.htmlhttp://www.shnenglu.com/lucency/archive/2008/04/07/46419.html#Feedback4http://www.shnenglu.com/lucency/comments/commentRss/46419.htmlhttp://www.shnenglu.com/lucency/services/trackbacks/46419.html

0. 序曲

写这短文的起因?前两天想d大的acm在线pȝ扑և道题做做。ؓ什么呢?因ؓ本h天大毕业,q个天大呢可是中国最早的大学,原名北洋大学?q可l对是货真h实的W一所大学。给大家推荐推荐?学风那是相当的好?/p>

扯多?q是回到本来的话题上。上了acmpȝ之后,先看了1001。那道题的意思是输入一些正整数(以EOFl束),把对应的字符输出。这个简?E序很快出来了:


 

#include <stdio.h>
int main()
{
int c;
while(scanf("%d"&c) != EOF)
{
putchar(c);
}
return 0;
}

 


E序q行,输入103 102 105 107<enter>

输出gfik?/p>

当时q行完之后马上想,Z么不是输入一个数字马上输Z个字W呢,因ؓ看程序确实是q样的逻辑,只要不是EOF,׃输出。又一?对了,是缓冲的问题。想hAPUE里边说得stdin应该是行~冲?另外,可以用setbuf,setvbuf讑֮的~冲。于是想stdin设成无缓冲的。于是程序变成这?


#include <stdio.h>
int main()
{
int c;
setbuf(stdin, NULL);
while(scanf("%d"&c) != EOF)
{
putchar(c);
}
return 0;
}


可是~译q行,q是老样?没有变化。想了想,没想出是啥原?于是开始google和APUE。终于算是明白了?整理在这ѝ?/p>

声明Q?/p>

本文很大部分内容来自APUEQ-UNIX环境高~程?/p>

1. ~冲cd?/p>

标准库提供缓冲是Z减少对read和write的调用。提供的~冲有三U类?整理自APUE):

  • 全缓册Ӏ?

在这U情况下,实际的I/O操作只有在缓冲区被填满了之后才会q行。对ȝ在磁盘上的文件的操作一般是有标准I/O库提供全~冲。缓冲区一般是在第一ơ对进行I/O操作?由标准I/O函数调用malloc函数分配得到的?/p>

术语flush描述了标准I/O~冲的写操作。缓冲区可以由标准I/O函数自动flush(例如~冲区满的时?;或者我们对调用fflush函数?/p>

  • 行缓?/div>

在这U情况下,只有在输?输出中遇到换行符的时?才会执行实际的I/O操作。这允许我们一ơ写一个字W?但是只有在写完一行之后才做I/O操作。一般的,涉及到终端的?-例如标注输入(stdin)和标准输?stdout)--是行~冲的?/p>

  • 无缓?/div>

标准I/O库不~存字符?span style="color: #ff0000;">需要注意的?标准库不~存q不意味着操作pȝ或者设备驱动不~存?br>

ISO C要求:

  • 当且仅当不涉及交互设备时,标准输入和标准输出是全缓存的?/span>
  • 标准错误l对不是全缓存的?/span>

但是,qƈ没有告诉我们当标准输?输出在涉及交互设备时,它们是无~存的还是行~存?也没有告诉我们标准错误应该是行缓存的q是无缓存的。不q?大多数实现默认的~存cd是这L:

  • 标准错误L无缓存的?/span>
  • 对于所有的其他来?如果它们涉及C互设?那么是行缓存的;否则是全~存的?/span>

2. 改变默认~存cd

可以通过下面的函数改变缓存类?摘自APUE):

void setbuf(FILE *restrict fp, char *restrict buf);
int setvbuf(FILE *restrict fp, char *restrict buf, int mode, size_t size);

q些函数必须在流打开之后、但是未Ҏ做Q何操作之前被调用(因ؓ每个函数都需要一个有效的文g指针作ؓW一个参??/span>

利用setbufQ可以打开或者关闭缓存。ؓ了打开~存Qbuf参数必须一个大ؓBUFSIZ的缓存,BUFSIZ是定义在stdio。h中的帔R?amp;amp;lt;&amp;lt;ISO/IEC 9899&amp;gt;&amp;gt;要求QBUFSIZ臛_?56。如果要关闭~存Q可以将buf设成NULL?/span>

利用setvbufQ我们可以设定缓存类型。这是通过mode参数指定的?/span>

关于q两个函敎ͼ可以看下表(摘自APUEQ:


Function

mode

buf

Buffer and length

Type of buffering

setbuf

non-null

user buf of length BUFSIZ

fully buffered or line buffered

NULL

(no buffer)

unbuffered

setvbuf

_IOLBF

non-null

user buf of length size

fully buffered

NULL

system buffer of appropriate length

_IOFBF

non-null

user buf of length size

line buffered

NULL

system buffer of appropriate length

_IONBF

(ignored)

(no buffer)

unbuffered

需要注意的是:如果在函数内为流分配了自动变量作为缓存,那么在退Z前需要将关闭。因此最好让pȝ自己分配~存Q这些缓存在关闭的时候会自动被释放?/span>


3.如果清理输入~存

关于q点可以参看comp.lang.c FAQ的Question12.26b:

Q: If fflush won't work, what can I use to flush input?

A: It depends on what you're trying to do. If you're trying to get rid of an unread newline or other unexpected input after calling scanf (see questions 12.18a-12.19), you really need to rewrite or replace the call to scanf (see question 12.20). Alternatively, you can consume the rest of a partially-read line with a simple code fragment like

while((c = getchar()) != '\n' &amp;&amp; c != EOF)
/* discard */ ;

(You may also be able to use the curses flushinp function.)

There is no standard way to discard unread characters from a stdio input stream. Some vendors do implement fflush so that fflush(stdin) discards unread characters, although portable programs cannot depend on this. (Some versions of the stdio library implement fpurge or fabort calls which do the same thing, but these aren't standard, either.) Note, too, that flushing stdio input buffers is not necessarily sufficient: unread characters can also accumulate in other, OS-level input buffers. If you're trying to actively discard input (perhaps in anticipation of issuing an unexpected prompt to confirm a destructive action, for which an accidentally-typed ``y'' could be disastrous), you'll have to use a system-specific technique to detect the presence of typed-ahead input; see questions 19.1 and 19.2. Keep in mind that users can become frustrated if you discard input that happened to be typed too quickly.

References: ISO Sec. 7.9.5.2
H&amp;S Sec. 15.2

4. 几点需要注意的地方

  • 对输入流q行fflush操作是无定义的?/span>
  • 无缓存ƈ不意味着一个个的那样处理输入,而是说当操作pȝq回它们Ӟ对于标准库函数来说它们是立即可用的。因可能有操作系l甚至是硬件的缓存,q些q不是setbuf可以控制的?/span>
  • 另外可以参?a >q里Q我是最先从q里开始看的)。还?a >q里。我从后面那个链接摘录一些重要的下来Q?/span>

setbuf() has to do with the delivery of bytes between the
C library FILE* management layer and the OS I/O layer.

Calls to fread(), fgets(), fgetc(), and getchar() work within
whatever FILE* buffered data is available, and when that data
is exhausted, the calls request that the FILE* buffer be refilled
by the system I/O layer.

When full buffering is turned on, that refill operation results in the
FILE* layer requesting that the operating system hand it a full
buffer's worth of data; when buffering is turned off, that
refill operation results in the FILE* layer requesting that the
operating system return a single character.

...setting an input stream to be unbuffered
does NOT tell the operating system to tell the device driver
to go into any kind of "raw" single-character mode. There are
system-specific calls such as ioctl() and tcsetterm() that
control what the device driver will do.







季阳 2008-04-07 12:48 发表评论
]]>
makefileW记http://www.shnenglu.com/lucency/archive/2008/02/24/43160.html季阳季阳Sun, 24 Feb 2008 03:23:00 GMThttp://www.shnenglu.com/lucency/archive/2008/02/24/43160.htmlhttp://www.shnenglu.com/lucency/comments/43160.htmlhttp://www.shnenglu.com/lucency/archive/2008/02/24/43160.html#Feedback0http://www.shnenglu.com/lucency/comments/commentRss/43160.htmlhttp://www.shnenglu.com/lucency/services/trackbacks/43160.html>。另外网上有一《跟我一起写makefile》也很好?br />
    makefile规则分ؓ三部分:目标(target)、条?prerequisite)、命?commands)?br />    target:prereq1...prereqn
       commans

1.自动变量

    $@Q代表目标文件名
    $%QThe filename element of an archive member specification.
    $<Q条件列表中的第一个条件的文g?br />    $?Q条件列表中所有比目标新的那些条g的文件名Q以I格分割
    $^Q条件列表中所有条件的文g名,以空格分剌Ӏ如果列表中有重复的条gQ则会被删掉?br />    $+Q跟$^cMQ区别在不会L重复的条件?br />    $*Q目标文件名的词q部?L扩展名剩下的部分)

    自动变量只能用在规则的命令部分,因ؓq些变量是make匚w到规则的目标和条件后才设|值的?br />    举个例子说明一下。假设某个工E有三个文gQcal.cppQcal.hQtest.cpp(下面再有说明时也以此Z)。makefile如下Q?br />        OBJS=test.o cal.o cal.o

        TARGET=cal.exe

        all:$(TARGET)

        $(TARGET):$(OBJS)
            @echo $@
            @echo $<
            @echo $^
            @echo $?
            @echo $+
            @echo $%
            @echo $*
    输出为:
            cal.exe
            test.o
            test.o cal.o
            test.o cal.o
            test.o cal.o cal.o
            ECHO is off.
            ECHO is off.
    至于最后两个ؓ什么会q样输出我还不太清楚Q有清楚地麻烦告诉我一下这两个怎么用。先谢谢?/b>?br />
2.模式规则
    模式规则也是规则Q也要满make的规则Ş式,分ؓ目标、条件、命令三个部分。只是目标、条件中的文件名的词q部分用%代替了,q个%跟shell中的*cMQ代表Q意长度的字符丌Ӏ还以上面说得工Eؓ例,makefile如下Q?br />        CPPFLAGS= -g
        CC=g++

        test:cal.o test.o
        cal.o:cal.h
    make的时候,输出如下内容Q?br />        g++  -g  -c -o test.o test.cpp
        g++  -g  -c -o cal.o cal.cpp
        g++   test.o cal.o   -o test
    q个makefile之所以能够正被处理Q是因ؓmake内徏了一些规则。例如:
        %.o: %.c
            $(COMPILE.c) $(OUTPUT_OPTION) $<
        %.o: %.cpp
            $(COMPILE.cpp) $(OUTPUT_OPTION) $<
    以及
        %: %.o
            $(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@
    q些规则里面?变量都是make的标准变?q有上面的CPPFLAGS,CC也是。另外还有一些别?Q这些标准变量有的有默认?例如CC的默认值是cc)Q有的没有。make在处理makefile文gӞ如果没有昑ּ的规则,那么׃查找是否有隐式的可用规则Q如果找到就会利用隐式规则来生成目标?br />
    2.1静态模式规?br />       静态模式规则指的是cM下面的规则:
        $(OBJECTS): %.o: %c
            $(CC) -c $(CFLAGS) $< -o $@
       q跟上面的模式规则类|不同在于规则的适用范围限制在了$(OBJECTS)。也是只针对这些目标来应用规则?br />
    2.2后缀规则
       后缀规则是指用一个或者两个后~q接h作ؓ目标Q同时省略掉条g部分。如下:
       .c.o:
           $(COMPILE.c) $(OUTPUT_OPTION) $<
       Ҏh的地Ҏ条g后缀在前Q目标后~在后?br />       q跟上面的规则:
           %.o: %.c
               $(COMPILE.c) $(OUTPUT_OPTION) $<
       功能是一LQ只是ؓ了兼容一些老的makepȝ?br />       需要注意的是,q里用到的后~必须在已知后~列表里面。有个专用的目标(target)—?SUFFIXES——用来设定后~列表。例如:.SUFFIXESQ?pdf .o .c 。?SUFFIXES:Q也是后面列表为空Q用来清I后~列表?br />

3.自动依赖生成
    q没发现q个在实际应用中有多方便Q有兴趣的可以看看我开始推荐的两个文档?br />

季阳 2008-02-24 11:23 发表评论
]]>
l于体会到emacs比vim方便的地方!http://www.shnenglu.com/lucency/archive/2008/02/16/42818.html季阳季阳Sat, 16 Feb 2008 15:48:00 GMThttp://www.shnenglu.com/lucency/archive/2008/02/16/42818.htmlhttp://www.shnenglu.com/lucency/comments/42818.htmlhttp://www.shnenglu.com/lucency/archive/2008/02/16/42818.html#Feedback0http://www.shnenglu.com/lucency/comments/commentRss/42818.htmlhttp://www.shnenglu.com/lucency/services/trackbacks/42818.html    刚刚试用了一下在emacs里用gdb调试E序Q不是一般的方便啊,呵呵。有直观的界面,而且q有console对gdb方便的控Ӟ使用了好几天emacs了,觉得q是有的比vim方便的地斏V?/p>

    当然Q单U用来编辑文件的话还是vim方便一些,毕竟vim区分模式Q有些操作可以用很少的按键快速的完成?/p>

    不知道emacs的宏录制功能如何。感觉vim里面的宏真的是太好用了,弄得我很是舍不得vim。要是有个编辑器能集合vim的高效快捯emacs的强大扩展能力就好了?/p>

季阳 2008-02-16 23:48 发表评论
]]>
Blog apihttp://www.shnenglu.com/lucency/archive/2008/02/16/42817.html季阳季阳Sat, 16 Feb 2008 15:39:00 GMThttp://www.shnenglu.com/lucency/archive/2008/02/16/42817.htmlhttp://www.shnenglu.com/lucency/comments/42817.htmlhttp://www.shnenglu.com/lucency/archive/2008/02/16/42817.html#Feedback0http://www.shnenglu.com/lucency/comments/commentRss/42817.htmlhttp://www.shnenglu.com/lucency/services/trackbacks/42817.htmlcpp blog api


http://www.shnenglu.com/lucency/services/metaweblog.aspx



google blog api


http://www.blogger.com:80/feeds/default/blogs



季阳 2008-02-16 23:39 发表评论
]]>
˾ƷѾþþþ| þþƷ99Ʒ| ߳þѹۿ| 99þþƷ| ˹ھƷþþþһ| þ99Ʒþþþ| ɫþþۺƷ| ƷþþĻ| Ұ¾þһ| þۺɫɫ| ɫվwwwþþ| ˳AVɫۺϾþ| 99þùۺ| ɫۺϺϾþۺӿ| ɫۺϾþ| ղƷaëƬþ| þ޾Ʒҳ| þ99ֻоƷ66| ۺ˾þôý| ˾þþƷһ | þþþ޾Ʒվ| ɫۺϾþ| þþSS鶹ŷպ| 91Ʒþþþþ | ˾Ʒþ| þþƷAɫ| þþþþþþŮ| ɫۺϾþ| þseƷһ| ŷպ˾Ʒþþѿ| 97Ʒ97þþþþ| ƷþþþӰԺɫ| ۺϾþþƷɫ| 91ƷۺϾþ| þ޹Ʒ| þóۺɫۺ| ھƷþþþþþɬ| ɫ͵͵88888ŷƷþþ| þþþùƷ| þþþAV鶹| ĻþþƷ|