意识流

一晃又有好久没更新博客了,唉

 

由于想起来“啊!得要开始写论文了!”的时候已经非常晚了,所以干脆把之前定的题目给改了,用写过的五子棋程序作为毕业设计,写了三四十页文字翻译了二三十页文字就差不多了,然后再使劲的折腾格式,然后再答辩,两个星期就过去了。

嗯——话说答辩这事儿比想象的轻松多了。

 

和更新博客一起被忘记了要更新的还有某个说了要重写的五子棋AI,还有某个待修复的把普通的后缀树当成广义后缀树使用的bug,还有作为程序员不折腾出一个就会死的属于自己的语言,等等。再说吧。

 

对了,回到学校发现女士们都一律小清新了,叹气,其实我挺喜欢那种样子的,可是大家都一样就没意思了。

千万不要有人把 Enya 和 Cara Dillon 叫做小清新就好。

 

最后,看点花花草草吧

water

grass

flower

flower

恭喜我吧,本博客今天开始得要翻墙才能围观了

倒不是得到了 Game For Windows 的特别关照,只是那个之前提供代替 ghs.google.com 域名解析的好心人干不下去了,所以算了,我也懒得折腾,觉得我这博客有点儿意思的人你就订阅一下或者想办法翻墙吧,获取信息毕竟是基本的生存技能啊。

另外我的 163 博客也可能会重新维护吧,说起来我也算是个网易的人了~另外那里贴照片什么的实在是方便(可是文件怎么办,JS 写的 demo 怎么办……),还不用担心访问量太大(这个可能是想多了,囧,不过某次某游戏被小众软件推荐的时候还真差点爆了当天的流量)…… 那些都再说,反正在网易可以无障碍访问 Google 的系列服务(以及 Twitter 等),我自己继续在这儿自娱自乐还是不会嫌麻烦的~

 

update:

  又找到一个非常不错的免费服务,可惜我都不敢表扬它,怕一旦知道的人多了它就死了……唉……

 

还是流水帐加意识流

1   来到网易实习

这些天我一直在后悔一件事情,就是还没看保密协议上面都写有些什么就把它给签了………而且到目前为止我还不知道上面写了些什么。

嗯,透露一下下肯定不是商业机密的事情吧,网易的伙食还不错。

2   晒晒我的 vimrc

分到电脑的那一天花了一个上午装各种软件,其中最耗精力的要数 VIM 了,自己的 vimrc 没有随身带着,又考虑到里面塞满了各种从网上看到的看上去很好玩但实际上一万年用不到一次的设置,所以干脆自己重新写了一份,里面全是每天都会用到的东西,不长:

set nocompatible
source $VIMRUNTIME/mswin.vim
behave mswin

if has("gui_running")
  colo Ifs
  set gfn=consolas:h11
  set go-=m
  set go-=T
  set cursorline
else
  colo blue
  set nocursorline
endif

syntax on
filetype on
filetype indent on
filetype plugin on

map  <space> :
map  <C-j> <C-w><down>
map  <C-k> <C-w><up>
map  <C-h> <C-w><left>
map  <C-l> <C-w><right>
imap <C-j> <down>
imap <C-k> <up>
imap <C-h> <left>
imap <C-l> <right>

let mapleader=";"
map <leader>w :w!<cr>
map <leader>q :close<cr>
map <leader>[ <C-o>
map <leader>] <C-]>
map <leader><space> <C-w>

map <leader>j :bn<cr>
map <leader>k :bp<cr>
map <leader>bd :bd<cr>

map <leader>tn :tabnew<cr>
map <leader>l  :tabn<cr>
map <leader>h  :tabp<cr>
map <C-t>      :tabnew<cr>
map <C-tab>    :tabn<cr>
map <C-S-tab>  :tabp<cr>

map <silent> <leader><cr> :noh<cr>

map <leader>tl :TlistToggle<cr>
map <leader>gf :NERDTree<cr>

set history=50
set autoread
set ts=4
set expandtab
set smarttab
set shiftwidth=4
set autoindent
set smartindent
set cindent
set scrolloff=5
set wildchar=<Tab>
set wildmenu
set cmdheight=1
set ruler
set nu
set lz
set hid
set whichwrap+=<,>,h,l
set ignorecase
set incsearch
set magic
set showmatch
set hlsearch
set laststatus=2
set statusline=\ %P\ %F%m%r%h\ %y\ %w\ \ CWD:\ %{getcwd()}%h\ \ Line:\ %l/%L:%c
set showmatch
set showmode

其中 <space><leader> 的设置照目前看来貌似很非主流,但是 <space> 不映射成 : 实在是可惜了,毕竟向下滚动有 j 就够了,而且 j 很好用,把 <space> 映射成 : 可以在敲命令的时候感觉十分痛快;另外 ; 就在小拇指上面,用来敲快捷键再方便不过,默认的用于重复 f, t, F, T 的操作也是基本上用不到的,所以,嗯……

这个设置没考虑到 Linux 下的情况,不过除了 gfn 之外其他应该没什么需要改动的吧。另外 Ifs 主题是我在 lettuce 主题的基础上改的,自己喜欢而已。

插件装得不多,因为发现插件多了启动会慢:

  • bufexplorer
  • c
  • mark
  • matchit
  • NERD_tree
  • supertab
  • taglist

c.vim 要修改一下,它有个 <leader>lcs 的映射,除了让我的 <leader>l 响应变慢之外啥用途都没有,罪过很大。

——然后用起来就很舒服了。

3   Qt,各种吐槽

最新版本的 Qt 光是安装文件竟然就有 1.5 GB !!! 它完全疯了!!!

有段时间特别喜欢 Qt,因为它的每个类都显得非常漂亮,比如说字符串类里面会提供宽字符和窄字符的转换,再比如说 QImage 类光是构造函数就有这么多种:

  • QImage ()
  • QImage ( const QSize & size, Format format )
  • QImage ( int width, int height, Format format )
  • QImage ( uchar * data, int width, int height, Format format )
  • QImage ( const uchar * data, int width, int height, Format format )
  • QImage ( uchar * data, int width, int height, int bytesPerLine, Format format )
  • QImage ( const uchar * data, int width, int height, int bytesPerLine, Format format )
  • QImage ( const char * const[] xpm )
  • QImage ( const QString & fileName, const char * format = 0 )
  • QImage ( const char * fileName, const char * format = 0 )
  • QImage ( const QImage & image )

不但功能上一应俱全,而且甚至不需要任何文档,一眼看上去就能知道每个函数是怎么用的。

让人不爽的还是 C++ 的二进制不兼容问题,上次做的一个小实验清楚的说明了想让不同的 C++ 编译器生成的二进制程序共用一个 C++ 类实在是很难的事情;而更早之前的经验表明即使是同一个编译器,不同的版本之间也不见得可以兼容,坑爹啊,也就是说你根本提供不了一个统一的运行时环境,写了个好玩的程序想发给别人装 B,只有每次都把一堆几十 MB 的 dll 和自己几 kb 的程序放在一起才行...这不是比 .Net 还过分吗...

还有,很大程度上托无所不能的 template 的福,C++ 编译的那速度... 上次编译 Qt 4.6 花了 4 个小时左右,后来我专门统计了一下,不算文件头注释和空行,Qt 4.6 一共有 2362083 行代码[1],按 4 个小时来算的话,那么编译+链接速度平均就是每秒钟 164 行了,还有什么语言能比它更慢一点的!!

怎么好像在对 C++ 吐槽……

……都怪 Qt 太大了, FLTK 这种 kb 级别和谁都能静态链接在一起的图形库,用 C++ 我当然没意见,否则您丫每隔一两月更新一次,每次都扔给我好几 G 的东西,还让我以前的程序面临死翘翘的危险,太过分了。

4   乱七八糟

今天在 cnBeta 上看到一句话 “别人一开源,我们就自主研发了。” 笑死了

搞ACM的你伤不起[2]” - 一看就知道是大牛手笔

[1] 包括自动生成的 moc 文件夹下的
[2] 墙外 其实不在墙外... 作者竟然是同事的同学,世界真小

流水帐+意识流 [2]

1. C++ 很变态

去网易面试了,一个 C++ 上的问题把我难到了:“多重继承的时候子类是不是要保存每个父类的虚函数表?”

我回答 “通常是的,但虚继承的时候好像 %#@$%#^&*” —— 其实我就记得虚继承的时候情况很有点乱,忘了究竟有多乱。

回来之后做了个实验:

#include <iostream>
#include <iomanip>
#ifdef _MSC_VER // 老兄,特立独行的永远是你!!
typedef unsigned int uint32_t;
#else
#include <stdint.h>
#endif

using namespace std;

class A
{
public:
    uint32_t x;
    uint32_t y;
    A():x(42),y(24){}
    virtual void f(){
        cout<<"A::f()    [this="<<this
            <<"]; this->x="<<x<<"; this->y="<<y<<";\n";
    }
};

class B:virtual public A
{
public:
    B():A(){}
};

class C:virtual public A
{
public:
    C():A(){x=21,y=12;}
    virtual void f(){
        cout<<"C::f()    [this="<<this
            <<"]; this->x="<<x<<"; this->y="<<y<<";\n";
    }
};

class D:public B, public C
{ 
public:
    D():A(),B(),C(){}
};

template<class T>
void showDetail(T const& v)
{
    int s=sizeof(T)/4;
    uint32_t const* p=reinterpret_cast<uint32_t const*>(&v);
    cout<<"  +-------------+\n";
    for(int i=0;i<s;++i) {
        cout<<"  | "<<setw(11)<<p[i]<<" | ";
        if(p[i]>10000) { // suppose it's a pointer
            cout<<" -> "<<*reinterpret_cast<uint32_t const*>(p[i]);
        }
        cout<<endl;
    }
    cout<<"  +-------------+\n";
}

int main(){
    A a;
    B b;
    C c;
    D d;
    cout<<"a:\n";
    showDetail(a);
    cout<<"b:\n";
    showDetail(b);
    cout<<"c:\n";
    showDetail(c);
    cout<<"d:\n";
    showDetail(d);
    return 0;
}

为的是看看虚继承的时候到底发生了什么,结果又是大出意外,GCC, M$VC, DigitalMars 编译出来的竟各不相同:

GCC

a:  +-------------+
    |     4666904 | -> 4324460
    |          42 |
    |          24 |
    +-------------+

b:  +-------------+
    |     4666924 | -> 0
    |     4666936 | -> 4324460
    |          42 |
    |          24 |
    +-------------+

c:  +-------------+
    |     4666956 | -> 4324764
    |     4666972 | -> 4631892
    |          21 |
    |          12 |
    +-------------+

d:  +-------------+
    |     4666988 | -> 4
    |     4667000 | -> 4324764
    |     4667016 | -> 4631892
    |          21 |
    |          12 |
    +-------------+

M$VC

a:  +-------------+
    |     4305572 | -> 4198672
    |          42 |
    |          24 |
    +-------------+

b:  +-------------+
    |     4305668 | -> 0
    |     4305664 | -> 4198672
    |          42 |
    |          24 |
    +-------------+

c:  +-------------+
    |     4305632 | -> 0
    |           0 |
    |     4305628 | -> 4217088
    |          21 |
    |          12 |
    +-------------+

d:  +-------------+
    |     4305684 | -> 0
    |     4305632 | -> 0
    |           0 |
    |     4305680 | -> 4217088
    |          21 |
    |          12 |
    +-------------+

DigitalMars

a:  +-------------+
    |     4448636 | -> 4205756
    |          42 |
    |          24 |
    +-------------+

b:  +-------------+
    |     4448640 | -> 0
    |     4581960 | -> 5856153
    |     4448636 | -> 4205756
    |          42 |
    |          24 |
    +-------------+

c:  +-------------+
    |     4448660 | -> 4205896
    |     4448648 | -> 0
    |     4448656 | -> 4203244
    |          21 |
    |          12 |
    +-------------+

d:  +-------------+
    |     4448660 | -> 4205896
    |     4448672 | -> 0
    |     4448664 | -> 0
    |     5838868 | -> 4500440
    |     4448680 | -> 4203252
    |          21 |
    |          12 |
    +-------------+

好吧,看这情景,我怎么回答都算不上错,因为哪有正确答案啊。

可惜把 Borland C++ Builder 和 Open Watcom 编译器都删了,不然如果看到 5 种不同的结果岂不是更欢乐~ 唉,难怪 C++ 木有 ABI。

另外,被问到虚函数表在对象的什么部位时我发烧似的答道“在尾部”,囧啊,谁教我的。(不过另一方面,可以诡辩一下的是, C++ 2003 标准甚至没有规定运行期多态一定要以虚函数表的形式实现,只是目前的编译器不约而同的采用了虚函数表而已,所以,嗯...)

Update:

Open Watcom 编译出来的结果是这样的:

a:
  +-------------+
  |          42 |
  |          24 |
  |     4240484 | -> 4198782
  +-------------+
b:
  +-------------+
  |     4240544 | -> 0
  |           8 |
  |          42 |
  |          24 |
  |     4240556 | -> 4198782
  +-------------+
c:
  +-------------+
  |     4240488 | -> 0
  |     4240500 | -> 4198952
  |          12 |
  |          21 |
  |          12 |
  |     4240508 | -> 4198866
  +-------------+
d:
  +-------------+
  |     4240520 | -> 0
  |     4240532 | -> 4198952
  |     4240512 | -> 8
  |          16 |
  |          21 |
  |          12 |
  |     4240540 | -> 4199045
  +-------------+

果然又不同——不过,惊喜啊,它还真把虚函数表放在了尾部。

2. 脑袋短路

还是网易面试,几个算法题都发挥得不够满意,甚至包括生成随机数的题,回头想想也有改进的空间。

嗯...不知道试题有没有涉及到某种需要保密的东西,所以暂时先不谈好了……

3. 春天来了

img_0059.jpg
ps. 实践表明,GCC 编译器会给类的成员函数添一个隐含的 this 参数,通常会自动赋值,然而用奇淫手段弄到了函数地址时也可以手动传对象地址去,就像在 python 中对待 self 那样;然而其他的 C++ 编译器似乎都没这么干,至少你把 void A::f() 当作 void f(A*) 来玩是不行的,可惜了,不然会挺好玩的。

流水帐+意识流

灰机

第二次坐灰机,在这个位置坐着,玻璃如果破了我就会被吸出去,然后在这个引擎里面被卷死掉,引擎内圈会变成红色,我的血和肉块会洒到地面,然而由于是晚上所以地面上不见得会有人发现;另一方面,这种死法肯定比摔下去要好,因为可以免去了很长一段等死的阶段——只要我别太怕死,双手紧抱住引擎的边缘,以至于一点一点的被吸进去。

 

某次 GR 上看到有人分享宅男必备的几个玩具,其中之一便是 Arduino,手痒,便入手了一个(同时还买了个遥控车,打算拆了改用 Arduino 控制,没到手);然而此刻我正在学校,手头上不但没有发光二极管、蜂鸣器、马达、高压包、电烙铁这些老朋友,甚至连导线都没有,于是唯一能用的输出是 13 号口自带的一个发光二极管。

一个发光二极管,那能玩的就只有摩尔斯电码了:

#define UNIT_TIME 250
#define OUTPUT_PIN 13

static char const mCode[][8]= {
    "-----", // O
    ".----", // 1 
    "..---", // 2
    "...--", // 3
    "....-", // 4
    ".....", // 5
    "-....", // 6
    "--...", // 7
    "---..", // 8
    "----.", // 9

    ".-",    // A
    "-...",  // B
    "-.-.",  // C
    "-..",   // D
    ".",     // E
    "..-.",  // F
    "--.",   // G
    "....",  // H
    "..",    // I
    ".---",  // J
    "-.-",   // K
    ".-..",  // L
    "--",    // M
    "-.",    // N
    "---",   // O
    ".--.",  // P
    "--.-",  // Q
    ".-.",   // R
    "...",   // S
    "-",     // T
    "..-",   // U
    "...-",  // V
    ".--",   // W
    "-..-",  // X
    "-.--",  // Y
    "--.."   // Z
};

void light(int time) {
    digitalWrite(OUTPUT_PIN, HIGH);
    delay(time);
    digitalWrite(OUTPUT_PIN, LOW);
}

void alpha(char c) {
    char const *code=0;
    if(c>='0' && c<='9') {
        code=mCode[c-'0'];
    } else if(c>='a' && c<='z') {
        code=mCode[c-'a'+10];
    } else if(c>='A' && c<='Z') {
        code=mCode[c-'A'+10];
    } else {
        delay(4*UNIT_TIME); // 应该停顿 7 个单位,不过每个单词结束后都已经停顿了 3 个单位时间
        return;
    } 
    for(;*code;++code) {
        switch(*code) {
        case '.':
            light(UNIT_TIME);
            break;
        case '-':
            light(3*UNIT_TIME);
            break;
        }
        delay(UNIT_TIME);
    }
    delay(2*UNIT_TIME);
}

void say(char const* sentence) {
    for(char const* p=sentence; *p; ++p) {
        alpha(*p);
    }
}

void setup() {
    pinMode(OUTPUT_PIN, OUTPUT);
}

void loop() {
    say("I Feel So Alone   ");
}

wikipedia 说同一个单词不同字母间有三个单位的间隔,但我发现这很不好认;接着我就想如果再多买一块板子,接上光敏电阻,一个亮,一个读,岂不是很好玩;接着又想,如果真有两块板子,那我何必用摩尔斯电码呢,用灭灯代表 0,亮灯代表 1,直接传输就好,爱折腾的话想办法用数组保存一个已经预先设定好的霍夫曼编码似乎更可以装B了,接着我又想,那我是不是要想办法让它知道什么时候是对方传输完了,什么时候是在传输一大堆的 0 呢,要不在每组数据末尾加上一个 END,开头加上 BEGIN?可是那我真要传输 BEGIN 这个单词了又怎么办?用 \ 标记?对呀,然后 \ 就表示为 \\,太完美了——可是那突然掉电怎么办,要么每隔一秒种说一次“还没完!”?…………。

Update: 我傻,用校验码啊

 

另外,开始看了看 Haskell,因为它的自我介绍很有意思,大意是 “即使你学了 Haskell 什么实际用途都没有,至少也可以提高你的编程能力”,好吧,这个我相信。

另外就我目前的认识来看 Haskell 推理形式的函数定义方法和 C++ template 元编程简直太像了 @_@ 说 C++ 恐怖果然是有道理的……

 

你也纠结过这个吗

横坐标是尺度较大的时间单位,纵坐标可以理解为每天花在对方身上的时间和精力。

 

于是我的期望是这样的:

而这样的情景也是我完全乐意接受的:

===============

另一方面,对方的期望是这样的:

于是后来,实际的情景就成这样了:

 

不知道有没有共性?

标题不知道怎么取

回家了,睡觉,看书,编程,聚会,无聊的时候还做做 ACM 玩,最近就是这样在过吧……嗯,和 MM 闹别扭了,烦,下面是意识流。

 

稍稍 PS 了一下,作壁纸,可惜的是镜头不给力,闪光灯也不给力,于是乎只好用很高的 ISO 来拍这些玩意儿——于是乎噪点很多,木有办法。

 

 

另外,你应该也会被这样的问题折腾过吧:给你一个容量 3 升的杯子和一个容量 5 升的杯子,想要 4 升水,怎么办;一直觉得这问题完全是闲得蛋疼,但某日在 POJ 上面遇到了,倒是觉得有点好玩了,其实就是一个广度优先的搜索(求最优解),啥技术含量都没有:

杯子 1:(ml)
杯子 2:(ml)
目标:(ml)


您若是很无聊,不妨可以试试 11111, 130, 121 之类的变态数据,我看它有那么多个步骤就觉得很欢乐,可能是幸灾乐祸的感觉吧:叫你拿这玩意忽悠我,看我以后怎么忽悠你。( BTW, 做这种实验的时候建议用 Chrome )

悲剧啊,只能混个纪念品玩玩

我的程序在这儿了: http://code.google.com/p/harmless/

很想把他叫做 Mostly Harmless,可是不能太狂啊,它明明就是 TOTALLY harmless。

另外眼下 svn 连接不上 google code, 不出意外的话原因应该就是众所周知的那个吧,我最近也懒得折腾了,所以代码先就在压缩包里看吧。

核心代码在这里: http://code.google.com/p/harmless/source/browse/trunk/main.cpp

Windows / Linux 可执行程序以及源码压缩包在这里: http://code.google.com/p/harmless/downloads/list

 

用到的技术有 alpha-beta, iterative deepening, transposition table, null move heuristic, bit board, threat space search (没写完而且不准的版本), principal variation ,然而这些算法中没有几个是容易调试的,再加上核心的评价函数依然是瞎掰的(试过用进化算法让它进化出一个比较靠谱的参数,然而结果是它进化到了一种自娱自乐的境界,和自己这种风格的AI竞赛倒还好,可实际棋力依旧不行),再加上整体框架上还有好些不成熟的地方,比如说我干嘛要记录下某个空位在四个方位的子力呢,记录下最好的两个方位就OK了啊——反正,改来改去,最后一次练习赛之后的版本棋力竟然越变越差,真伤心。

泥人五子棋里面似乎有好几个可以借鉴的想法,开局库可以多折腾几条记录进去,proof number search + threat space search + 开局库效果应该就会还可以了吧;另外可惜 db-search 接触得太晚了,它的思想也很吸引人。

寒假重写。

 

 

最后,附上两张几天前在厕所玩水的照片:

 

还有好多张,有时间了再传到网易去。

为了证明我还没死…这是我写的五子棋

完成度不到 40%,棋力相当差,最核心的在找不到必杀时的局面评价函数完全是瞎掰的……虽说如此当我小放水时它偶尔还是能赢的:

white-won

想在五子棋方面找自信的童鞋可以试试: gomoku.7z

 

竞赛毕竟是竞赛,技术细节源代码神马的就不透露鸟……何况还没写完呢……

BTW 为了保证逆向我的程序的成本比从头开始写一个五子棋程序要高,俺把 exe 文件加密鸟,如果杀毒软件抱怨的话(也不太可能抱怨啦,只是怕有这种可能性)——就请你做一个艰难的选择吧。

 

对某个五子棋AI竞赛很有兴趣!

具体的说是北大的这个: http://www.botzone.org/RATE/news/zhengbasai.htm

想写个厉害的五子棋AI是俺上大学之前就有的欲望,到现在也许可以试试手了……

至于 gomocup.... 还没有那胆子……

算是……神曲吧…………

我一直比较喜欢 Kate Bush 的,那诡异独特的风格很有味道,而且她的很多歌都超有难度、谁也模仿不来(嗯……就算不喜欢也可以用来试耳机)……

好吧,预热一下,先来听一首我觉得很好听的歌,一会儿再来交流一下我今天头脑发热一口气下载了她的三张专辑之后的发现……

歌名是呼啸山庄,对应书中闹鬼的那一段……

 

 

 

好了,下面……

 3.141592653589793238462643383279502884197
16939937510582319749445923078164062862088
214808651328230664709384460955058223

BTW. 不知是我听错了还是她背错了……前这么多位其实应该是

3.141 592653589793238462643383279502884197
16939937510582097494459230781640628620899
862803482534211706798214808651328230

 

 

PS. 貌似今晚有望见到外星飞船

PPS. 会不会有外星人接我走啊?

幸好 INCEPTION 木有用到 Fractal Maze

第一次见到 Fractal Maze 是在 Matrix67 的博客上,看了一眼直接狠识趣的绕道走了;今天复习开小差的时候木石短信骚扰我说找到“你证明不了这句话是正确的”的来历了,然后我就自然而然的顺着想到了自指,想到了“这句是假话”,想到了“没有任何事情是绝对的”,想到了“我知道我什么都不知道”,想到了“下面一句话是错的;上面一句话是对的”,想到了 —— Fractal Maze.

 

下图就是一个 Fractal Maze;它作为一个迷宫游戏、你作为玩家,老套的剧情出现了:你要从 S 点走到 F 点。

f-maze

// 图中间的白色部分是迷宫的复制:

f-maze-1

画出来之后当然简单,一眼就可以看出解了;然而就算你知道解,回头看看上面那迷宫,恐怕还是会很难绕出来吧。

 

后来,我就想到,幸好 INCEPTION 没有用到 Fractal Maze,不然大家都会晕死掉的吧。

 

【END】


PS. 下面是几个 Fractal Maze,任务都是从负极走到正极,感兴趣的可以看看吧,反正我是不适合玩这玩意儿的。

image

http://www.mathpuzzle.com/18Nov2003.html

解:http://www.mathpuzzle.com/smallFractalMaze.txt

 

image

http://numb3rs.wolfram.com/406/

 

image

http://www.mathpuzzle.com/18Nov2003.html

解: http://www.mathpuzzle.com/solutionFractalMaze.gif || http://www.mathpuzzle.com/Fractalsolution-A.gif

 

image

http://www.astrolog.org/labyrnth/equivlnt.htm

 

image

http://www.mathpuzzle.com/26Jun05.html

解:http://www.mathpuzzle.com/PullenFractalMaze.txt

 

关于安全聊天的一个想法

麻花疼和360的争吵现在真成了个全民参与的讨论话题呀,真可惜我对360没有感情,表达出对 QQ 的感情则会显得十分不斯文,所以这篇不打算谈那些玩意儿;倒是看到网上有些评论很是有趣,比如说我最喜欢的一条:腾讯顺便做点好事吧,请用户停止使用ie6,否则无法使用qq

 

嗯,我在想的问题是,如果我要做出一个是人就敢相信的闭源聊天软件的话,恐怕只能这样:

  1. 允许导入 (个人已有的) PGP Public Key 和个人账号绑定在一起;[ 不需要你来生成;尤其不需要你知道密钥 ]
  2. 假如我绑定了一个公钥,别人发给我消息时,将会在他的客户端用我的公钥实现非对称加密; [ 不要把隐私传到了你的服务器 ]
  3. 对话过程中需要上传到服务器的数据要么是明文+公开协议,要么就是通过我的公钥加密了的密文,我可以随时通过抓包工具来进行检查,确保没有小动作; [ 不要传些我不需要你传的数据 ]
  4. 我收到信息之后不需要聊天软件帮忙解密;帮我打开 PGP 窗口或是 GPG 控制台、输入好密文、等着我输密码那倒还可以。 [ 你还是不要接触密钥 ]
  5. 手动下载更新包,不需要用到管理员权限、不需要网络连接。
  6. 谁他妈愿意用这么复杂个玩意儿呀!

我想这些乱七八糟的是因为好奇我什么时候会愿意继续用 QQ ;于是结论便是: 基本上不太可能有那一天了。

 

我打算让这个博客看上去相对主流一点了

——于是它就成了现在的样子。

 

Hint: 快捷键有 j, k, g, G, z, r, v 以及两个彩蛋

j, k, g, G, z 都不知道是什么的话你可就太过于主流了。

v: view 

r: reply

 

另外很不解 RC4 犯什么错了,实现 RC4 加密的 JS 文件会被 Avast! 报毒(JS:Packed-C[Trj]),弄得我很烦,如果说用 JS 加密就是罪的话为啥 AES 没问题呢:
http://g00d.blogbus.com/logs/56991621.html

CKEditor Plugin For Micolog

个人比较喜欢 CKEditor, 所以就给 micolog 整了个 ckeditor 插件: 

http://micolog.appspot.com/plugins/view?key=agdtaWNvbG9nchsLEgZQbHVnaW4iD0NLRWRpdG9yIHBsdWdpbgw

 

其实 tinymce 也还可以啦,只是我做好这个插件之后才发现原来在 /views/admin/entry.html 中把 tinymce 的初始化代码改成这样,它也完全可以接受了:

(主要是它之前总是自作主张的把 URL 改成相对路径 —— 而且还是个错的,还有 html 的预览把 <p> 弄没了,结构一塌糊涂)

<script type="text/javascript">
$('#content').ready(function() {
    $('.editor-toolbar').show();
    var editor = null;
    $('#edButtonPreview').click(function(){if(editor);else initTinymce();});
    $('#edButtonHTML').click(function(){if(editor)tinymce.execCommand('mceRemoveControl',false,'content'); editor=null;});

    function initTinymce(){
        editor = $('#content').tinymce({
            script_url : '/tinymce/tiny_mce.js',
            theme : "advanced",
            
            plugins : "safari,pagebreak,save,advhr,advimage,advlink, inlinepopups,media,directionality,visualchars,nonbreaking,fullscreen,preview,autoresize",

            theme_advanced_buttons1:"bold,italic,underline,strikethrough,|,bullist,numlist,blockquote,|,forecolor,backcolor,|,justifyleft,justifycenter,justifyright,justifyfull,|,link,unlink,image,code,preview,|,fullscreen",
            theme_advanced_buttons2:"styleselect,formatselect,fontselect,fontsizeselect,|,pastetext,pasteword,removeformat,|,media,charmap,|,outdent,indent,|,undo,redo",
            theme_advanced_buttons3:"",
            theme_advanced_buttons4:"",

            theme_advanced_toolbar_location : "top",
            theme_advanced_toolbar_align : "left",
            theme_advanced_statusbar_location : "bottom",
            theme_advanced_resizing : true,
            
            content_css : "/tinymce/wordpress.css",
            extended_valid_elements : "textarea[cols|rows|disabled|name|readonly|class],script[src,type],canvas",
 
            file_browser_callback: 'micolog_file',
            relative_urls: false
        });
    }
    initTinymce();
});
function micolog_file(field_name, url, type, win){
   if (type=='image')
       {ext='jpg|png|jpeg|gif|svg';}
   else if (type=='mdeia')
       {ext='swf|wmv|avi|wma|mp3|mid|asf|rm|rmvb|flv';}
   else
       {ext="*";}

    tinyMCE.activeEditor.windowManager.open({
        file : '/admin/uploadex?ext='+ext,  
        title : '{%trans "Files"%}',
        width : 420,
        height : 200,
        resizable : "no",
        inline : "yes",
        popup_css: false,
        close_previous : "no"
    }, {
        window : win,  
        input : field_name  
    });
    return false;
}
</script>

老宋:“喂?
我正要给你打电话呢!横幅什么的准备好了没?
领导要来?
座位安排有什么讲究没?是要领导坐中间还是要专家坐中间?
哦,哦,领导啊
那左右有什么 关系没?
那怎么搞?
算了你来安排吧
……

 

挂了电话,跟办公室老师聊天,“什么领导啊”,“什么级别”,“那算是什么领导,比我都小”云云,听着我想笑,妈的什么世道。

 

PS. 俺今天总算完成了 PCA 算法+摄像头实时匹配人脸的某个程序,可以在专家们到来的时候用来 zhuangbility,老宋一高兴把一块从米国带回来的好时巧克力丢给我了,挺好,物质奖励比精神奖励实在多了。

PPS. 老师的电脑配置真好,气死我也,上面提到的算法在我自己的电脑上远远谈不上实时,但在他的电脑上就是了;另外那键盘呀,玩上篇的游戏轻松的就到了 18.53s 。

50 题

写了个游戏,基本上和 iPhone 上的 >=< 一模一样吧——由于我很喜欢后者,所以自己也做了个,便于在电脑上玩。 玩法很简单:小于 10 的式子丢到左边,大于 10 的式子丢到右边,等于 10 的则丢到下面,看谁速度快。一起来试试吧:

下载 swf


  还有个更具有挑战性的版本,玩的时候建议把音量开到最大:

下载 swf

 

Update:

其实,还有个比较符合宅男品位的:

下载 swf

 

Update Oct/23th/2010:

  • 多谢 枪毙自己 童鞋帮我找到的链接(AS3输入法的切换与限制),俺现在加上了号称能关闭输入法的代码——只是由于俺自己压根就没能在游戏界面中打开过输入法,所以效果无从验证了,希望大家玩得愉快,有问题欢迎反馈,而如果像枪毙自己童鞋这样还帮我找到了解决方案的话我会感动得一塌糊涂的。

  • 另,也欢迎大家把 flash 游戏下载下去玩;flash 的独立播放器看上去似乎下载不到了,但那是假象,其实只是被 adobe 藏起来了而已,它在这里:
    http://download.macromedia.com/pub/flashplayer/updaters/10/flashplayer_10_sa.exe

  • 最后,我自己目前的最好成绩是 16.65 秒全对,欢迎挑战。

Update Oct/24th/2010 :

图:mississippimississippi + iamif 的后缀树

花了很多功夫,俺终于实现了生成后缀树的线性算法,添加多个字符串真麻烦,而且后缀树的结构也真够复杂的,有图为证:


Suffix-Tree(mississippimissippi)+SuffixTree(iamif)

黑线是父子关系,红线是后缀链接,图像是用俺的程序生成 dot 文件、然后用 Graphviz 自动生成的(真丑),程序以及算法下次再解释,那也预计将是俺本学期的最后一篇日志,没时间复习了。

BTW,近期通过 www.if-yu.info 域名访问俺的博客似乎很慢 —— 那么直接访问 http://iamyuguo.appspot.com/ 应该好些

MP/KMP 算法详解 [草稿]

MP/KMP 算法详解

By If

1   Prologue

本篇文章主要针对的是对字符串匹配有兴趣的生物以及被某版本数据结构与算法教材中的 KMP 算法讲解弄得不知所云但与此同时却还难能可贵地保持着旺盛求知欲的不幸生在了错误年代的可怜童鞋,其他生物阅读本文前请慎重考虑因为它 可能对您的大脑(如果有)、小脑甚至包括脊髓都造成严重且不可恢复的创伤

2   Notations

下文可能会提到 “模式串”、“文本串”、“窗口” 这些词,它们的定义如下,如果这些文字使你头晕,请及时做好救治准备。

模式串、文本串
所谓 模式串 ,是指你想要找到(或者得到位置 &etc.)的字符串;而 文本串 ,则是指搜索的目标字符串。
比如说你要在 "lucky dog" 中寻找 "dog" ,那么 "dog" 是模式串, "lucky dog" 则是文本串;
而你若要在 "If is a lucky dog" 中寻找 "lucky dog" ,那么 "lucky dog" 便成了模式串, "If is a lucky dog" 则是文本串。

Understand?

窗口
无论用什么样的搜索算法,在搜索的过程中,总是需要将模式串与文本串进行比较,它们对齐的那部分区域,也就是们关心的那块区域,咱称为 窗口

另外,为了避免让已经适应 C/C++/C#/D/Java/JavaScript/Python/Go/... 语言思维的童鞋多绕一个弯,本文用到的数组下标都以 0 开始 —— 甚至包括费波拉契数列也如此。

3   Main Idea

MP/KMP 字符串搜索算法的思想精华在于利用已经匹配的部分包含的信息,加速搜索的过程。

嗯——已经匹配的部分包含什么信息?

它已经匹配了!

举例说,在某个字符串中查找子串 A B C D A B D A C 时,如果遇到 A B C D A B ,而紧跟其后的不是 D ,这时候我们可以将窗口右移四位(而不是一位),因为既然 A B C D A B 已经匹配了, 那么移动当前窗口之后 已经匹配过的地方 肯定需要保证 依然匹配 ,这里最好的做法即让 A B 相互对齐:

. . A B C D A B ? . . . . . .
    A B C D A B D A C

=>

. . A B C D A B ? . . . . . .
            A B C D A B D A C

因为,看呀,如果只右移一位,那么:

. . . . . . . . . . . . . . . // 先不用管这个字符串
    A B C D A B . . .         // 已经匹配的部分
=>    A B C D A B D A C
      |
    糟糕!

如上面所示, A B C D A B 匹配了,那么移动一位之后,第一个字符 A 就肯定会对着 B ,绝对不可能在这个地方找到匹配

右移两位、三位或者四位时发生的状况可以依此类推;而右移四位时就不同了:

. . . . . . . . . . . . . . . // 暂时还不用管这个字符串
    A B C D A B . . .         // 已经匹配的部分
=>          A B C D A B D A C

这个时候才 可能 成功匹配。

4   MP 算法

4.1   原理

MP 算法基于这样一种观察:

Note

注意了,这里以及下面所说的 前缀后缀 都是指 不包括自身 的“真”前缀或后缀 ( proper prefix/suffix )

发生了不匹配之后,移动窗口时,定要保证 将模式串已匹配部分的一个前缀和一个相同的后缀对齐,并使这个前缀尽可能长

什么意思?

首先让我们列出模式串 A B C D A B 的所有前缀:

0: /*Empty*/
1: A
2: A B
3: A B C
4: A B C D
5: A B C D A

我们再列出它所有的后缀:

0: /*Empty*/
1: B
2: A B
3: D A B
4: C D A B
5: B C D A B

发现前缀 A B == 后缀 A B ,将它们对齐(即,接下来直接从第 3 位开始比较),完美了,前两位不必重复比较了。

原理上说也不难理解: 从左向右移动窗口的过程就是用前缀去匹配后缀的过程 ,而第一次匹配成功的肯定是最长的相同前缀/后缀 —— 在上例中,两个空字串也相等,可是如果将它们对齐的话那可就“移过头了”。

这么看,我们发现,在模式串的每一个位置上,匹配失败之后能最大限度的将窗口移动多少位 —— 即,与什么位置对齐, 只与模式串在该位置前方的子串有关 ,与文本串无关,与模式串在该点之后的字符也无关。

于是,自然而然的就想到了,为什么不把这么个失败后对齐的位置存放在一个数组中呢,这样每次匹配失败之后就按照它的指示进行跳转。

F[i] == max{ j | pattern[0:j) == pattern[i-j+1:i+1) and 0<j<i } ,也就是当前位置上保证前缀等于后缀的最大长度。

对于模式串 A B C D A B D A C , F 数组如下:

Index 0 1 2 3 4 5 6 7 8
Pattern A B C D A B D A C
F 0 0 0 0 1 2 0 1 0

继续扯,在位置 i 匹配失败之后,可以将窗口继续右移一位,并从 F[i-1] 位置开始继续比较模式串与文本串(按照定义,pattern[ 0 : F[i-1] ) 已经保证匹配了),用代码表示的话就是这样:

int MP(char const* pattern, char const* text)
{
    int i=0,j=0,m=strlen(pattern);
    int F[m];
    calcF (pattern, F); // 计算跳转数组
    for(;text[i];++i,++j) {
        while(j>=0 && pattern[j]!=text[i]) {
            // 第一个字符不匹配则右移窗口、从 0 开始比较
            if (j==0) {
                j=-1;
                break;
            }
            j = F[j-1]; // 对齐
        }
        if(j>=m-1) {    // 找到了!
            return (i+1-m);
        }
    }
    return -1;
}

—— 多和谐呀!



对了,内个 F 数组怎么算?

4.2   跳转数组的计算

观察一下,希望求出拥有相同后缀的最长前缀,这个过程不也是一个字符串匹配的过程吗:

1.  A B C D A B D A C
    A B C D A B D A C
    |
    0  // 定义为 0

2.    A B C D A B D A C
    A B C D A B D A C
      |
      0 // 匹配部分的长度

3.      A B C D A B D A C
    A B C D A B D A C
        |
        0

4.        A B C D A B D A C
    A B C D A B D A C
          |
          0

5.6.        A B C D A B D A C
    A B C D A B D A C
            | |
            1 2

7.              A B C D A B D A C
    A B C D A B D A C
                |
                0

8.                A B C D A B D A C
    A B C D A B D A C
                  |
                  1

9.                  A B C D A B D A C
    A B C D A B D A C
                    |
                    0

如上面所示,将模式串向右移动,并与自身做比较,在位置 i 上,pattern[i:] 与 pattern 自身相匹配的部分的长度就是 F[i]

注意第6步到第7步! 为什么可以直接右移两位呢?

—— 因为 F[1] 已经算出来了!于是我们可以将之前 MP 算法中的思想用在这里,聪明的你想到了没有?

用代码来说就是这样的,看不懂的话我会很伤心:

void calcF(char const* pattern, int* F)
{
    int i=0,j=0;
    for(;pattern[i];++i,++j) {
        while(j>0 && pattern[j-1]!=pattern[i]) {
            j = F[j-1];
        }
        F[i] = j;
    }
}

4.3   另一种表示

对了有没有人觉得 MP 算法中对于第一个字符不匹配时的特殊处理感觉到很生硬的?

嗯,其实呢,考虑到第一个字符失败时的特殊情况其实也不怎么特殊,不如干脆把这种情况也放到 F 数组中去统一处理好了:

Index 0 1 2 3 4 5 6 7 8 9
Pattern A B C D A B D A C  
F -1 0 0 0 0 1 2 0 1 0

这样,MP 算法表达起来更简单了:

int MP(char const* pattern, char const* text)
{
    int i=0,j=0,m=strlen(pattern);
    int F[m+1];
    calcF (pattern, F); // 计算跳转数组
    for(;text[i];++i,++j) {
        while(j>=0 && pattern[j]!=text[i]) {
            j = F[j];   // 对齐
        }
        if(j>=m-1) {    // 找到了!
            return (i+1-m);
        }
    }
    return -1;
}

calcF 也并没有因此变复杂:

void calcF(char const* pattern, int* F)
{
    int i=0,j=-1;
    F[0]=-1;
    for(;pattern[i];++i,++j) {
        while(j>=0 && pattern[j]!=pattern[i]) {
            j = F[j];
        }
        F[i+1] = j+1;
    }
}

理解之后就会觉得,这种表示方法比较 “省事”;下面咱就都用这种表示得了。

Nevertheless, 其实它们是一码事。

4.4   可是...

我们再停下来看看 “暴力” 搜索算法 —— 有没有可能暴力算法比 MP 算法还快呢?

答案是 “Yes!”

( 哈哈!你输了吧! )




让我们想象一下在一个字符集很大的串 —— 比如说 UTF-16 字符串吧,中寻找一段模式串;而模式串的第一个字符出现在文本串中的频率根本就不大,那么看看第一次匹配失败时它们两者工作的流程吧:

MP 算法 暴力算法
  1. i>=n?
  2. j>=0? ( 此时 j == 0 )
  3. 比较 pattern[j] 与 text[i]
  4. j = F[j]
  5. j>=0? ( 此时 j == -1 )
  6. j = j+1
  7. j >= m?
  8. i = i+1
  9. 到第 1 步
  1. i>=n?
  2. j = 0
  3. j < m?
  4. 比较 pattern[j] 与 text[i]
  5. i = i+1
  6. 到第 1 步

嗯,所以说,这个地方是有优化空间的,Knuth、Morris、Pratt 的论文 [1] 中有提到,俺就不展开了 —— 因为真正牛B的优化在下面:




5   KMP 算法

其实 MP 算法的效率还有提升的空间,不过从模式串 A B C D A B D A C 中看不明显;我们试试这样一个模式串: A B A B A B C

假设在 A B C A B C A B A B A B C A C 中查找 A B A B A B C ,按照 MP 算法的思想,先算出 F 数组:

Pattern A B A B A B C  
F -1 0 0 1 2 3 4 0

于是查找的过程就是这样的:

1.  A B C A B C A B A B A B C A C
    A B A . . . .

2.  A B C A B C A B A B A B C A C
        A . . . . . .

3.  A B C A B C A B A B A B C A C
          A B A . . . .

4.  A B C A B C A B A B A B C A C
              A . . . . . .

5.  A B C A B C A B A B A B C A C
                A B A B A B C

从第 1 步到第 2 步、从第 3 步到第 4 步,我们发现,字符 AC 的不匹配导致了第一次失败,然后紧接着又直接导致了第二次失败。

如此,我们又惊喜的发现,在 A B 之后若是遇到不是 A 的字符,我们完全可以跳三步!因为跳两步的话算是把 A 对齐了 —— 可是它们会被对齐到一个不是 A 的、将会导致匹配失败的字符上面去。

这样的规则有什么规律呢?我直接放出代码吧:

// 这是我们计算 MP 算法中的 F 数组的函数:
void calcF(char const* pattern, int* F)
{
    int i=0,j=-1;
    F[0]=-1;
    for(;pattern[i];++i,++j) {
        while(j>=0 && pattern[j]!=pattern[i]) {
            j = F[j];
        }
        F[i+1] = j+1;
    }
}

睁大眼睛准备找茬喽:

// 只需要改一句话:
void calcF(char const* pattern, int* F)
{
    int i=0,j=-1;
    F[0]=-1;
    for(;pattern[i];++i,++j) {
        while(j>=0 && pattern[j]!=pattern[i]) {
            j = F[j];
        }
        // !这里!
        F[i+1] = pattern[j+1] == pattern[i+1]?
                   F[j+1]:
                   j+1;
    }
}

为什么呢?因为对于同一个字符导致的失败,失败在前面应该跳到哪里,到后面就还是应该跳到哪里。

另外,这个时候咱似乎就比较喜欢把这个数组称作 next 数组了 —— 其实还是同一回事。

那么, A B A B A B Cnext 数组如下,请您欣赏:

Index 0 1 2 3 4 5 6  
Pattern A B A B A B C  
Next -1 0 -1 0 -1 0 4 0

6   复杂度分析

至于为什么 KMP 算法的复杂度是线性的,我们再回头看看 另一种表示 一节中的算法主体:

int MP(char const* pattern, char const* text)
{
    int i=0,j=0,m=strlen(pattern);
    int F[m+1];
    calcF (pattern, F);
    for(;text[i];++i,++j) { // j 增大,i 增大
        while(j>=0 && pattern[j]!=text[i]) {
            j = F[j];       // j 减小
        }
        if(j>=m-1) {
            return (i+1-m);
        }
    }
    return -1;
}

i 只有增大的份,所以 ++i 最多执行 n 次,这个很显然。

j 初始值为 0,一共增加了 n 次,而 j>=-1 ,于是 j = F[j] 这一句最多也就执行了 n+1 次(否则就会出现 j<-1 的情况了)。

所以就是线性的了!

7   KMP 算法的最长停顿

为了说明 KMP 算法在文本串上的某一个字符上进行了很多次比较的极限情况(也就是所谓的停顿或者E文的 delay ),我们首先要介绍一下 “费波拉契串” —— 因为,很巧,它就是能使 KMP 算法达到最糟糕状况的模式串,一会儿我们会说到。

提到 “费波拉契” ,相信不少人会直接想到 1, 1, 2, 3, 5, 8, 13, 21, 34 ... ,是的,费波拉契串的定义也十分类似:

设 P 是个费波拉契串,那么:

P[0] = b
P[1] = a
P[i] = P[i-1]P[i-2]

所以:

P[2] = ab
P[3] = aba
P[4] = abaab
P[5] = abaababa
P[6] = abaababaabaab
P[7] = abaababaabaababaababa
P[8] = abaababaabaababaababaabaababaabaab
...

计算出 P[7] 的 F 数组和 Next 数组如下,我们一会儿要用到,你也可以先把它当作找规律的题看看:

P a b a a b a b a a b a a b a b a a b a b a  
n 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
F -1 0 0 1 1 2 3 2 3 4 5 6 4 5 6 7 8 9 10 11 7 8
Next -1 0 -1 1 0 -1 3 -1 1 0 -1 6 0 -1 3 -1 1 0 -1 11 -1 8

费波拉契串有几个性质值得注意一下:

Note

文章前面已经假设了我的世界里费波拉契数列下标是从 0 开始的,这里再强调一遍。

  1. strlen(P[n]) == Fibonacci[n] (这个应该很容易理解吧)

  2. 设函数 c(t) 的作用是交换字符串 t 的最后两个字符,例如 c("abcdef") == "abcdfe",那么当 n>=2 时 P[n-1]P[n-2] == c(P[n-2]P[n-1]) :
    n == 2 时这是显然的;
    当 n>2 时可以用数学归纳法证明:
    c(P[n-2]P[n-1]) = P[n-2]c(P[n-1]) = P[n-2]P[n-3]P[n-2] = P[n-1]P[n-2]
  3. 由上面两条性质我们又可以推导出:
    Next[Fibonacci[n]-2] == F[Fibonacci[n]-2] == Fibonacci[n-1]-2, n>=2
    这是因为,可以把一个费波拉契串分解开:
    P[n] == P[n-1]P[n-2] == P[n-2]P[n-3]P[n-2] == c(P[n-3]P[n-2])P[n-2] == P[n-3]c(P[n-2])P[n-2] == ...
    具体以 P[7] 为例,
    P[7] == ab-ab-ba---ab------ba
    其中省略掉的部分根据 性质2 表现出的规律与前方相等,因此如果在 P[7] 的最后一个字符 b 处发生了不匹配,接下来应该在下列位置重新试着匹配:
    ab-a--b----a-------b-
    它们正好占据着 Fibonacci[2]-2, Fibonacci[3]-2, .. , Fibonacci[7]-2, ... 的位置。

因此,如果在费波拉契串的第 n 位,n == Fibonacci[k]-2 上发生了不匹配,接下来则还需要 k-1 次比较;

又因为 Fibonacci[k-1] == (φk - (-1)kφ-k)/sqrt(5) == round( φk / sqrt(5) ), 于是可以解得 k ~ logφ(n),其中 φ 是黄金比例 (1+sqrt(5))/2 == 1.618...

—— k 便是文本串上的一个字符的最多比较次数。

8   为什么费波拉契串这么神奇

为了证明为何费波拉契串就是使停顿时间最长的模式串,我们再看看 MP 算法的基本思想:将字符串已匹配的一个前缀和一个对应的后缀匹配。

假设字符串 S 有且仅有一个相等的前缀和后缀(设为 a ),那么 S 可以表示为

S = aB = Ca

再假设 a 本身也有且仅有一个相等的前缀和后缀(设为 e ),那么 a 也可以表示为

a = eF = Ge

对应 MP 算法,匹配 Ca 时若在 a 之后失败,则会将 aB 的 a 与其对齐:

Cax
Cay

==>

Cax
 aBy

若在 B 的第一个字符处再次失败,则下一次对齐是这样的:

CGex
 GeBy

==>

CGex
  eFBy

KMP 算法在这里还要求 F 的第一个字符和 B 的第一个字符不等(否则会跳过这一段)

我们很容易可以证明想要 KMP 算法在这个地方停留尽可能长的时间需要满足 |S| <= |e| + |a| :因为若 |e| + |a| > |S|,那么令 d = |e| + |a| - |S| 则 Ca = CGe = aB 算式中, a 和 e 将有长度为 d 的重叠,于是 B 的第一个字符等于 e[d];同理,在 aB = eFB = Ca 算式中,可以得到 F 的第一个字符为 a[d],由 a = eF 可以得到 a[d] = e[d],和 KMP 的要求不符。

于是 |S| == |e| + |a| 是使 KMP 算法的停顿时间达到最长的极限情况——很容易发现,满足这条件的便是费波拉契串了。

9   PS

对了,如果我要找出模式串在文本串中所有的出现怎么办?

提示一下:目前为止 F 数组(或者 next 数组)的最后一个元素我们还没有用到过是不是?

10   References

[1]KNUTH D.E., MORRIS (Jr) J.H., PRATT V.R., 1977, Fast pattern matching in strings, SIAM Journal on Computing 6(1):323-350.
[2]http://www-igm.univ-mlv.fr/~lecroq/string/node8.html#SECTION0080
[3]http://www.topcoder.com/tc?module=Static&d1=tutorials&d2=stringSearching

Last update: 2010/11/23 18:53:11

有人注意到上海科技馆的男厕标识很奇特吗??

几天前翻照片玩,找出了09年10月7号在上海科技馆拍的一张照片,当时我就不理解,目前我还是不理解。

 

iiiiif 拍攝的上海科技馆男厕。

 

如图,求解。

 

——对了,俺这儿 Flickr 有访问障碍,所以看不到图而且又不想那个啥的话:网易 || 原图

也可以考虑围观一下我那个只谈风月的blogbus :: 那一次的上海之行