算法的脑力风暴。。


母亲说"冬"是节,所以今天下午后没有看书学习,跑去游戏机房狂玩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、付费专栏及课程。

余额充值