算法的脑力风暴。。

20 篇文章 0 订阅
18 篇文章 1 订阅

母亲说"冬"是节,所以今天下午后没有看书学习,跑去游戏机房狂玩Drifting。
回来后给读书的、考研的、工作的朋友们发个祝福。。

习惯性到CSDN看看贴子,发现有个标题很特别,叫"如何提高编程速度"
http://community.csdn.net/Expert/TopicView3.asp?id=5248357
区区没有回复这个贴子,因为这个问题本身有问题。。
<<<<<
楼主的post内容如下:
作  者:         dirtysalt (李纳斯)
等  级:        
信 誉 值:         100
所属论坛:         C/C++ C语言
问题点数:         20
回复次数:         18
发表时间:         2006-12-22 9:35:59
我看了网上一位网友的帖子,做百度笔试题
求123456789字典顺序中一个特定的数

他们居然只用5分钟就想出来,花了20分钟解决
我,我居然花半个小时考虑,用了一个半小时才完全写正确

问题出来了,如何提高编程速度
<<<<<

一看又是百度的面试题,就知道都考的是算法了。题目没看明白。楼主表述不清,
还好,后来又补了一贴,区区才明白他问什么

<<<<<
回复人:dirtysalt(李纳斯) ( 一级(初级)) 信誉:100     2006-12-22 15:45:10     得分:0
我针对的并不是某个问题来问你们。只是当我看到同样的人做这道题目时,我有些感概。
具体题目是编写一个函数int getline(int n)
对于n=1 返回123456789
n=2返回123456798
n=3 123456879
以此类推
这个问题是百度一道面试题,我已经做完了,所以不需要劳烦大家。
只是看到别人花很少时间就写完,我有些感概。
我发现我的毛病,一些程序中小问题,导致调试时间很长,所以速度下降
我想知道你们是怎么解决这个问题的,如何提高编程速度(当然,按Jamesonang(Jameson Ang) 说就是要在好的算法下,尽快完成程序)。
<<<<<

题目的意思就是对一个字符串"123456789"来一个字典序全排列,然后按输入求出第N个排列。。
信手打开命令行,输入perl -e "print 9*8*7*6*5*4*3*2*1"
结果是 362880。。不知道百度的人对时间和空间复杂度要求是什么。这个问题规模不大。

所以先用脚本来写一个吧~
下面这个perl程序是随手写出来的,不考虑效率,在区区的机器上运行6秒左右可以算出所有的结果
# perl
my   @arr   =   1 .. 9 ;
my   @permute ;

sub  make_permute{
    
my  ( $head_str , $idx , $arr =   @_ ;
    
if  (  8   ==   $idx ){
        
push   @permute ,   $head_str   .   $arr -> [ 0 ];
        
return ;
    }
    
for   $num  ( 0 .. @ $arr - 1 ){
        (
$arr -> [ $num ] , $arr -> [ 0 ])  =  ( $arr -> [ 0 ] , $arr -> [ $num ]);
        
my   $h   =   shift  @ $arr ;
        make_permute(
$head_str   .   $h ,   $idx + 1 ,   $arr );
        
unshift  @ $arr ,   $h ;
        (
$arr -> [ $num ] , $arr -> [ 0 ])  =  ( $arr -> [ 0 ] , $arr -> [ $num ]);
    }
}

make_permute(
"" , 0 , @arr );
@permute   =   sort  { $a   <=>   $b } ( @permute );
# my $FH;
#open $FH,">","./out.txt";
#print $FH "$_ " for (@permute);  #打印到文件
#print  "$_ " for (@permute);     #打印到
####


其实在思考题目时,这个perl程序并没有完全写出来,运行6秒应该也算是超时才对,
不过,这个脚本很容易实现,而且可以拿来当测试使用。。

如果对这个程序优化一下,速度可以提高很多的,第一个perl程序很烂,是因为最近没怎么
用perl,所以有些生疏。正常的perl黑客,应该可以不费吹灰之力写出下面的版本:
# perl
my   $top   =   9 ;
my   @arr   =   1 .. $top ;
my   @permute ;
sub  make_permute{
    
my  ( $head_str , $idx , $arr =   @_ ;
    
if  (  $top - 1   ==   $idx ){
        
push   @permute ,   $head_str   .   $arr -> [ 0 ];
        
return ;
    }
    
for   $num  ( 0 .. @ $arr - 1 ){
        
my   @tarr   =  @ $arr ;
        
my   $h   =   splice   @tarr , $num , 1 ;
        make_permute(
$head_str   .   $h ,   $idx + 1 ,   @tarr );
    }
}
make_permute(
"" , 0 , @arr );
# ########################

改进过的程序可以对付小规模的问题,更易于调试。代码不但简洁了,速度也变快了,区区
的机子算出所有的362880个排列只需要3.9秒~~~

也就是说,虽然未必在时空上达到题目要求,但的确有可能有人几分钟就先拿到结果(不是区区,
版本一和版本二上的修改,花了区区不少时间)~

那现在我们假设时间要求是1秒吧~这个算法行得通么?应该没问题,如果对perl的运行效率和内存
字符串的操作有深入理解,应该知道如果用C语言来实现,可以优化到一秒内的
/** ******************************************** */
#include 
< stdio.h >
#include 
< stdlib.h >
#include 
< string.h >

#define TOP 
9

char   * num_str;
char   * long_permute;
int    long_idx;

int  factor( int  n_){
    
return  n_ == 1 ? 1 :factor(n_ - 1 ) * n_;
}

void  fill_n( char   * arr_,  int  n){
    
int  i;
    
for (i = 0 ; i < n;  ++ i){
        arr_[i] 
=   ' 0 ' + i + 1 ;
    }
    arr_[i] 
=   0 ;
}

void  make_permute( int  deep_){
    
int  i,k;
    
char  t;
    
if (TOP - 1   ==  deep_){
        strcpy(long_permute
+ long_idx, num_str);
        long_idx 
+=  TOP + 1 ;
        
return ;
    }

    
for (i = 0 ; i  <  (TOP  -  deep_);  ++ i){
        t 
=  num_str[deep_ + i];
        
for ( k = deep_ + i; k > deep_ ;  -- k){
            num_str[k] 
=  num_str[k - 1 ];
        }
        num_str[deep_] 
=  t;

        make_permute(deep_
+ 1 );
       
        
for ( k = deep_; k < deep_ + i;  ++ k){
            num_str[k] 
=  num_str[k + 1 ];
        }
        num_str[deep_
+ i]  =  t;
    }
}
void  output( void ){
        FILE 
* outfile;
        
int  i,len;
        len 
=  factor(TOP);
        outfile 
=  fopen( " out1.txt " , " w " );
        
for (i = long_idx = 0 ; i < len;  ++ i, long_idx  +=  TOP + 1  ){
            fprintf(outfile,
" %s " ,long_permute + long_idx);
        }   
}
int  main( void ){
    num_str 
=  malloc( (TOP + 1 *  sizeof( char ) );
    long_permute    
=  malloc( factor(TOP)  *  sizeof( char *  (TOP + 1 ) );
    long_idx        
=   0
    fill_n(num_str,TOP);
   
    make_permute(
0 );
   
    output();
    
return   0 ;
   
}
/** ******************************************** */

在区区的机器上,这个C程序只用0.3秒就把所有的排列输出到out1.txt。。呵呵。。看来
最简单的方法在时间上都能满足要求了。。
上面的三个程序,并不是为了表明C比perl要快3.9/0.3-1倍,因为在最后一个C版程序中,
区区自行管理内存,省去了很多不必要的操作,这个在perl当中也是可以优化实现的。。

写了一大堆程序之后,回来原先贴子上的话题:怎么加快编程速度。。
那区区认为,通过上面的例子就知道,如果你手头有很好的工具语言,有类似问题的解决
经验,对自己所用的语言有很深入的理解,有很广的知识面,而且还经常编程,经常调试,
编程的速度自然而然就快了~~~

上面的算法,是区区连想都没想就已经存有的。全排列是以往做过的东西。

!!没有结束~
如果只是这样,那也太丢人了,C版的程序是很快,但是里面有个大得可怕的常数:
long_permute    = malloc( factor(TOP) * sizeof(char) * (TOP+1) );
这条语句得占去好多MB的内存,如果对空间复杂度也有要求咋办?

不过还好,也没什么紧张的,因为上面那几个程序区区一开始在思考时都没有去写,区区
开头就在思考时空复杂度都很小的算法——————要不然,这道题应该没什么意思了~

其实准确地说,区区是在找规律:123456789、串、字典序、全排列,全都是有规律的说~
列了列草图,从小的做起吧
1  0

12 0
21 1

123 000
132 001
213 010
231 011
312 100
321 101

1234  0000
1243  0001
1324  0010
1342  0011
1423  0100
1432  0101
2134  0111
2143  1000

一开始很直觉地去找寻2进制数与排列的关系。。在长度1-3倒是隐约发现一个共同点:某位上
如果置1,排列上就与前一字符互换。很开心,只是一试就有个规律。开始求证么?NONONO,
四位排列也不多,所以也画出来。…………很快可以看出来,这个规律不对。

回头一想:当然不对,一个以阶乘增长,一个以乘幂增长。。。
等等~~~区区再回头一想:增长……阶乘增长————不正是规律么?
1 2 6 24
再看头一个字符出现的次数,呵呵……正好。。。
一个串的字典序排位,第一个字符与剩余字串排列数目(阶乘)有关,剩余字符串可以递推~
公式:对于长度为N的字符串,求第K的排列时,首字符应该是 ceil( K / ((N-1)!) )号字符

心算检验~~正确,写代码吧。。

# perl
sub  factor{
    
return   $_ [ 0 ==   1   ?   1   :  factor( $_ [ 0 ] - 1 *   $_ [ 0 ];
}
sub  k_of_dict{
    
my  ( $n , $k , $arr =   @_ ;
   
    
for ( my   $i = 0 $i < $n - 1 ++ $i ){
        
my   $t   =   $k / factor( $n - $i - 1 );
        
$k   =   $k   %  factor( $n - $i - 1 );
        
next   if   int ( $t ) == 0 ;
        
splice  @ $arr ,   $i ,   $t + 1   ,   $arr -> [ $i + $t ] ,  @{ $arr }[ $i .. $i + $t - 1 ];
    }
}

# my $top=9;#输出到文件。
#my $FH;
#open $FH,">","out2.txt";
#my $len = factor($top);
#for (my $i=0;$i<$len;++$i){
#    my @arr = 1..$top;
#    k_of_dict($top,$i,@arr);
#    print $FH @arr," ";
#}
###############################################

最后的这个程序,对于输出单个字典位置上的字符串,无论时间还是空间复杂度上,都不再
与N的阶乘有关。。相比与之前的版本,不再需要多余的内存了。。如果进一步发掘,可以找
出更好的算法。

丰富的数据结构知识、数学建模知识、分析统计可以找出更好的算法~~



本来如果还有时间,是打算写一个飞速的C版本。
但是完成这四个程序,脑力体操做得也很累~~

我们毕竟和那天常常在线做ACM题目的黄金圣斗士们不同,他们天天在为竞赛做准备的。。
台上一分钟,台下十年功,如果有人可以五分钟做出来,那说明他天份高,下苦功吧。

                                    P.S:想这道题还没写Blog花时间,唉~


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值