Jackei 的测试生活与人文社会读本

带着梦想和激情在现实中旅行
posts - 746, comments - 3391, trackbacks - 19, articles - 0
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

算法的力量

Posted on 2006-10-15 22:18 Jackei 阅读(3727) 评论(31)  编辑 收藏 所属分类: 08.乐思斋笔记00.置顶推荐

有这么一个数,当把它的最后一位(个位)挪到第一位的时候,得到的新数刚好是原来数的两倍。问这个数是多少?

 

——出自 1985 出版的一本小学5年级学生用的数学课外读物——《儿童数学世界》

这个问题看似简单,就是要找一个数出来,把这个数个位上的数字挪到最前面去,例如 123 变成 31212345变成51234。但是还要求得到的“新数”要是原来数的两倍。

简单的分析一下这条业务规则,不难得出下面的结论:

1.       取一个数作为“原数”;

2.       把“原数”个位上的数字挪到最前面,保存为一个“新数”;

3.       比较两个数字,如果“新数”是“原数”的两倍,则打印两个数并退出程序;

4.       如果不符合要求,则原数自加1并回到步骤2

显然通过手工方式找到这个数是不太现实的,为了加快查找这个数的速度,让我们编写一段代码来提高工作效率。已经实现的代码如下:

 

 1#~ defined a method to move the last number to line-begin
 2
 3def get_new_number(original_number)
 4
 5    
 6
 7    #~ get last number
 8
 9    last_number = original_number%10
10
11    puts "the last_number is : "+last_number.to_s 
12
13    
14
15    #~ get the length of original_number
16
17    original_number_in_sting=original_number.to_s
18
19    puts "the length of original_number is : "+original_number_in_sting.length.to_s
20
21    
22
23    #~ set the original_number = original_number/10 
24
25    original_number = original_number/10
26
27    puts "the new original_number is : "+original_number.to_s
28
29    
30
31    #~ move the last number to line-begin of original_number
32
33    for counter in 2..original_number_in_sting.length 
34
35        last_number=last_number*10
36
37    end
38
39    puts "the new last_number is : " + last_number.to_s
40
41    
42
43    #~ return the new number
44
45    return last_number+original_number
46
47    
48
49end
50
51 
52
53 
54
55#~ initialization
56
57#~ set the variable original_number = a number that the number >11
58
59original_number = 1
60
61puts "First: the original_number is : " + original_number.to_s
62
63#~ set the variable new_number = get_new_number(number01)
64
65new_number = get_new_number(original_number)
66
67puts "First: the new_number is : "+new_number.to_s
68
69#~ finished initialization 
70
71 
72
73 
74
75while original_number*2 != new_number
76
77 
78
79    original_number = original_number + 1
80
81    puts "the original_number in loop is : "+original_number.to_s
82
83    
84
85    new_number = get_new_number(original_number)
86
87    puts "the new_number in loop is : "+new_number.to_s
88
89    
90
91end
92
93 
94
95puts "We’ve got the number! It is : "+original_number.to_s
96
 

 

 

上面的代码是在 Ruby 1.8.4 下面调试通过的。这是典型的完全通过分析业务规则并忠实于业务规则而实现的一段代码,其中定义了一个方法专门来处理“把个位的数字挪到最前面生成一个新数”这件事情,其他部分就是不断地反复比较、尝试,直到找到我们所期望的那个数。这段代码也并不复杂,其中“#~ ”表示注释掉的内容,puts 表示打印信息在屏幕上。如果你有兴趣可以很容易的用其他语言改写。

 

 

如果说上面的分析和代码实现是使用了“业务视角”的话,下面我们再换个视角看看。

根据业务规则——有这么一个数,当把它的最后一位(个位)挪到第一位的时候,得到的新数刚好是原来数的两倍——我们可以知道,这个数至少是两位以上的,并且可以人为的分为两个部分——个位部分和其他部分,可以用一个等式来表示这条业务规则想表达的意思:

2*(10X+Y) = Y*10 (n-1) + X

让我们继续化简这个等式:

20X+2Y = Y*10 (n-1) + X

19X = Y*10 (n-1) – 2Y

19X = Y (10 (n-1) -2)

最终我们得到了下面这个等式

X = Y (10 (n-1) -2)/19

在上面的等式中,Y表示个位上的数字,X表示其余的部分,n表示这个数的位数,例如:对于123这个数,Y=3X=12n=3。我们可以知道XY一定都是正整数,另外,我们还可以知道Y一定是一个0-9之间的数字,所以我们只要求得X的值,就很容易的可以知道我们要找的那个数了。

最后一个关键,就是n的取值,但其实这个值我们是可以控制的,我们可以尝试着给n赋一个值,然后把10 (n-1) 作为一个值来处理。如果在一个既定的范围内找不到我们需要的数,我们可以继续加大n的取值。这样在我们的等式中就只剩下X这一个未知数了。

最终我们得到下面的代码:

 

 

 1 = 1
 2 
 3 #~ 计算10 的100次方内是否有我们要找的数
 4 
 5 for i in 1..100
 6 
 7     n =* 10
 8 
 9     for y in 1..9
10 
11 #~ 如果找到了我们需要的数,就打印出“原数”和“新数”
12 
13         if (y*(n-2))%19==0 then
14 
15                   puts "the original number is            : " + (10*((y*(n-2))/19)+y).to_s
16 
17                   puts "the new number is              : " + (2*(10*((y*(n-2))/19)+y)).to_s
18 
19         end
20 
21     end
22 
23 end
24 


 

执行这段代码后,我们获得了下面这些返回结果:

 

the original number is      : 52631578947368421

the new number is              : 105263157894736842

the original number is      : 105263157894736842

the new number is              : 210526315789473684

the original number is      : 157894736842105263

the new number is              : 315789473684210526

the original number is      : 210526315789473684

the new number is              : 421052631578947368

the original number is      : 263157894736842105

the new number is              : 526315789473684210

the original number is      : 315789473684210526

the new number is              : 631578947368421052

the original number is      : 368421052631578947

the new number is              : 736842105263157894

the original number is      : 421052631578947368

the new number is              : 842105263157894736

the original number is      : 473684210526315789

the new number is              : 947368421052631578

 

从中我们可以看到,除了标为红色的第一组以外,其他的数都是符合我们要求的。

 

前面写了这么大堆,当然目的还是想说明一下这两种方法的差别。

第一种方法是完全面向业务的分析和实现方法,代码并不算累赘,而且很容易通过阅读代码反向来了解业务的原始需求和业务规则;而后一种则是通过数学的方法进行分析和抽象之后得到的结果。相比较之下,相信大家不能看出两者之间执行效率上的差距——因为第一种方法的原理是从1开始逐个尝试。

我就不再计算圈复杂度或者执行效率之类的刻板数据了,让我们用一个更直观的方法来对比一下这两种算法的差别。

第一种方法是我最开始的做法,那段代码看似中规中矩,但是通过最后得到的结果我们可以看到,符合我们要求的最小的一个数也大于1017次方,而使用我的方法在一台P4 3G + 1G 内存的机器上运行了一小时也不过才尝试到109次方,照此计算,至少要连续运行10多万年才能找到第一个符合要求的数字。

而第二种方法,则是得益于QQ群里一位昵称为“岚”的朋友的指点,使用这个方法,一秒中已经可以完成10100次方以内的查找。

 

1 vs. 10万年!


   这就是算法的力量!这就是知识的价值!

如果你还在用代码描述着业务,那么尝试一下第二种方法吧 ^_^

 

再次感谢“岚”的指点。

 

 

Feedback

#1楼    回复  引用  查看    

2006-10-15 23:35 by Dflying Chen      
Ruby赞

#2楼    回复  引用  查看    

2006-10-16 00:02 by Jason Cui      
酷。

#3楼    回复  引用  查看    

2006-10-16 00:40 by 戴南      
所以我们做应用开发的,要更多的关注整体架构.
业务建模的问题还是交给数学高手吧.
我是这么想的.
毕竟知识的积累也正是每个人点滴的积累,而非自己一人所能达到.

#4楼    回复  引用  查看    

2006-10-16 08:28 by 跨越      
这是深有体会的,特是在处理复杂业务的时候。一个很简单的改变就会省很多时间

#5楼    回复  引用    

2006-10-16 09:29 by Emily_wong [未注册用户]
hehe!

支持戴南的说法!
个人认为,算法的确很大程度上能简化程序的复杂度,提高效率。但单纯一味地钻研算法,会增加在工程应用、产品研发过程中整体效率的开销。

凡事均需要衡量取舍的。

#6楼    回复  引用    

2006-10-16 10:24 by 岚 [未注册用户]
算法是核心,没有了算法,一切都不成立了。

#7楼    回复  引用  查看    

2006-10-16 10:38 by 极地银狐.NET      
算法在计算机领域永远不老。支持!

#8楼    回复  引用    

2006-10-16 10:51 by seamoon [未注册用户]
数学是大自然的描述语言!
好的程序员应该对各种语言都有比较好的了解和掌握。

#9楼    回复  引用    

2006-10-16 15:25 by sals [未注册用户]
不错的网站 呵呵
算法学过 但是不会 比较难啊

#10楼    回复  引用  查看    

2006-10-16 17:29 by zhh007's Bolg      
张见识了.

#11楼    回复  引用    

2006-10-16 20:23 by 张家界旅游网 [未注册用户]
感谢!!好文章越多越好。。。牛!

#12楼    回复  引用  查看    

2006-10-17 02:35 by 上善若水      
出自 1985 出版的一本小学5年级学生用的数学课外读物——《儿童数学世界》
我想知道在这本书上是如何给出解答的呢?小学五年级的数学读物啊!

#13楼 [楼主]   回复  引用  查看    

2006-10-17 09:19 by Jackei      
@上善若水

这道题是做为一道思考题列出的,所以并没有讲解具体的解题思路。


@其他各位

我个人认为并不是非要多么高深的算法才能叫做“算法”,相信大家在日常的开发中会经常用到算法的,例如对数据库中大量数据的操作,反复对文件的读写,都会遇到如何提高程序效率的问题的,只是有没有专门去思考或者提炼罢了。

#14楼    回复  引用  查看    

2006-10-17 09:26 by 上善若水      
我只是好奇,你前面提出的两种方法,不论是笨的一个数一个数找的方法还是后面的快速的算法,好象都显然不适合于小学五年级学生的思考范围,即然属于小学生五年级的课外思考题,那一定有适合小学生五年级的算法思想,我猜那应当更简单才是。

#15楼    回复  引用    

2006-10-17 09:54 by robertungame [未注册用户]
不错.
不过上面我们还可以进一步分析,因为将最后一个数放置到第一位的时候.且这个新数New和以前的位数Old相等,且New = 2*Old. 从而我们第一位数Y肯定大于或等于2*1. 因此我们在上面将Y的取值限制到2..9,从而运行的结果就可以将第一个错误项排除.

#16楼    回复  引用  查看    

2006-10-17 09:59 by 上善若水      
我真的很感兴趣小学生五年级的时候怎么做这道题。

#17楼    回复  引用    

2006-10-17 10:26 by 泥娃 [未注册用户]
估计是反推法:

1,从个位开始算,设原数的个位数为"2" 新数的个位数就是"4"
2,原数十位数是"4",新数十位数是"8"
....
...
....
一直推到新数的最新值等于原数的个位数就完成了

这样小学生一要一只笔和一张纸就可以完成

#18楼    回复  引用  查看    

2006-10-17 10:30 by 上善若水      
@泥姨
有道理,这个方法好象很简单。呵呵.反推时再考虑一下乘法的进位就可以了。

#19楼    回复  引用  查看    

2006-10-17 10:32 by 上善若水      
这种方法才是最简单的算法。前面提的什么算法的力量,是不是有些牵强了。硬是找了一个最笨的方法作为比较。呵呵。

#20楼    回复  引用  查看    

2006-10-17 10:35 by 上善若水      
楼主不要误会,我只是说,有时我们学的太多了,反而不会做小学生的题目。呵呵。每次侄女儿要我交她数学题的时候,我都头疼,其实我已经不大会做她的题目了,一上来就是列方程,可小学生还没有学方程呢,他们要的是逻辑推理的综合算式,那个难啊。:)

#21楼 [楼主]   回复  引用  查看    

2006-10-17 12:17 by Jackei      
@上善若水
您不用客气 ^_^
不过您提到的思路偶之前的确没有想到,实在汗颜……

另外,上面提到的“反推法”偶还没想明白 -_- 。能否具体解释一下?

#22楼    回复  引用  查看    

2006-10-17 15:53 by 上善若水      
我不敢居功,我没有想方法,我只是想一个小学生五年级的课外题,绝对不会你想的那么复杂。

泥姨提出反推法,我觉得是可以符合小学生的思维的。
the original number is : 473684210526315789

the new number is : 947368421052631578

以这组答案为例:
如果新数是老数的两倍的话,那新数的第一位数也就是老数的第二位数必然是第一位数*2以后的尾数。例如这例中的最后一位数是9,那么其第二位数必然是 9 * 2 = 18 的尾数 8 这就推出了第二位数必然是8,依此类推,第三位数必然是 8 * 2 = 16 的尾数 6 + 前面的一个进位 1 所以得出第三位数必然是7,依此类推,即可找出所有的位置上的数,一直找到正好产生的数等于第一位数即可。这种方法是正面推演的逻辑方法,正是小学生的方法。上初中以后就开始解方程了。呵呵。这处算法的效率,呵呵,没有浪费的,算十次即可。

#23楼    回复  引用    

2006-10-17 18:17 by zdg [未注册用户]
Track:
也说说算法的力量
刚巧看到一个网友研究“算法的力量”,说明好的算法有时候能大大提高速度。不过就这个网友提出的问题,其实不需要使用计算机来求解,数论方法很容易就能获取到方程的所有解。

http://blog.csdn.net/zdg/archive/2006/10/17/1338415.aspx

#24楼 [楼主]   回复  引用  查看    

2006-10-17 19:19 by Jackei      
@上善若水

呵呵,真是,真是,真是不知道该说什么才好了,偶现在知道为什么数学老师总不喜欢偶了 ^_^


@zdg

您的 blog 经常访问出错,要是您不介意,我想把您的那篇文章贴过来,如何?

#25楼    回复  引用    

2006-10-18 12:41 by 茶茶 [未注册用户]
值得思考的好东西:)
赞一个~~~

#26楼 [楼主]   回复  引用  查看    

2006-10-24 12:10 by Jackei      
今天又重新看了一下。发现偶最初的想法是要说明一下如果在设计和编写程序的过程中,只是用代码重新描述了一遍需求,是有可能会存在问题的;而如果对需求重新进行建模和分析,然后从另外一个角度来设计和实现,则效果会完全不同。

不过最后大家都讨论到数学上面去了 ^_^

#27楼    回复  引用    

2006-11-10 11:04 by 笨笨Q [未注册用户]
有意思的结果。看来我们的思维太僵化了。
想起以前听过一个故事,一家工厂生产的物品总有不达标的,不达标好像是包装是空的,漏装了吧,但是因为高速流水线上人力不足,没有办法一一检查。于是高薪聘请了一位专家,设计一套系统来专门检测,还到德国考察,购买高额硬件设备,培训专职操作人员,等等等等,终于得到了改进。忽问一家竞争的小公司只采有了一个工人的建议,花费了微不足道的一点小钱就解决了,原来他们在流水线旁边固定了一个强力风扇,不停对着经过的物品吹,不合格的立马被吹下生产线。于是问题解决了。哈哈哈哈。和楼上提出的简单解决方法,道理是一样的,我们有时候把问题复杂化了。看来大家都需要换换脑子,试着轻松点生活了。

#28楼    回复  引用    

2007-01-31 11:54 by 华华 [未注册用户]
haha, 好有意思,学到了2方面的知识:
1、算法的力量;
2、小学生的题还是要回归到小学生的思维方式去解答。

还有 笨笨Q讲的小故事,一个风扇就解决了所有问题,真是爽快啊。

#29楼    回复  引用    

2007-01-31 12:00 by 华华 [未注册用户]
咦! 第一组红色的错误数字怎么会出现,是程序有问题?

#30楼    回复  引用    

2007-02-13 15:23 by locker [未注册用户]
看需求定义, 如果是要求不是M级别, 我根本不会去考虑算法。如果是MM级的,我不能不考虑。 没必要小题大做。 绝大多数时间,算法不是程序员要考虑的事。

#31楼    回复  引用    

2008-01-22 16:59 by 林立 [未注册用户]
写程序离不开算法,说“算法不是程序员要考虑的事。”的人,只能是个coder,编码工人而已。

标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2006-10-16 09:28 编辑过


相关链接: