移山之道

关注软件开发技术和管理的社区网站

欢迎光临 移山之道 登录 | 注册 | 帮助
in 搜索

Silver

  • C# 4.0 可选参数问题

    看见C#4.0可选参数功能挺兴奋的,一方面office编程不用写那么一大堆:

    excelApp.get_Range("A1", "B4").AutoFormat(myFormat, Type.Missing, 
        Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing);
     
    而可以简化为:
    excelApp.Range["A1", "B4"].AutoFormat( Format: myFormat );

    另外,原来一大堆函数重载也可以精简了,一个函数就能多用:

    static void Print(int Copies=1, string ColorMode="Color", string DocumentName="") {...}
     
    Print(1);
    Print(1, "Color");
    Print(1, "Color", "My doc");
    Print(Copies: 1);
    Print(ColorMode: "Color");
    Print(DocumentName: "myDoc.txt");
    Print(Copies: 1, ColorMode: "Color");

    问题

    但是,第一次想用“可选参数”精简下我原来的代码,就遇到问题了:

    public PolyLine(PointF[] points, Color color = Color.White)
    {
        figColor = color;
     
        mPoints = points;
        AddCenterPoint();
    }

    报错说:

    Error    2    Default parameter value for 'color' must be a compile-time constant 

    没想结构体里面的静态属性都不行…又改成:

    public PolyLine(PointF[] points, Color color = Color.Empty)

    结果一样的错误,看了下定义:

    // Summary:
    //     Represents a color that is null.
    public static readonly Color Empty;

    是个静态只读字段,还是无法编译时确定value?

    貌似限制还挺大..

    发表于 2010年8月28日 9:21 作者 silver | 0 评论
    归档在:
  • 学术搜索中的“作者识别”

    问题

    遇到一问题:“重名的问题中外都有,例如:搜索 Michael Smithhttp://academic.research.microsoft.com/Search.aspx?query=michael%20smith 你可以看到有很多 Michael Smith,  同时还有Mike 的拼写, 不同的 middle name 等问题。 每篇论文提供的作者,单位信息也不一样怎么办?”

    看到这个问题,我觉得根源可以描述为:从网上扒下m篇文章,其中可以抽取n个作者名字出来,现在需要确定哪些作者名字是指的同样一个人(合并问题)?

    难点一:作者单位关系演变历程

    例如: Michael David SmithAffiliation history 可能是:

    · Stanford University
    · Harvard University
    · University of Alberta
    · University of Calgary
    · Carnegie Mellon University

    同一个作者,可能在很多单位发表过论文,如何识别出每个作者的单位关系演变历程?

    难点二:名字多种写法

    例如:Michael David Smith 也可能是 Michael D. Smith 或者其他拼写

    对于非英语国家的作者,名字可能在文章中出现多种可能,例如:中文刊物发表的文章叫“张三”,英文的叫“ZhangSan”,或者直接是其他英文名。

    难点三:文献信息不全

    如果文献包含了作者简介,还比较好,但是很多只有单位信息。例如:Michael D. Smith 的《A High-Performance Microarchitecture with Hardware-Programmable Functional Units》只有Harvard University, Cambridge

    信息来源

    在思考方法之前,应该先考虑解决这个问题的信息来源:

    1. 作者单位信息。一般来说,每个能找到作者的文章都有单位信息,但是有上述“演变问题”。
    2. 作者简介。这个信息量大(性别、出生年月、出生地、单位、研究方向、邮箱、电话等),不也可能存在差异,如描述性差异、语言性差异(中文、英文)
    3. 作者个人主页。如果能找到这个,应该能找到挺多信息,如:发表文献列表等。
    4. 合作者,例如:通过发掘这些文献,能找出和 Michael D. Smith 一起写过文章的作者:
    5. Glenn H Holloway
      Mark A Horowitz
      Kartik Hosanagar
      Yu Jeffrey Hu
      Yu Hu
      Fang Huang
      Lily Huang  Lily Huang
      David S Johnson
      Mike Johnson
      Mahmut Taylan Kandemir
      David Ron Karger
      Murali Krishnan

    6. 作者涉及领域,通过查看作者发表文章,可以推断出作者涉及过的研究领域,例如:Michael D. Smith的研究领域
    7. Microprogram Design Aids,  Microcode Applications,  Memory Structures,  Design Styles,  Cache Memories, Multiple Data Stream Architectures (Multiprocessors) …

       

    8. 关键词。可以总结出作者列出过的所有关键词:

    code instrumentation
    embedded agile methodologies
    embedded programming
    embedded systems, agile methods, test driven development, tool support
    embedded unit
    empirical
    feature selection
    game tree search
    general purpose microarchitectures
    global instruction scheduling…

        7.   投过的会议、刊物…

    初步思路

        初步考虑起来,这个“作者合并”算法应该非常复杂——得先建立作者属性集合,找到最可靠的信息(计算两个作者某些属性的相似度、关联度)进行合并,合并之后更新属性集合,然后继续迭代….

        这个问题貌似会涉及到很多难点,需要更多时间考虑…

    另辟蹊径

    信息残缺、非结构化,难度很大,我想是否可以通过其他一些手段解决?——

    可否无缝整合“学术搜索”、“Word 文献管理”、“Windows Live ID”,“Msn 空间” 等功能?

    1. Word中一直缺少强大的“文献管理功能”,现在大家基本都用第三方工具。可否在Word中写文章的时候可以使用类似“Windows Live ID”登录,系统提供通过数据挖掘发现的个人文章,用户可以自行修改(删除不属于自己的)——通过强大方便的文献管理功能吸引大量用户使用,让作者自己完善文献信息? ?      image
    2. 在Word中可以直接点击登录到“学术搜索”或者“Msn 空间” 上的“个人学术主页”,包含个人的照片、单位关系历史、发表文献等等一系列信息,方便修改。最好能够整合社交网络的各种功能,例如:人人网、博客、facebook, twiiter等,但是要突出学术交流的特色,提出一些创新功能吸引研究人员。可以学习Web of Science网站 Research ID 提供的那些功能。

    image

    期待结果:

    1. 如果通过完善自己在“微软学术搜索”上面的主页,能够增加自己文章的引用,能够得到其他作者的提问、评论、反馈,发展成一个比较大的社区,估计信息来源更加准确,搜索结果也会更加准确。
    2. 在一个人气社区上面可以建立前面帖子提到的“学术立方”。
    发表于 2010年8月26日 0:28 作者 silver | 0 评论
    归档在:
  • 从一件小事看软件的销售

    从一件小事看软件“细节决定成败” 

    一、近期准备给一些客户的demo,我用到了office 2010的试用版,确实挺多方便的功能,展示效果也挺好

    二、经理及其他同事为了能打开我做的demo,也开始用office 2010,并且开始计划采购一批

    三、大家在使用过程中发现,ppt中的视频经常出现“编码器不可用”的提示,无法播放——需要关闭所有打开的PPT文件,重新启动就可以了,这种现象在给客户的demo和讲课过程中是非常严重的问题

     四、就因为这么一个小问题,大家普遍觉得office2010“不稳定”,放弃了采购计划

     结论:由此可以看出,个人软件产品细节挺重要的...

     

    发表于 2010年8月23日 13:40 作者 silver | 0 评论
    归档在:
  • 写个程序做推理

    问题

    教授选出两个从2到9的数,把它们的和告诉学生甲,把它们的积告诉学生乙,让他们轮流猜这两个数
    甲说:“我猜不出”
    乙说:“我猜不出”
    甲说:“我猜到了”
    乙说:“我也猜到了”


    问这两个数是多少?

    分析

    初初看这个题目,感觉是他们在说废话~!@#¥%

    不过,看过《越狱》的同学应该对这些spy敏锐的眼光,善于抓住线索而赞叹。

    运行结果

    何不写个程序来推理呢?运行一下得到:

    第一个数是3  第二个数是4

    第一个数是3  第二个数是6

    解法

    for (int i = 2; i < 10; i++)
        for (int j = i; j < 10; j++)
        {
            var sums = DecomSum(i + j);
            if (sums.Count < 2) continue;
           //如果不满足第一个条件——甲说不知道 => 他得到的数可以分解为多个“数对”相加
     
            var products = DecomProduct(i * j);
            if (products.Count == 1) continue;
           //如果不满足第二个条件——乙说不知道 => 他得到的数可以分解为多个“数对”相乘
     
            if (!Condition3(sums)) continue;//如果不满足第三个条件——甲说:“我猜到了”
     
            if (!Condition4(products)) continue;//如果不满足第四个条件——乙说:“我也猜到了”
     
            Console.WriteLine("第一个数是{0}  第二个数是{1}\n", i, j);
            //Analysis(i, j);
            //Console.WriteLine("\n");
        }

    *注:List<Point> DecomSum(int sum) 函数是分解和

           List<Point> DecomProduct(int product) 函数是分解积

    再来看其中的函数:

    bool Condition3(List<Point> sums)
    {
        var unique = 0;
        foreach (var s in sums)
            if (DecomProduct(s.X * s.Y).Count == 1) unique++;
                
        if (unique == sums.Count - 1) return true;
     
        return false;
    }//甲马上说知道了 => 他得到的数“和分解”出來的多个“数对”的积当中,
    只有一个“积分解”是不唯一的,其他都是唯一的
    bool Condition4(List<Point> products)
    {
        var condition3_satisfied_cnt = 0;
        foreach (var s in products)
        {
            var sums = DecomSum(s.X + s.Y);
     
            if (Condition3(sums)) condition3_satisfied_cnt++;
        }
     
        if (condition3_satisfied_cnt==1) return true;
     
        return false;
    }//乙说:“我也猜到了” => 他得到的数“积分解”出来的多个“数对”的和当中,
    只有一个的“和分解”满足Condition3

    分析

    取消其中”分析“代码的注释,得到:

    第一个数是3  第二个数是4

    分析:
    条件一:7可以分解为:3+4,2+5,
    条件二:12可以分解为:2*6,3*4,
    条件三:
    12(=3*4):
    12可以分解为:2*6,3*4,
    10(=2*5):
    10可以分解为:2*5,

    条件四:
    8(=2+6):
    8可以分解为:4+4,3+5,2+6,
    7(=3+4):
    7可以分解为:3+4,2+5,

    第一个数是3  第二个数是6

    分析:
    条件一:9可以分解为:4+5,3+6,2+7,
    条件二:18可以分解为:2*9,3*6,
    条件三:
    20(=4*5):
    20可以分解为:4*5,
    18(=3*6):
    18可以分解为:2*9,3*6,
    14(=2*7):
    14可以分解为:2*7,

    条件四:
    11(=2+9):
    11可以分解为:5+6,4+7,3+8,2+9,
    9(=3+6):
    9可以分解为:4+5,3+6,2+7,

    小结

    个人觉得这道题目挺有启发的——在生活工作中要保持“把握已知信息的态度”,还是我曾经提到的“能力是一种态度“

    看来要多向Micheal Scofiel学习啊…

    这个题目涉及到:”组合问题“、”推理问题“、”换位思考“,有点容易晕

    刚刚搞定,有空再想想有没问题或者有没更好办法…欢迎大家批评指教

    发表于 2010年8月22日 13:15 作者 silver | 0 评论
    归档在:
  • “学术立方”之遐想

    最近试用了Microsoft Academic Search,发现它用“人立方”技术展现作者之间的关系,挺酷的:

    image

    http://academic.research.microsoft.com/

    结合最近的一些搜索需求,于是我便有了如下遐想:

    能否充分利用“人立方”的技术,构造一系列“学术立方”出来?

    1  “学科立方”

    自复杂系统理论兴起以来,人们逐渐意识到学科分割带来的一些问题,学科之间的交叉与融合逐渐成为新的趋势。

    02年诺贝尔经济学奖,卡尼曼的“前景理论”就挺好玩,看起来是很常见但是一直没人提出的一些现象。或许只有站在经济学和心理学的交叉点上,才能系统全面看待这些问题。

    那么,能否构造一个“学科立方”呢?

    1. 展现学科之间的交叉融合关系?
    2. 展现学科之间的交叉程度及趋势(卡尼曼之后经济学和心理学交叉的文献情况?)
    3. 发现潜在学科群?

    比如可以先以:“城市规划”为中心展现,然后如果用户点中“环境学”,再以它为新的中心展现(类似人立法效果):

    当然,还可以通过作者来分析,比如一个学科的人发表文章到另外一个学科的杂志。

    2 “方法立方”

    有时候科研人员会有这样的需求,比如:我搞了个将“遗传算法”和A*算法结合起来的并行求解方法,因为时间或者项目资源有限,只和EXCEL做了对比。

    那么,“方法立方”能否展现:

    1. 一个方法(如:遗传算法、小波、马尔可夫链)等的起源、发展、所有改进分支?
    2. 每个分支上的改进链条?例如:谁在谁的方法上提出了什么,改进了多少?后来谁又在他的基础上又改进了多少?
    3. 我要解决某个问题,当今最好的最快的方法是?

    3 “学术机构立方”

    我想应该可以通过共同发表文章、新闻等(如IBM和东北大学成立“生态城市联合研究院”等)展现学术机构之间的合作情况:

    1. 以被搜索的学术机构为中心(如:清华大学),展现它主要和那些学术机构有过合作
    2. 连线可以是新闻,或者是共同发表文章的数量

    4 “学术带头人立方”

    例如,输入“人群动态学”,能否展现:

    1. 这个学科代表性人物有哪些?
    2. 他们之间是否有关系?
    3. 他们是否兼任相近学科的带头人?

    5 “文献立方”

    我觉得中国期刊全文数据库非常好用的一个功能是:打开一篇文献的页面它能够列出:

    1. 参考文献
    2. 共引文献
    3. 相似文献

    这样能省去很多搜索的时间,通过多次利用这个功能,能够查到很多相关的文章。

    那么,可否通过一些更加可视化的方式展现这个过程?

    小记

    暂时就想到这些,我想如果一系列“学术立方”全部推出的时候,应该是一个非常酷的引擎:)

    当然具体实现还会涉及到很多技术难题…

    我会继续思考类似问题,希望大家多提提意见。

    发表于 2010年8月21日 22:50 作者 silver | 0 评论
    归档在:
  • 最短摘要生成与多模式匹配(三)

     

    Boyer-Moore算法——“坏后缀规则”?

    如前贴所述,BM算法的“好后缀规则”实际上有两种情况:“中缀”和“前缀”。

    “前缀”没啥问题,但是我发现“中缀”往往会出现“跳转以后朝向注定的失败匹配”,如下图所示:

    image

    如上图,在第6位X处发生了miss match,BM算法会跳转到3位,也就是将pat挪到如上表第三行的样子,再从11位开始比较,假设11~7都匹配成功了,但是X!=B,第6位是注定失败的。

    那么可否将“中缀跳表”独立出来,匹配过程中判断”好后缀”加上它的前导字符构成的子串是否在pat中存在?如果不存在就是个“坏后缀”。

    例如:上图中的“XXY”实际上是“坏后缀”,而不是“好后缀”,因为在pat中根本不存在这样的子串。

    如何考虑“坏后缀规则”?

    如果要实现这个规则,需要注意两个问题。

    如下图,和前面一样,假设在倒数第三位X处发生了miss match,可以跳转到两个位置(如箭头所示),但是有一个跳转是重复的,A=A。

    image

    因此可以看出,如果要独立“中缀跳表”,需要考虑两个问题:

    1. 某位置的中缀跳转可能不止一个;
    2. 跳转中可能存在重复。如果出现重复,需要取最右边的那个。

    很容易想到,满足这两个需求,用个哈希表恰好。

    一种实现方法

    将(二)贴中的计算中缀跳表的地方用一个函数AddInfixJump(i, j);代替,这个函数如下:

    /// <summary>
    /// 中缀跳表
    /// </summary>
    static Dictionary<char, int>[] iJumps;
    static void AddInfixJump(int i, int j)
    {
        if (j < m - 1)//最后一位采用坏字符移动,这里不需要
        {
            if (iJumps[ j ] == null) iJumps[ j ] = new Dictionary<char, int>();
            //添加中缀跳 能保证从小到大
            if (!iJumps[ j ].ContainsKey(pat[ i ])) iJumps[ j ].Add(pat[ i ], i);
        }
    }

    这样去计算中缀跳表,能够保证没有重复,并且右边的跳转肯定是先计算的,所以保留的也是最右边的。

    然后在搜索过程中,如果在pat倒数第1位以前发生了miss match(也就是不用“坏字符规则”)的时候,先设置跳转为原来的那个表(比原来少了中缀跳转而已),然后再看看是否有好的“中缀”:

    shift = mJumps[ j ];//设置为前缀和坏后缀混合跳转
     
    if (iJumps[ j ] != null &&//存在中缀 
        iJumps[ j ].ContainsKey(text[ i ]))//且跳转以后匹配text[ i ]
        shift = m - 1 - iJumps[ j ][text[ i ]];//设置中缀跳 (m-1-j + j-k)

    这样能够避免上面出现“注定的失败”了。

    实验结果

    通过实验我发现,这样改进以后,在随机字符串和英文文章的匹配中,比较次数能够降低10%~30%,但是总体性能提高却不明显。我估计性能肯定是损失在C#的泛型字典Dictionary<>上面了,“坏后缀”的思路应该很好,但是可能需要别的实现方法:比如字符集小的话可以用数组,或者别的语言实现。有人想到好方法麻烦告诉我:)

    后记

    这和我一直以来的编程经验是相反的:“一般来说,不应该先考虑太细节的优化,算法思想的优化是最重要的”;但是在字符匹配的程序中不完全是这样,字符比较的消耗很小,稍微复杂的逻辑和数据结构都会损失性能,所以凡事都有例外。

    有些细节优化却能够提升性能,例如,原版的BM搜索过程是这样的:

    var shift = 0;
    var k = (int)text[ i ];
    if (k < cJumps.Count) shift = cJumps[ k ];
    else shift = m;
    i += Math.Max(shift, mJumps[ j ]);

    贴(二)中,我将其改为:

    if (m - j == 1)//末位不匹配
     {
         var k = (int)text[ i ];
         if (k < cJumps.Count) i += cJumps[ k ];
         else i += m;
     }
     else i += mJumps[ j ];

    实际上就是当匹配超过1个字符时候,不用去计算“坏字符跳”。这样小改动一点,在随机字符和英文测试中,性能都提高了10%左右。

    当然要说明的是,MB算法总体性能相比最原始的BruteForce算法还是有大幅提高的,pat越长越明显。

    发表于 2010年1月4日 17:21 作者 silver | 2 评论
    归档在:
  • 最短摘要生成与多模式匹配(二)

    Boyer-Moore算法

    通过学习和借鉴之后,感觉这个算法思想挺精妙,同时也比较复杂。我根据自己的理解写了一个算法,望大家指教。

    前面提到过,这个算法是从右往左匹配,根据“坏字符规则”和“好后缀规则”来跳转,避免无谓的比较。

    坏字符规则

    这个规则比较简单,首先看pat的末位是不是和要查找的文本匹配,如果不匹配,pat里面又没有这个字符,则可以完全跳过这个字符,如下图:

    如果pat里面有这个字符,则跳到pat里面最右的这个字符(当然排除自己),如下图中pat的第二个T:

    可见这个规则是在末位就不匹配的时候用的,如果已经匹配了几位了,这几位就是“好后缀”了。

    好后缀规则

    个人觉得,如果要清晰理解这个规则,应该再明确两个概念,一个是“中缀”、一个是“前缀”。

    中缀

    如下图,在pat=wowwow中,第3位的w就是中缀,它有两个特点:一是和后缀匹配;二是它前面的字符不能和“后缀”前面的字符一样,否则跳转过去没意义。

    下图中,如果匹配到4的时候发生miss match,就跳到2,换个w来比。

    image

    另外要注意的是,中缀可能很多,在BM算法中就考虑移动到最右边的这个中缀,如下图,x y 的中缀出现了好多次,如果在8出现mis match,就跳到5,而不考虑2了。

    image

    注意,最前面的x y不算中缀,而算“前缀”。

    前缀

    和后缀匹配的前缀这里简称“前缀”。如果pat=wowwow,其前缀是w 和wow,长度分别为1 和 3,这说明前缀长度可以是跳跃的,长度为2的前缀不存在。

    如AAAcAAAAAAcAAA的前缀长度就有7、3、2、1。

    最后,在前缀跳转和中缀跳转中,应该取小的,避免遗漏(中缀跳<前缀跳)。

    如果中缀、前缀都不存在,就可以直接跳过整个已经匹配的部分。

    算法实现

    坏字符表构造

    坏字符表理论上可以用哈希表来做,不过我实验了下,字符比较本来运算量就小,用哈希有点费,还是改用数组了,cJump 是坏字符跳表,由于不知道字符集的大小,这里用动态扩展策略。

    其中,m = pat.Length;

    static List<int> cJump = new List<int>();
    static void BuildCharJump()
    {
        cJump.Clear();
        for (int i = 0; i < m - 1; i++)
        {
            var k = (int)pat[ i ];
            while (k >= cJump.Count) cJump.Add(m);
            cJump[k] = m - 1 - i;
        }
    }

    好后缀表构造

    好后缀表又称为match jump,可以通过上次帖子介绍的“逆向的MP fail links”来构造,因为是倒着匹配的。

    为了便于理解,这里给出一个“逆向MP fail links”的构造过程:

     image

    static int[] mJumps;
    static void BuildMatchJump()
    {
        if (m < 2) return;//少于一个字符,不存在后缀
        //一、初始化
        var n = m - 1;
        mJumps = new int[ n ];
        for (int k = 0; k < n; k++)
            mJumps[ k ] = (n - k) + m;//初始化为最大移动数
     
        //二、构造逆向 MP fail links,并计算中缀跳表
        var fails = new int[ m ];//逆向 MP fail links
        int i = n;
        int j = fails[ n ] = m;
        while (true)
        {
            while (j < m && pat[ i ] != pat[ j ])
            {
                if (j < n)// 已匹配长度 +  移动数 
                    mJumps[ j ] = Math.Min(mJumps[ j ], (n - j) + (j - i));//计算中缀跳,记录最右的中缀
                j = fails[ j ];
            }
            if (i == 0) break;
            fails[--i] = --j;
        }
        //三、计算前缀跳表
        var s = 0;
        while (j < m)//顺着最后一个箭头往前可找到所有前缀
        {
            if (pat[ i ] == pat[ j ])
            {
                var L = m - j;//前缀长度
                for (int k = s; k < m - L; k++)//已匹配长度 + 移动数
                    mJumps[ k ] = Math.Min(mJumps[ k ], (n - k) + (m - L));
                s = m - L;
            }
            j = fails[ j ];
        }
    }

    查找算法

    最后的查找算法再用两个表就好了:

    static public int IndexOf(string pattern, string text, int index)
    {
        if (pat != pattern)
        {
            pat = pattern;
            BuildCharJump();
            BuildMatchJump();
        }
     
        int i = index + m - 1, j = m - 1;
        while (i < text.Length)
        {
            if (j < 0) return i + 1;//找到
     
            if (pat[ j ] == text[ i ]) { i--; j--; }
            else
            {
                if (m - j == 1)//末位不匹配
                {
                    var k = (int)text[ i ];
                    if (k < cJumps.Count) i += cJumps[ k ];
                    else i += m;
                }
                else i += mJumps[ j ];
     
                j = m - 1;
            }
        }
        return -1;
    }

    后记

    个人觉得MB算法还可以有改进的地方:可以提出一个“坏后缀规则”,因为MB算法构造跳表的时候没有考虑text的内容,所以没办法知道“坏后缀”,明天咱们继续分析这个改进。

  • 最短摘要生成与多模式匹配(一)

    问题

    《编程之美》有一道题目“最短摘要生成”:若输入“微软 亚洲研院 使命”三个关键字,查找最短的摘要:

    微软研究院的使命是使未来的 计算机 能够看、听、学,能用 自然语言 与 人类 进行交流。 ... 微软亚洲工程院将以微软亚洲研究院的科研成果为依托,创造关键技术、为微软亚洲研究院核心技术的 孵化 提供有力保证。 ... 微软研院的科研环境 编辑本段 回目录…

    书中给出的解法是基于“分词”后的数组的。

    那么,如果是客户端蜘蛛临时抓的网页,或者是在一堆文件中查找,这时候没有分词、没有建立倒排表,如何直接生成呢?再加上有中英文混杂的情况(查找某种表达的翻译)?

     

    冰山一角

    这时我想到了基于“多模式匹配”改进原书中算法,因此决定研究下经典的AC_BM算法。查阅了很多文献后,意外的发现:小小的“字符串匹配”居然有这么多学问~:),引用一段文献——

    “String-matching is a very important subject in the wider domain of text processing. String-matching algorithms are basic components used in implementations of practical software existing under most operating systems. Moreover, they emphasize programming methods that serve as paradigms in other fields of computer science (system or software design). Finally, they also play an important role in theoretical computer science by providing challenging problems…”

    这段文字出自 http://www-igm.univ-mlv.fr/~lecroq/string/node1.html ,这个网站上竟有35个单模式匹配算法的详细分析、例子、代码、java动画演示…

    多模式匹配在:入侵检测、数据挖掘、DNA配对、文本处理(查找替换)等方面都有广泛的应用。

     

    算法发展简要回顾

    先简要回顾下经典的AC_BM算法是怎么来的:

    1. 1977年,这三个人改进Morris-Pratt 算法提出了Knuth-Morris-Pratt 算法——将“有限状态自动机”+“递归/递推”思想用得很精妙。

    2. 同年,Stanford和Palo Alto研究院的两个人提出了经典的Boyer-Moore算法,主要思想为:

    a) 提出了从右往左匹配的思路;

    b) 通过“坏字符移动”+“好后缀移动”避免无谓的比较;

    c) “好后缀移动”借鉴了Knuth-Morris-Pratt 算法。

        题外话:这个BM算法就是大家日常“查找替换”时候用的:)

    3. 1980年,Horspool简化了Boyer-Moore算法:用实验证明了只采用“坏字符移动”在一般情况下性能足够了。这是因为“好后缀”出现的概率并不大,除非是DNA一类的“小字符集”中的匹配,类似AGTCAAGGTTTC…

    4. 1975年,Bell Labs的两个人提出了Aho_Corasick算法,主要思想是构造了“多模式”(多关键字)的“有限状态自动机”(是一棵多关键字树,Knuth-Morris-Pratt 算法是单模式的“有限状态自动机”)

    5. 2001年,Silicon Defense的三个人将Aho_Corasick算法和Boyer-Moore算法思想结合起来(因为AC算法没有如BM算法一样很好的跳转,还有优化的空间),就成了AC_BM算法。

    由此可以看出,要想搞懂AC_BM算法,得从头一个个来。

     

    引用一个题外话:

    AC 算法被用于fgrep1.0(在UNIX中通过-f使用)

    BM_AC算法在gre中应用,并被fgrep2.0收用。

    Knuth-Morris-Pratt 算法

    在单模式匹配中,可把要匹配的关键字看成一个“有限状态自动机”:

    image

    例如上图,如要匹配:GCAGAGAG,遇到G就转状态1,再遇到G还是在状态1,遇到C就转状态2… 到8就成功

    这个思路挺不错,问题是跳转的条件比较麻烦,于是Morris-Pratt就将其简化为两个:成功s和失败f,如下图所示:

    clip_image004

    要匹配ABABABCB,成功了就向前,失败了就退回来,再失败了递归退后…如下表:

    那么构造的时候可以递推构造,思路挺好:)

    image

    那么这个算法最关键就是要构造失败的这些箭头:MP fail links,可以用一个int[]保存。

    设要匹配的模式是string pattern; m = pattern.Length; 那么MP fail links可以很简洁地构造:

     int[] Morris_Pratt()
        {
            var fails = new int[m];
            int i = 0;
            int j = fails[0] = -1;
            while (i < m - 1)
            {
                while (j >= 0 && pattern[ i ] != pattern[j]) j = fails[j];
                fails[++i] = ++j;
            }
            return fails;
         }
    

    不知道大家有没发现MP fail links 有个问题,假设遇到:

    image

    设要匹配的文本是 string text; 如果text中出现了“…ABAY…”,那么按照MP fail links跳转换过来的还是B(如上表最后一行),肯定匹配不上的,所以Knuth-Morris-Pratt算法就改进了这个问题(将上面while里面改了下),构造KMP fail links:

     ……
    while (i < m - 1)
      {
            while (j >= 0 && pattern[ i ] != pattern[j]) j = fails[j];
            i++; j++;
            if (pattern[ i ] == pattern[j]) fails[ i ] = fails[j];
            else fails[ i ] = j;
    }
    …….

    就递推地实现了所有首位字符相同的跳转都合并的效果,如下图所示:

    clip_image009

    那么在查找的时候,就可以利用 KMP fail links 实现跳转,而且思路和构造的时候类似:

     ……
     int i = index, j = 0;
     while (i < text.Length)
     {
           while (j >= 0 && pattern[j] != text[ i ]) j = fails[j];
           i++; j++;
           if (j == pattern.Length) return i - pattern.Length;//找到
     }
     return –1
     

    后记

    今天先写到这里,明天咱们继续研究BM算法,感觉挺巧妙的是利用简短的MP fail links可以找到所有的“中缀”和“后缀”。

    需要说明的是,直接利用KMP 搜索效率提高并不明显,因此“查找替换”一般用BM算法。

    发表于 2009年12月31日 14:21 作者 silver | 1 评论
    归档在:
  • IBM 中国研究院 Offer 之感言——能力是一种态度

        当我对着远程的大屏,给北京的IBM中国研究院几位面试官汇报完30分钟技术报告之后,心里忐忑不安,这已经是终面了…一关关拼得不容易,但却很精彩!

        在之后的几天,很高兴接到了来自IBM两位高级经理的电话,分别给我介绍了他们部门情况和项目情况,表示我的报告“印象深刻”,“能力很突出”…真的是非常感谢他们能给我这个机会!

    诀窍

        我不是聪明过人的天才,但是我相信自己的研究能力,这来源于一个诀窍——我悟出一条“定律”,那就是:能力是一种态度!

        简单解释如下:这世界上不缺乏聪明人,但是缺乏懂得运用自己聪明才智的人。

        今天的帖子,我希望通过7个真实的故事,去诠释这条定律——能力是一种态度!

    1. 我通过qq在课题组做了一次试验,将同一个问题群发给了6个成员,有3个人回复我:“这个我没遇到过,不会做啊…”;有2个去Google了下,大概告诉了解了应该怎么弄;有一个人,做程序试验了不同方法的优劣,告诉我最好的方法是什么。多年以后,对很多问题都报以“没遇到过,不会啊…”的人,和能力在积累的人,虽然一样聪明,但是差距就很大了。

    2. 行人仿真系统研发的初期,我突然发现A*算法和Social Force Model特别有意思,就钻了进去,花了一些时间把它们研究透彻…当我兴致勃勃给老板介绍完各种模型算法之后,老板说:“你去给领导介绍这些模型算法是没用的,他们要的是效果,模型是无止境的…”。当时我也很沮丧,但是后来事实证明,我的“不务正业”是对的!因为智能化的动态寻路能力成为了我们系统的核心创新点,也是每次项目介绍中最得意的内容。

    3. 给北京做的“城市轨道交通运营辅助决策系统”是要在08奥运前上线的,时间很紧,临近系统调试的时候,北京测试人员突然打电话说发现某些车站之间候选路径似乎少了一条…当时大家都认为可能就是边界条件问题,稍微改改就好了。我研究了下这个K短路算法(其他人负责开发的)发现竟是理论上的缺陷…

    一时半会儿又没办法给领导解释清楚,我就决定重写这个部分,用数据来说明。由于时间太紧,在北京回上海的火车上看这些很多文献,凭借着良好的A*算法基础,很快设计出新的算法。通过测试发现,老算法共丢了500多条路径!(总共十几万条左右),这时候大家总算舒了一口气了…

    但我并没有罢休,因为匆忙,算法速度不快。继续花了几天,将北京轨道网络中2万多OD之间清分计算时间优化到10多分钟,最后优化到1分钟(在我笔记本上)。上线调试当天,领导赞叹道:“这算法可真是又快又准啊!”

    4. 还是上面这个系统的故事:当时北京路网基础数据是一个硕士负责录入的,他毕业以后,上海路网数据没人弄了,老板叫我去做。虽然只是半天时间的体力活,但是心里很不是滋味…

    虽然有人劝说:“花个半天搞定算了哦!”但是我决心不用笨办法——我花了一个星期,凭借曾经开发的“二维矢量图形库”,设计出一个智能化的基础数据管理子系统,只需要在图上简单点击,然后拷入excel中的车站名称和代码,系统自动识别,然后再自动生成区间、换乘关系等等6张数据库表需要的全部数据。后来课题组利用这个工具构建了很多路网,因为非常简单,这个子系统也成为了后来863中网络客流仿真系统的基础。

    5. 上物流系统课老师提到一个著名的NP问题——Vehicle Routing Problem,要求大家回去写写系统设计书。我当时就决定要开发这个系统,后来的几个星期,我发现遗传算法和自然界的规律真的是如此的吻合,达到了如痴如醉的地步,被女朋友嘲笑为:“整天关在屋里下崽…”后来结果是,我设计的遗传算法,不但能够求解最少需要多车,还能找到总里程很短的方案。

    6. 在斯坦福访学主要是参与一个疏散仿真系统的研究。但是,由于有遗传算法的背景,另外一个教授介绍我参加他们的一个课题——办公大楼改造优化方案的辅助决策系统。

    刚开始我认为这是一个确定性问题,因此采用A*算法得到了比较好的效果,已经可以满足项目需求了。我想为了作对比,又设计了遗传算法,居然发现在少数情况下能“变异”到更好解。大量实验后,我发现了两种算法虽然原理差别很大,数据结构上却存在内在联系,能够组合成一种具备通用性的框架,解决大量离散优化问题。

    完全出于对科学问题本身的痴迷,我并没有罢手——自行设计了一种数据结构替换哈希表,将两个算法性能同时提升10倍之多(在遗传算法中提出了“花名册”的概念),后来又发明一种“交叉算法”,再次将遗传算法提升十几倍。当时测试案例的人说已经完全跟不上了(已经让他反复做了好多次了),因此最后我们paper里面的数据不是我最快的算法得到的。

    7. 利用上面的“离散优化问题搜索框架”,我发现还可以解决《编程之美》中的许多问题。在大家都忙于找工作面试的时候,我却整整花了一个月关在寝室里研究《编程之美》,有时候挑战一个题目整整花去1天时间,当时我身边的人都说我“不务正业”,我自己都有点怀疑了。可是事实证明,这份研究不但证明了兴趣,还证明了我的算法能力,对后来找工作很有帮助。

    结论

        通过上面的故事,我想已经可以证明我这个定律了——能力是一种态度。如果要问态度是什么,那么我想是一种单纯的,没有任何功利的科学态度,对问题本身的执着。

       

        这7个故事也可以用下面这张图串起来,一面的时候时间很短,我就打印了这个图,凭借它去打动面官。

    image

    后记

        在我编程之美的学习连载里面,大家看到了很多好的点子。但是,它们都是在平时走路、吃饭、散步的时候想出来的,我这个人从来不擅长考试,女朋友嘲笑我——如果你去微软面试估计危险,因为你没有时间散步…

        我曾经想过,对于一个博士,面试不一定能看出其水平,但是,如果给他30分钟PPT时间,介绍他的研究,水平就一目了然了。

        真的没想到的是,在IBM的后2轮面试,全部一一印证了我曾经的设想…用PPT去演讲是我最擅长的东西,因为太多次的磨练了:)

        微软的技术是我多年研究和喜爱的东西,微软也是我多年梦想的企业。但是,如今却迟疑了,IBM这份《智慧地球赢在中国》的白皮书确实令人震撼——它能够站在世界经济大环境的角度务实考虑中国的未来…

  • 方曲序列——一道有趣的搜索核心研发试题

    问题

    假设有4×4的方阵(里面可放字符串),给现定一个序列,检查这个序列是否在方阵的“连续对角线”上面:

    例如:从左上到右下的连续对角线序列可能是:

    1 2 5 9 6 3 4 7 10 13 14 11 8 12 15 16
    1 5 2 3 6 9 13 10 7 4 8 11 14 15 12 16
    image

    分析

    刚刚看到此题目,可能有点晕,“连续对角线”是啥?仔细一看,其实就是方阵里面的曲线序列(如下图),我就叫它“方曲序列”吧。

    image

    再一想,这样的序列应该有8条。考试的时候时间紧,只能大概写写思路了,回来用VS08调试着搞感觉好很多:( 不适合应试哈哈

    我们来分析下,这个题目的关键是,要把握住曲线运动其实是两个方向:

    1. 沿斜线运动
    2. 斜线平移

    那么,再看这些斜线的长度分别为 1 2 3 4 3 2 1,那么我们在枚举到这个序列之和的这些关键带点的时候:

    1. 需要平移斜线
    2. 需要翻转斜线上的运动方向

    解法

    有了思路,借助VS08 强大的调试功能,我写了8个方向,通用的,O(N)(N是序列长度) 复杂度的 “方曲序列” 函数:

    运行函数:PrintWinding(4);

    就可以得到 n=4 的时候8条“方曲序列”:

    1 2 5 9 6 3 4 7 10 13 14 11 8 12 15 16
    13 14 9 5 10 15 16 11 6 1 2 7 12 8 3 4
    4 3 8 12 7 2 1 6 11 16 15 10 5 9 14 13
    16 15 12 8 11 14 13 10 7 4 3 6 9 5 2 1
    1 5 2 3 6 9 13 10 7 4 8 11 14 15 12 16
    13 9 14 15 10 5 1 6 11 16 12 7 2 3 8 4
    4 8 3 2 7 12 16 11 6 1 5 10 15 14 9 13
    16 12 15 14 11 8 4 7 10 13 9 6 3 2 5 1

    代码如下:

            static void PrintWinding(int n)
            {
                var square = new string[n, n];
                for (int i = 0; i < square.Length; i++)
                    square[i / n, i % n] = (i + 1).ToString();
    
                var p = new int[] { 0, n - 1 };
                for (int sign = -1; sign <= 1; sign += 2)
                    for (int i = 0; i < p.Length; i++)
                        for (int j = 0; j < p.Length; j++)
                            PrintWinding(square, p[ i], p[j], sign);
            }
    其中:
            static void PrintWinding(string[,] square, int x0, int y0, int sign)
            {
                int n = square.GetLength(0);//边长
    
                  int xn = Math.Abs(x0 - n + 1);//目标点x
                int yn = Math.Abs(y0 - n + 1);//目标点y
    
                int dx = Math.Sign(xn - x0);//平移方向x增量
                 int dy = Math.Sign(yn - y0);//平移方向y增量
    
                 int stepX = -dx * sign;//斜线方向x增量
                 int stepY = dy * sign;//斜线方向y增量
    
                 int flag = sign == 1 ? 0 : 1;
    
                int x = x0, y = y0;
                int s = 1, k = 1, sum = 1;
                for (int i = 0; i < square.Length; i++)
                {
                    Console.Write(square[y, x] + " ");
    
                    if (i + 1 < sum)
                    {
                        x += stepX;
                        y += stepY;//沿斜线方向移动
                      }
                    else if (i + 1 == sum)
                    {
                        stepX *= -1;
                        stepY *= -1;//斜线方向掉头
    
                          if (k == n) flag = Math.Abs(1 - flag);
    
                       if ((k & 1) == flag) x += dx;//平移
                          else y += dy;//平移
    
                         if (s < n) k++;//前半三角
                         else k--;//后半三角
    
                         sum += k;
                       s++;
                    }
                }
                Console.WriteLine();
            }

    讨论

    有了8个方向,通用的“方曲序列” 函数,我们就不难写出测试某序列是否在”连续对角线“上面的函数了,可以考虑采用PLINQ写个并行算法,多个核的话每个核测试一个(些)方向的序列,找到就返回true。

    发表于 2009年11月4日 15:44 作者 silver | 5 评论
    归档在:
  • 编程之美——差之毫厘

    问题

    不理解计算机底层运作机制,有时候会写出很费的代码,这说明了两个问题:

    1. 底层透明性做得还不够好;
    2. 在这样的技术条件下,我们还需要去多了解底层。

    来看两个有趣的例子,问题需求是:求一个二维整数数组的和,非常简单吧?

    本帖通过4个解法(后两个是并行算法),说明微小的改动对程序运行效率的巨大影响。

    *注:需要注意的是,测试结果是在release下的,debug下代码未优化。

    解法一

            static int SumA(int[,] array, int n)
            {
                int sum = 0;
                for (int x = 0; x < n; x++)
                    for (int y = 0; y < n; y++)
                    {
                        sum += array[y, x];
                    }
                return sum;
            }
    运行测试函数:Test(SumA, array, n);
    得到结果:

    SumA: 1073741824, 20.4777841s

    其中:

           测试函数为:

            static void Test(SumDele Sum, int[,] array, int n)
            {
                int sum = 0;
                Stopwatch watch = new Stopwatch();
                watch.Start();
                for (int i = 0; i < n; i++) sum += Sum(array, n);
                watch.Stop();
                Console.WriteLine("{0}: {1}, {2}s", 
                Sum.Method.Name, sum, watch.Elapsed.TotalSeconds);
            }
            delegate int SumDele(int[,] array, int n); 
        初始条件为:
                int n = 1<<10;
                int[,] array = new int[n, n];
    
                for (int x = 0; x < n; x++)
                    for (int y = 0; y < n; y++)
                        array[x, y] = 1;

    解法二

           就改动一行:

            static int SumB(int[,] array, int n)
            {
                ……
                        sum += array[x, y];
                ……
            }
    再看运行结果: 

    SumB: 1073741824, 4.8847906s

    在我的机子上居然提高了近4倍多!!(每次运行稍有差异,一般在3~5倍)有些机器甚至更高。

    分析

    在现在的经济和技术条件下,计算机中存储设备的容量和速度是”鱼和熊掌“的关系,没办法两全,人们只好采用了多级结构来解决:快的、贵的、小的存储设备靠近CUP,反之远离:

    image

    在这种结构中,上层会通过缓存部分下层中数据副本来提高访问速度:CUP要去找一个数据,先从近的找,没有”命中“再往下继续。那么CUP访问到一个数据往往也会将其附近的数据一并缓存,可称为Locality特性,在上面例子中,由于:

    1. 二维数组在内存中是按行存放的;
    2. CUP会在其缓存附近的数据。

    因此,按行循环的时候利用到了大量缓存数据,效率高。这也说明,如果二维数组是按列存放的机器里面就刚好反过来。

    解法三

    随着多核处理器的逐渐普,为了充分利用多核的硬件资源,微软正在研发下一代并行系列框架(Parallel FX)。目前微软已经将PLINQ和TPL包含在Parallel Extension中,我们可用并行算法来求解下试试:

            static int SumC(int[,] array, int n)
            {
                int m = Environment.ProcessorCount;
                int[] result = new int[m];
    
                int step = n / m;
                int leftover = n % m;
    
                Parallel.For(0, m, (i) =>
                {
                    int from = i * step;
                    int to = from + step + (i < m - 1 ? 0 : leftover);
    
                    for (int x = from; x < to; x++)
                        for (int y = 0; y < n; y++)
                        {
                            result[ i ] += array[x, y];
                        }
                });
    
                return result.Sum();
            }

           这个算法根据机器处理器的数量将任务分块,最后加总结果。我机器的是双核的,因此分了两块并行算。

           运行程序得到结果为:

          SumC: 1073741824, 10.8568457s

           奇怪的是,为何没有提高??反而差了?

    分析

        这是由于False Sharing导致的。由于每个核的L1缓存互相独立,因此CPU必须有一种机制,能够确保一个核在它的L1 Cache中改动一个数据后,其他核内L1 Cache中包含这个数据的整个Line就会过期。这意味着其他核在读取地址相同,或者是接近的数据时会遇到L1 Cache Miss。CPU的这种同步机制就是MESI协议

         由于int[] result很小,会被多个CPU缓存,然而一个CPU改动了其值就会导致其它CPU缓存中的副本过期,下次就需要重新加载,因此损失了很多性能…

    解法四

          解决这个问题,只需要改动3行,将中间结果另外用一个变量保存:

            static int SumD(int[,] array, int n)
            {
             ……
                    int temp = 0;
                    for (int x = from; x < to; x++)
                        for (int y = 0; y < n; y++)
                        {
                            temp += array[x, y];
                        }
                    result[ i ] = temp;
             ……
            }

    运行程序得到结果:

    SumD: 1073741824, 2.5991568s

    这个例子说明了几个问题:

    1. 首先,我们需要理解计算机底层的运作机制。
    2. 但是,这并不代表应该“刀耕火种”。利用PLINQ,我们甚至都不需要显式地去关心多线程就可以实现并行求解,看上去是“语法糖”,但是它们会逐步改变我们编程的思维方式。

    最后将四个解法结果放一起:

    • SumA: 1073741824, 20.4777841s
    • SumB: 1073741824, 4.8847906s
    • SumC: 1073741824, 10.8568457s
    • SumD: 1073741824, 2.5991568s

    参考资料

    计算机体系结构与程序性能

    Running Queries On Multi-Core Processors

     

    闲话

    最近做了某知名网络公司的笔试题,感觉如果公司实际开发也是这么底层的话,那么个人认为其效率不是很高,原因在于:

    1. 很多SDE可能在解决同样的问题;
    2. 很多SDE可能在犯着同样的错误;
    3. 很多TE可能在查找着同样的BUG;

    我遇到的几个例子:其招聘网站录入信息时候出现几个BUG,其软件登录也会弹出个找不到文件错误…

    发表于 2009年11月3日 20:15 作者 silver | 1 评论
    归档在:
  • 一些常用集合算法——之幂集合生成

    问题

    在开发过程中常常需要处理集合,因此我写了一些常用算法,贴出大家提提意见。

    本帖介绍幂集合生成算法。

    解法一

    思路为:

    1. 构造初始序列00…0
    2. 如果全部为11…1,则停止
    3. 寻找位置最低的0,将其变1
    4. 将刚刚那个0前面的都变0

    这里可以用到前面帖子构造的“动态有序集合”——BinHeap,共设置两个:一个用于保存0的位置,一个用于保存1的位置:

        public static List<T[]> Subset<T>(IList<T> list)
            {
                var zeros = new BinHeap<int>(false);//0的位置集合(因为不需要建立索引,传入false)
                var ones = new BinHeap<int>(false);//1的位置集合
                List<T[]> subsets = new List<T[]>();
                for (int i = 0; i < list.Count; i++) zeros.Push(i);//初始状态全0
    
                while (ones.Count < list.Count)
                {
                    var i = zeros.Pop();
                    while (!ones.IsEmpty && i > ones.Peek()) zeros.Push(ones.Pop());
                    ones.Push(i);
    
                    i = 0;
                    var arr = new T[ones.Count];
                    foreach (var p in ones) arr[i++] = list[p];
                    subsets.Add(arr);
                }
                return subsets;
            }
    运行结果

    输入abcde,得到2^n – 1 个子集:

    1:a  2:b  3:ab  4:c  5:ac  6:bc  7:acb  8:d  9:ad  10:bd  11:adb  12:cd  13:adc 14:bdc  15:abcd  16:e 

    17:ae  18:be  19:aeb  20:ce  21:aec  22:bec  23:abce  24:de  25:aed  26:bed  27:abde  28:ced  29:acde  30:bcde  31:abdec

    讨论

    这里也没有用一般算法的“扫描”的办法,而是利用的两个“动态有序集合”解决了问题,效率提高很多。

    解法二

    当然,这个问题也肯定可以用前面连载里面广泛用到的“分级排列(组合)法”。

        public static List<T[]> Subset2<T>(IList<T> list)
            {
                List<T[]> subsets = new List<T[]>();
                var heap = new List<T[]>[list.Count + 1];//0~n级的组合
                for (int i = 0; i < heap.Length; i++) heap[ i ] = new List<T[]>();
                heap[0].Add(new T[0]);
                for (int i = 0; i < list.Count; i++)
                    for (int j = i + 1; j > 0; j--)
                        foreach (var p in heap[j - 1])
                        {
                            var subset = new T[p.Length + 1];
                            p.CopyTo(subset, 0); subset[p.Length] = list[ i ];
                            heap[j].Add(subset);//更新j级
                             subsets.Add(subset);
                        }
                return subsets;
            }
    运行结果

    1:a  2:ab  3:b  4:abc  5:ac  6:bc  7:c  8:abcd  9:abd  10:acd  11:bcd  12:ad  13:bd  14:cd  15:d 

    16:abcde  17:abce  18:abde  19:acde  20:bcde  21:abe  22:ace 23:bce  24:ade

    25:bde  26:cde  27:ae  28:be  29:ce  30:de  31:e

    可以看出,顺序稍微有些差异,但结果相同,解法二效率稍高。
    发表于 2009年11月1日 21:50 作者 silver | 1 评论
    归档在:
  • 一些常用集合算法——之组合生成

    问题

    在开发过程中常常需要处理集合,因此我写了一些常用算法,贴出大家提提意见。

    本帖介绍组合生成算法。

    分析

    开一个数组,数组元素的值为1表示其下标代表的数被选中,为0则没选中。

    首先初始化,将数组前m个元素置1,表示第一个组合选中前m个元素。

    然后找到从左到右的第一个“10”组合,将其变为“01”组合,

    同时将其左边的所有“1”全部移动到数组的最左端,例如:

    1 1 1 0 0 //1,2,3

    1 1 0 1 0 //1,2,4

    1 0 1 1 0 //1,3,4

    0 1 1 1 0 //2,3,4

    1 1 0 0 1 //1,2,5

    1 0 1 0 1 //1,3,5

    0 1 1 0 1 //2,3,5

    1 0 0 1 1 //1,4,5

    0 1 0 1 1 //2,4,5

    0 0 1 1 1 //3,4,5

    解法

    具体实现的时候,不需要每次“从左往右扫描”——我们可以这样考虑:每次将“10”变为“01”,可能产生新的翻转位置,那么就只需要记录新的翻转位置。

     public static List<T[]> Combination<T>(IList<T> list, int n)
        {           
            n = Math.Min(list.Count, n);
            var combs = new List<T[ ]>(); 
            var selection = new Dictionary<int>(n);//用于记录被选中元素的位置
             Stack<int> pins = new Stack<int>();//记录翻转位置
             for (int i = 0; i < n; i++) selection.TryAdd(i);//初始的时候选中前n个元素
             if (n < list.Count) pins.Push(n - 1);//初始翻转位置
             while (true)
            {
                 <添加生成的组合>
                 if (pins.Count == 0) break;
    
                 var i = pins.Pop();
                 selection.Remove(i);//去掉i
                 var j = i + 1;
                 selection.TryAdd(j);//选中j
                    
                 if (j + 1 < list.Count && !selection.ContainsKey(j + 1))
                    pins.Push(j);//增加翻转点
    
                   bool adjusted = false;
                 <将i左边这些连续的1全部移动到最左>
                 if (!adjusted && i - 1 >= 0 && selection.ContainsKey(i - 1))
                   pins.Push(i - 1);//增加翻转点 
             }
           return combs;
       }
    其中:
    <添加生成的组合>代码为:
            int cnt = 0;
           var newComb = new T[ n ];
           foreach (var index in selection) newComb[cnt++] = list[index];
           combs.Add(newComb);
    <将i左边这些连续的1全部移动到最左>代码为:
           if (i >= 2)
          {
              int z = 0;//从左数0的数量
                while (!selection.ContainsKey(z)) z++;
               if (z > 0 && z < i)
               {
                  var m = Math.Min(z, i - z);
                  for (int k = 0; k < m; k++) selection.TryAdd(k); //选中k
                  for (int k = i - 1; k >= i - m; k--) selection.Remove(k); //去掉k
                  pins.Push(i - z - 1);
                  adjusted = true;
               }
           }

    运行结果

    输入abcdef,选3个:

    1:abc  2:dab  3:dac  4:dbc  5:abe  6:ace  7:bce  8:ade  9:bde  10:cde  11:abf  12:acf  13:bcf  14:adf  15:bdf  16:cdf  17:aef  18:bef  19:cef  20:def

    讨论

    网上大多是“扫描算法”,这里用“翻转点”做了改进——不断构造新的翻转点,直到没办法再翻转。

    发表于 2009年11月1日 21:40 作者 silver | 1 评论
    归档在:
  • 一些常用集合算法——之全排列生成

    问题

    在开发过程中常常需要处理集合,我写了一些常用算法,贴出大家提提意见Big Smile

    本帖是最简单的全排列生成算法,因为全排本来列就很费,所以以简洁为重,没太多考虑效率。

    解法

            public static List<T[]> Permutation<T>(IEnumerable<T> list)
            {
                List<T[]> permutations = new List<T[]>();
    
                Permutation(new List<T>(list), 0, permutations);
    
                return permutations;
            }
            static void Permutation<T>(List<T> list, int h, List<T[]> permutations)
            {
                if (h == list.Count - 1)
                {
                    permutations.Add(list.ToArray());
                    return;
                }
    
                for (int i = h; i < list.Count; i++)
                {
                    Swap(list, h, i);
                    Permutation(list, h + 1, permutations);
                    Swap(list, h, i);//撤销交换
                  }
           }
            static void Swap<T>(List<T> list, int i, int j)
            {
                if (i != j)
                {
                    var temp = list[ i ];
                    list[ i ] = list[j];
                    list[j] = temp;
                }
            }

    运行结果

    输入abcd:

    1:abcd  2:abdc  3:acbd  4:acdb  5:adcb  6:adbc  7:bacd  8:badc  9:bcad  10:bcda  11:bdca  12:bdac  13:cbad  14:cbda  15:cabd  16:cadb  17:cdab  18:cdba  19:dbca 20:dbac  21:dcba  22:dcab  23:dacb  24:dabc

    讨论

    算法思路很简单,就是一位一位去填,和全排列计算的思路一致。

    此算法在只需要输出或者测试排列的问题中,可以不去生成所有排列的实例,从而节约大量内存。

    发表于 2009年11月1日 21:10 作者 silver | 0 评论
    归档在:
  • 不用API构造简单自旋锁

    问题

    想必大家都记得《编程之美》“双线程下载”的精彩例子。这里我们来看一个类似的例子。

    分析

    由于采用“挂起/唤醒”线程的方式加锁,往往涉及到“用户态/核心态”切换,导致不小的性能损失,因此人们发明了自旋锁,在一些分布式应用程序中,性能得到了10~1000倍的提升。

    解法

    这里我们来看一个最简单的,不用API构造的自旋锁:

       class 简单自旋锁
        {
            int c1 = 0, c2 = 0, who_wait;
    
            void WrokA()
            {
                int taskCnt = 0;
                while (taskCnt++ < 5)
                {
                    c1 = 1;//请求进入临界区
                        who_wait = 1;
                    while (c2 == 1 && who_wait == 1) ;
                    DoSomeWork(taskCnt + "A", ref c1);
                }
            }
            void WrokB()
            {
                int taskCnt = 0;
                while (taskCnt++ < 5)
                {
                    c2 = 1;
                    who_wait = 2;
                    while (c1 == 1 && who_wait == 2) ;
                    DoSomeWork(taskCnt + "B", ref c2);
                }
            }
            void DoSomeWork(string who, ref int c)
            {
                try
                {
                    DoSomeWork(who);
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.Message);
                }
                finally
                {
                    c = 0;//离开临界区
                   }
            }
            void DoSomeWork(string who)
            {
                Console.WriteLine(who + " enters critical section...");
    
                Console.Write(who + " is working");
                for (int i = 0; i < 10; i++)
                {
                    Thread.Sleep(300);
                    Console.Write(".");
                }
                Console.WriteLine();
    
                Console.WriteLine(who + " leaves critical section... \n");
            }
    
            public void Test()
            {
                var threadA = new Thread(new ThreadStart(WrokA));
                var threadB = new Thread(new ThreadStart(WrokB));
    
                threadA.Start();
                threadB.Start();
            }
        }

    运行程序,得到如下结果:

    1A enters critical section...

    1A is working..........

    1A leaves critical section...

    1B enters critical section...

    1B is working..........

    1B leaves critical section...

    2A enters critical section...

    2A is working..........

    2A leaves critical section...

    2B enters critical section...

    2B is working..........

    2B leaves critical section...

    ……

    讨论

    自旋锁需要解决如下问题:

    1. 没有试图进入临界区的线程不得阻止其它线程进入;

    2. 不能让某线程“刚好每次都进入临界区”,某线程“刚好每次都等待”;

    3. 不能出现互相等待的“死锁”;

    4. 不能出现进入临界区之前,线程互相“谦让”,导致不确定谁进入的“活锁”。

    5. 某线程出错不能影响其它线程。

    这个例子只是双线程,我们以后再研究多线程的例子。

    发表于 2009年10月29日 7:16 作者 silver | 2 评论
    归档在:
更多内容 下一页 »
Powered by Community Server (Personal Edition), by Telligent Systems
访问计数:     京ICP备06016978号
王屋村村民除了看yishan.cc, 还浏览下列网站.