递归与回溯算法

C/C++, 日志 No Comments »

      长久以来一直对递归一知半解。最近看面试方面的书,在数据结构中有很多算法是和递归有关的,另外一些经典问题:打靶问题、8皇后问题、0-1背包问题也和递归有关,在有些算法中递归甚至是必不可少的,因此很有必要把递归算法再重新学一下。

      首先说一下递归和迭代的区别。迭代是反复的意思,有时候也指重复执行和反复执行。迭代一般是通过循环执行一组指令,并且在每次执行后都会从变量的原值推出它的新值。迭代往往需要一个迭代表达式,如f(n+1)=f(n)+f(n-1)。大家可以看出递归中的经典案例N!也可以通过迭代来做,而且更高效(因为迭代相比递归一般要高效很多)。

      下面就来谈递归。递归的基本思想是把规模大的问题转化为规模小的相似的子问题来解决。在函数实现时,因为解决大问题的方法和解决小问题的方法往往是同一个方法,所以就产生了函数调用它自身的情况。另外这个解决问题的函数必须有明显的结束条件,这样就不会产生无限递归的情况了。文[1]中对于递归讲解比较细致,大家可以参考。下面展示两段代码[1]:

返回一个二叉树的深度:


int depth(Tree t){
      if(!t) return 0; 

    else { 

        int a=depth(t.right); 

        int b=depth(t.left); 

        return (a>b)?(a+1):(b+1); 

    } 

}

判断一个二叉树是否平衡:


int isB(Tree t){
      if(!t) return 0; 

    int left=isB(t.left); 

    int right=isB(t.right); 

    if( left >=0 && right >=0 && left - right <= 1 || left -right >=-1) 

        return (left<right)? (right +1) : (left + 1); 

    else return -1; 

} 

    第一个算法还是比较好理解的,但第二个就不那么好理解了。第一个算法的思想是:如果这个树是空,则返回0;否则先求左边树的深度,再求右边数的深度,然后对这两个值进行比较哪个大就取哪个值+1。而第二个算法,首先应该明白isB函数的功能,它对于空树返回0,对于平衡树返回树的深度,对于不平衡树返回-1。明白了函数的功能再看代码就明白多了,只要有一个函数返回了-1,则整个函数就会返回-1。(具体过程只要认真看下就明白了)

    对于递归,最好的理解方式便是从函数的功能意义的层面来理解。了解一个问题如何被分解为它的子问题,这样对于递归函数代码也就理解了。这里有一个误区(我也曾深陷其中),就是通过分析堆栈,分析一个一个函数的调用过程、输出结果来分析递归的算法。这是十分要不得的,这样只会把自己弄晕,其实递归本质上也是函数的调用,调用的函数是自己或者不是自己其实没什么区别。在函数调用时总会把一些临时信息保存到堆栈,堆栈只是为了函数能正确的返回,仅此而已。我们只要知道递归会导致大量的函数调用,大量的堆栈操作就可以了。

    下面介绍和递归密切相关的算法:回溯算法。回溯算法是一种搜索算法,它可以通过对问题从一个状态出发,搜索从该状态出发的所有状态,当一条路走到尽头时,再后退一步继续搜索,直到所有路径都试探过。回溯算法通常由递归来实现,因为递归结束时会返回调用处(上一步),回溯结束时也是返回上一步,两者可以很好的契合。文[2]中对于回溯的讲解挺详细的,大家可以参考下。这里引用文中的一段话,我觉得写的挺好的:“通过把递归结束的条件设置到搜索的最后一步,就可以借用递归的特性来回溯了。因为合法的递归调用总是要回到它的上一层调用的,那么在回溯搜索中,回到上一层调用就是回到了前一个步骤。当在当前步骤找不到一种符合条件情况时,那么后续情况也就不用考虑了,所以就让递归调用返回上一层(也就是回到前一个步骤)找一个以前没有尝试过的情况继续进行。当然有时候为了能够正常的进行继续搜索,需要恢复以前的调用环境。”。下面通过一个例子来介绍递归回溯的实现

楼梯问题:假设一个楼梯共有10级台阶,一个人可以选择一次走1级或2级台阶,请问总共有多少种不同走法?

    这个问题是之前一个师兄跟我说的,当时考虑了好久也不得其解,现在通过递归回溯算法来解决它。具体代码如下:


#include <iostream> 

using namespace std; 

const int STAIRS=10;
  const int MAX_STEP=2; 

int result_number=1;
  int steps[STAIRS]; 

void print_result(){
      cout<<"This is the "<<result_number++<<" result: "<<endl;

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

        if(steps[i]==0){

            break;

        }

        cout<<steps[i]<<" ";

    }

    cout<<endl;

} 

void do_step(int stairs,int step_number){
      if(stairs==1){

        steps[step_number]=1;

        print_result();

        steps[step_number]=0;

        steps[step_number-1]=0;

        return;

    }

    else if(stairs==0){

        print_result();

        steps[step_number-1]=0;

        return;

    }

    else{

        for(int i=1;i<=MAX_STEP;i++){

                  if(stairs-i<0){

            break;

            }

            steps[step_number]=i;

            do_step(stairs-i,step_number+1);

        }

     }

} 

int main(){
      cout<<"the program started..."<<endl;

    do_step(STAIRS,0);

    cout<<"there are "<<result_number-1<<" results."<<endl;

}

     do_step函数的功能是:当执行时如果参数stairs为1,或0,则直接输出结果,并且把steps[]恢复到上一步的状态,退出函数。如果stairs大于1,则尝试分别走1步和2步,此后问题转化为原问题的子问题。明白了函数的功能就很容易看懂代码了。该函数中出现了for循环嵌套 递归的情况,大家只要认为是多次执行其中的代码就可以了(那个我们不理解的复杂的函数堆栈调用关系可以正确无误的实现我们要求的函数调用,呵呵)。程序会列出每个结果,总的可行走法共89种。

    大家了解了回溯算法后就可以解决一大类问题了,包括上面说的打靶问题、8皇后问题等。大家也可以尝试通过回溯算法来解决“数独”问题,通过搜索来查找可行的数独解(也算是个小小的挑战吧,^_^)。 

参考资料:

[1] 漫谈递归思想 http://www.cnblogs.com/BLoodMaster/archive/2010/03/23/1692641.html

[2] 递归回溯总结 http://hi.baidu.com/sulipol/blog/item/32411851bb9724551138c2eb.html

[3] 《程序员面试宝典》第8章 循环、递归与概率

fedora下gruff的安装使用

Linux, Ruby, 日志 No Comments »

最近一段时间一直在准备着找工作,学各种东西。期间还是感觉很有收获的,等有时间整理一下然后发布出来。实验室的项目也还在进行,不过我的工作相对少了很多(因为快要毕业了嘛,还是很感激老师能体谅我们的,^_^)。

目前我在项目中的任务是做一个系统的图形界面。当初也不知道怎么想的,竟然想用web形式来实现,也许是为了不让自己之前学的ruby和rails不要白费吧,呵呵。现在想想Web GUI的想法的确挺好的,由于ruby的类库丰富,开发相对还是比较轻松的,不过为了让C++系统和rails的能正常交互消息还是需要学一下http类库libcurl方面的知识(这些知识它的官网上有很多,也很详细)。

由于项目的需要,我们的图形界面上需要显示一些数据的分析图表,使用ruby的gruff工具包可以绘制很好的数据分析图表。下面说下linux环境下gruff的安装和使用,安装过程如下:

1、首先安装ruby-1.8.6环境以及ruby gems,大家可以在网上找相关的教程。

2、安装ImageMagick的库,gruff的运行依赖于这些库

yum install ImageMagick

yum install ImageMagick-devel

3、安装TrueType字体。ImageMagick在画图时需要用到这个库。有些linux版本会默认安装,那样的话大家就可以跳过这步了

wget http://www.osresources.com/files/centos-windows-fonts/msfonts.tbz

mkdir /usr/share/fonts/default/TrueType

tar xvjpf msfonts.tbz -C /usr/share/fonts/default/TrueType/

4、安装 rmagick 的gem包

gem install rmagick

5、安装gruff所需的gem包。gruff包会依赖以下包:json_pure、rubyforge、hoe、rake

gem install gruff

至此gruff就安装完成了。下面介绍一个gruff的代码实例,实例中会绘制一个柱状图,其他类型的图的画法请参考gruff的rdoc文档。

示例代码如下:

require 'rubygems'
require 'gruff'

g = Gruff::Bar.new(600)        # The graph will be 600 pixels wide.
g.bar_spacing = 0.5   # the bars' space
g.title = 'The Nodes\'s Load'
g.theme_37signals        # The best-looking theme, in my options.

load_info_1=[21,3,6,14,7,11,19]   #the data information, you can use you own data here.
load_info_2=[8,27,13,4,9,14,10]

range = (1..7)
g.data('Node 1', range.collect { |x| load_info_1[x-1] })
g.data('Node 2', range.collect { |x| load_info_2[x-1]})

g.labels = {2 => 'n=2', 4 => 'n=4', 6 =>'n=6' }
g.write('load_info_bar.png')

输出结果图片如下:

load_info_bar

可以看出,gruff生成的图片还是挺漂亮的。我这里的数据是自己任意写的,没什么实际的意义。

PS:我个人还是很喜欢Ruby语言的,它的语法优美,代码看起来很像伪代码,很易读;它有丰富的类库,对文本处理的功能很强。大家如果想学一门脚本语言的话建议学习Ruby,可以很好的提高你的工作效率,呵呵

参考资料:

[1]http://hi.baidu.com/rainchen/blog/item/089ef7364497de320a55a9a3.html

[2] Ruby Cookbook:  Recipe 12.4. Graphing Data

blog主题更新

Tips, 日志 No Comments »

     鼓捣了好久wordpress主题,发现还是最开始的主题比较好。之前换其他主题主要是因为文章栏太窄了,当显示前面的游戏的时候显示不完整。之后在网上下了很多别人推荐的主题,包括Stripey、BlackGelly、Manly、pyrmont等,这些主题初看起来的确不错,漂亮的外观和字体,但当真正使用时才发现各种问题。因为我的blog现在装了很多插件,如代码显示还有评论表情,嵌套评论等,当采用新主题时,要么插件不能使用,要么显示错误。而且这些主题也有不同的缺点,页面的边栏中的内容要么太少,要么太多。

     现在才明白,真正好用的主题还得自己动手改。自己花了段时间对页面的布局进行调整,主要是修改背景图片和css样式。幸运的是在网上找到了这个主题的头部的图像的psd文件,可以方便的对图像进行缩放等简单的修改而不损失图像质量。我现在的主题是glossyblue,最初在yo2的时候也是用的这个主题。目前安装的插件如下:

  • Akismet :用来抵制垃圾评论的。装上这个插件后垃圾评论少了好多,我的邮箱也清净好多。
  • Custom Smiles:用来评论中使用表情的,挺好用的
  • Add From Server:用来往wordpress中上传文件的,比wordpress自带的上传工具要好用得多
  • Syntax Highlighter and Code Prettifier Plugin for WordPress :用来显示代码的插件,这个插件的功能还是比较强大的,可以支持各种语言,c/cpp, bash、java、ruby等
  • Wordpress Thread Comment :由于我的主题自身不支持嵌套评论,所以用了这个插件
  • WP SlimStat :wordpress的统计工具,可以统计博客的访问量,有哪些页面比较流行,访问者所用的浏览器,最近的关键词等,信息很多,很好用

     另外推荐一个网址 一些不错的WordPress主题下载站 ,上面有好多很好的主题,大家可以根据自己的需要修改。大家如果有什么关于WP主题的问题或意见欢迎交流。

     PS:最近在试用Windows Live Writer,一个wordpress的离线编辑器,目前感觉还是挺好用的,大家也可以试试。。。

Linux程序输入、输出重定向

C/C++, Linux No Comments »

对于输出重定向大家应该都比较了解了,一般都是指把输出重定向到一个文件中,而对于输入重定向一般就不是很常用了。暂时的一个应用就是实现程序的脚本控制,比如你用脚本启动另一个程序,然后又需要给这个启动的程序发送命令,这时就需要采用输入重定向了,这对于一些服务类型的程序还是很有用的。要实现真正意义的输入重定向还是比较麻烦的,需要用到管道。

下面用一个简单的示例来实现程序的输入,输出重定向。

//file_name:shi_li.cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>

#define MAX_LINE 80
#define PIPE_STDIN  0
#define PIPE_STDOUT 1

int main(){
 int child_pid;

 char outPath[30];
 strcpy(outPath,"./out.file");

 int pfds[2];
 if (pipe(pfds)== 0){
  child_pid=fork();
  //in child process the return pid==0
  if ( child_pid == 0 ) {

   //input file,may be used later.
   //char inPath[30];
   //strcpy(inPath,"./in.file");

   //int inFd = open(inPath,O_RDWR|O_CREAT,S_IRUSR|S_IWUSR);
   int outFd = open(outPath,O_WRONLY|O_APPEND|O_CREAT,S_IRUSR);

   close(0);
   dup2( pfds[PIPE_STDIN], 0);
   dup2(outFd,1);
   close(pfds[PIPE_STDOUT]);
   execl("./getstring","getstring",NULL);
   printf("start getstring: error!!!");
   exit(1);
          }
  else {

   close(pfds[PIPE_STDIN]);
       char msg[MAX_LINE];
         int b=1;
        for(b=1;b<6;b++){              
           sleep(1);
             sprintf(msg,"2+%d\n",b); 
             write( pfds[PIPE_STDOUT], msg, strlen(msg) );              
                 }
         close(pfds[PIPE_STDOUT]);
         sleep(2);
         printf("all done.\n");
         }
       }
 return 0;
} 

接下来是一个很简单的程序输入:

//file_name: getstring.cpp

#include <unistd.h>
#include <iostream>
using namespace std;

int main(){ 
 string tmp;

 while(cin >> tmp,!cin.eof()){
  if(cin.bad()){  
   cerr<<"io stream error"<<endl;
   exit(1); 
  }
  if(cin.fail()){
   cerr<<"io stream error"<<endl;
   cin.clear(istream::failbit);
   continue;
  }
  //ok to process 
  cout<<"all done well:"<<tmp<<endl;
 }
}

编译并运行:

#g++ -o getstring getstring.cpp
#g++ -o shi_li shi_li.cpp
#./shi_li

上述代码包括两个小程序,getstring简单的接收输入的字符串并在终端上回显。shi_li程序负责启动getstring并把输入重定向到管道,输出重定向到out.file文件。执行代码后我们可以查看out.file文件来获得程序执行的结果。

上述小程序可以用在脚本时对向子程序发送命令,从而完成程序测试的自动化。上述代码只是个demo,如果想在实际测试中可用还需要根据自己的情况进行些许修改。

参考资料:

[1] http://blog.chinaunix.net/u/19573/showart_1225848.html

[2]http://www.opengroup.org/onlinepubs/009695399/

[转]Manufactoria:非常好玩的自动机编程游戏

BrainStorm 2 Comments »

这是我在Matrix67上看到的一个很好玩的自动机编程游戏。很遗憾我自己还没玩通关,呵呵。

游戏介绍:它是真正意义上的程序设计游戏,游戏不但提供了完备的读写和流程控制功能,甚至还引入了随机测试数据。游戏很快就会引入算法的思想,因为玩家渐渐会发现,这些谜题并不是单靠模拟就能解决的;后面的谜题则越发困难,需要相当有技巧性的算法设计,对脑力绝对是一个大挑战。如果你热爱算法与程序设计,你一定会爱上这个游戏的。

原文地址:http://www.matrix67.com/blog/archives/3306

游戏来源:http://jayisgames.com/games/manufactoria/

ubuntu下成功安装sfslite

C/C++, Linux No Comments »

之前曾采用MIT的开源chord来开发P2P的系统,由于chord依赖于sfslite的一些库文件,所以需要安装sfslite-0.8.16。自此带来了很多问题,因为sfslite需要gcc-4.1.2才能编译,用最新的gcc-4.3或4.4都会编译失败,而且它好像还和操作系统有关,在fedora 7下可以正常编译。如果您不想使用fedora 7这种老版本的系统那就麻烦了。

最近新安装了ubuntu 10,突然想在ubuntu下尝试一下,这样后续的开发就不用再装fedora 7的虚拟机了,而且也可以方便程序的移植。下面简要介绍安装的过程:

首先安装gcc-4.1.2,具体安装过程参考“在ubuntu中编译、安装gcc 4.1.1过程以及遇到的问题”,上面讲的很详细。主要有一个地方需要注意,gcc-4.1.2依赖texinfo库,默然configure不支持texinfo 4.10+,需要修改configure文件。在其中找到’texinfo[^0-9]*([1-3][0-9]|4\.[2-9]|[5-9])’编辑成’texinfo[^0-9]*([1-3][0-9]|4\.[2-9]|4\.[1-9][0-9]*|[5-9])’后保存,编译通过。[注] 无需修改LD_LIBRARY_PATH变量,在ubuntu下没有这个变量,因为已经被废除了,直接安装好后就可以使用了。

[tips]可以使用ubuntu中自带的 update-alternatives 命令来方便的进行多个gcc版本之间的切换(具体命令使用可以在google上搜)。

接下来就是使用gcc 4.1.2来编译sfslite,首先下载sfslite-0.8.16的tar包。(不要使用sfslite-0.8.17,编译会出错)

然后解压,进入源文件根目录,输入 ./configure –with-sfsmisc (with前是两个-) 只要这一个选项就可以了,不用输–with-dmalloc

接下来如果直接make的话会出错,会提示 unknown sizeof  ucred 。在网上找了很久,终于发现只有在编译时加上-D_GNU_SOURCE才可以。在gcc编译选项中增加 -D_GNU_SOURCE,这需要改makefile文件。具体为async和arpc目录下的Makefile文件中找到“ECFLAGS =” 改为:“ECFLAGS = -D_GNU_SOURCE”

然后编译安装即可。

WP Theme & Icons by N.Design Studio
Entries RSS Comments RSS 登录