练手程序2006-9-9

  这个题目是很早之前我在CSDN里看到的,乍看之下好像比较简单,当然,尽管我写ASM,但我的思路一向是用C来完成的,所以一时觉得这个程序好写。当我分析了一阵以后,脑子就乱了,这一乱,我就不敢下手了。因为传说是google的面试题,于是保存着这个题目等以后来完成。

  前两天跟jailu写程序,不知道是脑子突然有点开窍还是怎么地,今天早上花了2个小时把C的demo与ASM的demo都写出来了,现在就差实践了。2006-9-9 13:31

  实践证明我真的是很笨,demo程序有问题,后来改了我一下午。又写了几个版本的程序,花了一天多的时间,虽然写出来的程序效率高了很多,但也是我想了好久的了,如果是面试题的话,我想我也只能拍屁股走人,把题拿回家去慢慢想喽。

  这个题目的英文原题是:Consider a function which, for a given whole number n, returns the number of ones required when writing out all numbers between 0 and n. ;For example, f(13)=6. Notice that f(1)=1. What is the next largest n such that f(n)=n?  翻译过来大体是这样:有一个整数n,写一个函数f(n),返回0到n之间出现的"1"的个数。比如f(13)=6,现在f(1)=1,问下一个最大的f(n)=n的n是什么?为什么f(13)=6, 因为1,2,3,4,5,6,7,8,9,10,11,12,13.数数1的个数,正好是6.请大家写出自己的算法,并统计一些在你机器上计算出1111111110的时间。语言不限,随便了。 

  我根据两个情况写个两种程序:一:针对单个用户输入计算0到N的1出现的个数。二:应题目要求,只计算到1111111110的所有符合情况的数字并输出。  这两个程序在各自的领域工作地很好,但算法不一样,如果互换算法的话,效率极其低下。

  这里感谢一下jailu的提醒,否则我就差点要放弃ASM了,他跟我说:f(n)=f(n-1)+F(n),我才明白,我不可以把前面一个算法应用到第二个算法上去,从而做了无数的无用功。

  因为是随手的程序,所以写法上很不严谨,程序很脆弱,很不好意思,呵呵。

  以下是第一个算法:图形界面,计算单个值。因为有针对某些数如1000等做特别处理,所以某些大数的计算反而非常地迅速。数值的计算时间不定,但好像一般在10秒内完成统计。第一行输出信息显示统计结果,第二行输出信息显示计算时间。  又因为只使用了DD是32位的,就是说,如果输入的数字大于2147483647(2^31),会导致程序崩溃。但这个数已经足够大到算到1111111110这个最大值了。

主程序ex.asm:

 


.586 
.model flat
,
stdcall 
option casemap:none 

include windows.inc 

include user32.inc 
include kernel32.inc 
include shell32.inc
include    masm32.inc

includelib user32.lib 
includelib kernel32.lib 
includelib shell32.lib
includelib    masm32.lib

include    C:masm32macrosMACROS.ASM

DLG_MAIN    equ    
1000


.const
xten    dd    
1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000

.data
fmt_num    db    
" %d ",0
.data?
hInstance    dd    ?
hWinMain    dd    ?
tmpstr    db    
128 dup    (?)
x    dd    ?
time    dd    ?



.code

_fun    proc    @x
    
    LOCAL    @sum
    
    mov    ecx
,
@x
    .if    ecx > 
0

        mov    eax
,ecx
        shl    ecx
,1

        shl    ecx
,1
        mov    edx
,xten[ecx]
        mov    @sum
,edx
        dec    eax
        invoke    _fun
,
eax
        mul    xten
[04h]

        add    eax
,@sum
        ret
    .endif
    mov    eax
,1

    ret

_fun endp


_c    proc    @z
    
    LOCAL    @sum
,@s,@y,@xten,@x
    push    x
    pop    @x
    
    mov    eax
,
@z
    .if    eax 
== 0

        ret
    .elseif    eax < 
10
        mov    eax
,1
        ret
    .endif
@@:    mov    edx
,@x
    shl    edx
,1

    shl    edx
,1
    mov    ecx
,xten[edx]
    
    mov    @xten
,ecx
    dec    @x
    xor    edx
,
edx
    mov    @sum
,
edx
    mov    eax
,
@z
    div    ecx
    mov    @s
,eax    ;

    mov    @y,edx    ;余数
    
    .if    eax 
== 0

        jmp    @B
    .elseif    eax 
== 1
        mov    @sum
,edx
        inc    @sum
        invoke    _c
,
edx
        add    @sum
,
eax
        dec    @xten
        invoke    _fun
,
@x
        add    eax
,
@sum
        ret
    .else
        xor    edx
,
edx
        mov    ecx
,
@xten
        mul    ecx
        dec    eax
        inc    @x
        invoke    _c
,
eax
        mov    @sum
,
eax
        invoke    _c
,
@y
        add    eax
,
@sum
        ret
    .endif
    ret

_c endp


_main proc
    pushad
    push    
9

    pop    x
    invoke    GetTickCount
    mov    time
,eax
    invoke    GetDlgItemInt
,hWinMain,4211,NULL,
TRUE
    invoke    _c
,
eax
    invoke    SetDlgItemInt
,hWinMain,4212,eax,
TRUE
    invoke    GetTickCount
    sub    eax
,
time
    invoke    SetDlgItemInt
,hWinMain,4213,eax,
TRUE
    popad
    ret    

    push    eax
    pushad
    invoke    wsprintf
,addr tmpstr,addr fmt_num,
eax
    invoke    MessageBox
,hWinMain,addr tmpstr,NULL,
MB_OK
    popad
    pop    eax
_main endp

_ProcDlgMain    proc    uses ebx edi esi hWnd
,wMsg,wParam,
lParam

        LOCAL    @szBuffer
[256]
:byte
        mov    eax
,
wMsg
        .if    eax 
==
 WM_CLOSE
            invoke    EndDialog
,hWnd,
NULL
        .elseif    eax 
==
 WM_INITDIALOG
            push    hWnd
            pop    hWinMain
        .elseif    eax 
==
 WM_COMMAND
            mov    eax
,
wParam
            movzx    eax
,
ax
            .if    eax 
==
    IDOK
                invoke    _main
            .endif
        .else
            mov    eax
,
FALSE
            ret
        .endif
        mov    eax
,
TRUE
        ret

_ProcDlgMain    endp

start:
    invoke    GetModuleHandle
,
NULL
    mov    hInstance
,
eax
    invoke    DialogBoxParam
,hInstance,DLG_MAIN,NULL,offset _ProcDlgMain,
NULL
    invoke    ExitProcess
,
NULL

    end    start

主界面:ex.rc

#include        <C:masm32include esource.h>

#define    DLG_MAIN        1000


DLG_MAIN DIALOG 
190130,145,56
STYLE DS_MODALFRAME 
| WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU |WS_MINIMIZEBOX
CAPTION 
"练手程序"

FONT 
10"宋体"
{
 GROUPBOX 
"请输入数字",-1,2,5,138,24

 EDITTEXT 
421110,144411,ES_NUMBER | ES_RIGHT
 ltext    
" ",4212,6,32,140,12
 
 ltext    
" ",4213,6,42,140,12

 
 DEFPUSHBUTTON    
"判断(&C)",IDOK,61,12,36,14
 
}


 

第二种算法:先是采用jailu说的穷举算法,后来经过一步步的改进,形成现在的情况,比最原始的穷举算法,速度提高了1分多钟,采用字符界面输出。  现在就是奇怪的是,竟然传递参数到堆栈反而比使用寄存器快?!

主程序ex.asm

 

 


.586  
.model flat
, stdcall 
option casemap:none 

include windows.inc 

include user32.inc 
include kernel32.inc 
include shell32.inc
include    masm32.inc

includelib user32.lib 
includelib kernel32.lib 
includelib shell32.lib
includelib    masm32.lib

include    C:masm32macrosMACROS.ASM

max        equ    
1111111110
max1    equ    
2600001
max2    equ    
1111111110

.const
xten    dd    
1 , 10 , 100 , 1000 , 10000 , 100000 , 1000000 , 10000000 , 100000000 , 1000000000

.data
fmt_num    db    
"  %d  " , 0
fmt_my    db    
" f(%d)=%d " , 10 , 0
fmt_time    db    
" 任务结束,共耗时 %d ms . " , 0
fmt_t    db    
"         耗时 %d ms. " , 10 , 0
.data?
hInstance    dd    ?
hWinMain    dd    ?
tmpstr    db    
128  dup    (?)
x    dd    ?
time    dd    ?



.code

_f    proc    @z
    LOCAL    @flag
    mov    eax
, @z

    xor    ecx
, ecx
    xor    edx
, edx
    mov    @flag
, edx
    div    edi
    .if    edx 
==   1
        inc    ecx
        mov    @flag
, edi
    .endif
    .while    eax > 
0
        xor    edx
, edx
        div    edi
        .if    edx 
==   1
            inc    ecx
        .endif
    .endw
    mov    eax
, ecx
    add    eax
, esi
    .if    (@flag 
==   1 ) && (ecx  ==   1 )
        mov    ecx
, ebx
        add    ebx
, 8
        .if    (eax >
=  ecx)&&(eax < =  ebx)
            push    eax
            invoke    wsprintf
, addr tmpstr , addr fmt_my , eax , eax
            invoke    StdOut
, addr tmpstr
            invoke    GetTickCount
            sub    eax
, time
            invoke    wsprintf
, addr tmpstr , addr fmt_t , eax
            invoke    StdOut
, addr tmpstr
            pop    eax
        .endif
    .endif
    ret

_f endp

_main proc
    pushad
    push    
9
    pop    x
    
    invoke    GetTickCount
    mov    time
, eax
    xor    ebx
, ebx
    xor    esi
, esi
    mov    edi
, 10
    .while    ebx <
=  max
        invoke    _f
, ebx
        mov    esi
, eax
        .if    eax 
==  ebx
            invoke    wsprintf
, addr tmpstr , addr fmt_my , ebx , eax
            invoke    StdOut
, addr tmpstr
            invoke    GetTickCount
            sub    eax
, time
            invoke    wsprintf
, addr tmpstr , addr fmt_t , eax
            invoke    StdOut
, addr tmpstr
        .endif
        inc    ebx
    .endw
    
    invoke    GetTickCount
    sub    eax
, time
    invoke    wsprintf
, addr tmpstr , addr fmt_time , eax
    invoke    StdOut
, addr tmpstr
    popad
    ret    

_main endp

start:
    invoke    GetModuleHandle
, NULL
    mov    hInstance
, eax
    invoke    _main
    invoke    ExitProcess
, NULL

    end    start

 

 以下是一些输出信息:

 

12:27 2006-9-10:采用了有针对性的跳步算法,程序速度又提高了不少,哇哈哈。
  原理是根据个位数如果为1且整个数也只有个位数为1,往前跳8,因为接下来的9个数f(n)=X,f(n+1)=X,…f(n+8)=X,既然是一样的,那就

跳过,不用算了,0~100,少测了72次,以些类推,时间节省。当然,同时因为增加了判断,所以时间上也有消耗,两者折和,时间上还是有节

省,所以我就改进了算法,结果果然时间上大有提高,算到最大数,比原来的算法快了50秒左右!

以下摘录一些结果:0--260001
f(0)=0  耗时 0 ms.
f(1)=1 耗时 0 ms.
f(199981)=199981  耗时 30 ms...
f(199984)=199984  耗时 40 ms.........
f(1599981)=1599981 耗时 310 ms...
f(1599984)=1599984 耗时 320 ms...
f(1599987)=1599987  耗时 330 ms....
f(2600000)=2600000 耗时 560 ms.
f(2600001)=2600001 耗时 560 ms.
任务结束,共耗时 570 ms .

----以下是运行到最大数的结果:(部分结果根据时间不同)
f(0)=0 0 ms.
f(1)=1 0 ms.
f(199981)=199981 30 ms.
f(199982)=199982 40 ms.
f(199983)=199983 50 ms..
f(199985)=199985 60 ms.
f(199986)=199986 70 ms..
f(199988)=199988 80 ms.
f(199989)=199989 90 ms.
f(199990)=199990 100 ms.
f(200001)=200001 110 ms.
f(1599981)=1599981 381 ms.
f(1599982)=1599982 391 ms.
f(1599983)=1599983 401 ms..
f(1599985)=1599985 411 ms.
f(1599986)=1599986 421 ms..
f(1599988)=1599988 431 ms.
f(1599989)=1599989 441 ms..
f(2600000)=2600000 671 ms.
f(2600001)=2600001 681 ms.
f(13199998)=13199998 3155 ms.
f(35000000)=35000000 8582 ms..
f(35199981)=35199981 8643 ms..
f(35199983)=35199983 8653 ms.
f(35199984)=35199984 8663 ms..
f(35199986)=35199986 8683 ms...
f(35199989)=35199989 8703 ms.....
f(117463825)=117463825 30524 ms.
f(500000000)=500000000 141434 ms.........................
f(502600000)=502600000 142205 ms..
f(513199998)=513199998 145239 ms.
f(535000000)=535000000 151448 ms.
f(535000001)=535000001 151448 ms...
f(535199984)=535199984 151518 ms.....
f(535199989)=535199989 151528 ms...
f(535200001)=535200001 151528 ms.
f(1111111110)=1111111110 349623 ms.
任务结束,共耗时 349623 ms .

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值