搜寻所有水仙花数(阿姆斯特朗数)的快速算法(搜完1到60位数字组合耗时2秒)

2024-07-18 更新: 对关键代码做了进一步优化。
2024-07-20 应网友建议: 对广义水仙花数进行了搜寻,见链接:
搜寻广义水仙花数(包含首位为0的情形)


搜寻水仙花数的算法很多,代码也不少。
很少有 fortran 版本的,快到 10 秒以内的算法还没见到。
给出一个搜寻所有水仙花数(阿姆斯特朗数)的快速算法,fortran90 代码。


计算耗时:
在 32 位 win7+3.2GHz 主频系统上,CVF 编译器,1~39 位 1.6 秒,1~60 位 2.9 秒
在 64 位 Linux+3.4GHz 主频系统上,gfortran 编译器,1~39 位 1.0 秒,1~60 位 1.8 秒


算法要点:
A、自定义数据结构 big_integer 表示大整数,构建加法和乘法运算子程序。
B、定义数组 s,预存数字 1 到 9 的 1 到 60 次方及其 0 到 60 倍数值,减少重复计算。
C、采用递归算法,简化代码,将递归层次 n 之外的所有递归参数都定义为公共变量,提高效率;递归枚举数字 9 到 1 的个数组合,总和不超过 nn 位;0 的个数不需要递归求解, 递归结束时自然确定;n 为 1 时,检验是否为水仙花数。
D、提前剪枝策略:
a、递归从 9 开始,到 1 结束;每一层的个数循环从大到小;剩余可选数量记为 nb;
b、检查各位数字幂的和值 tt 的位数,大于 nn 则 cycle;小于 nn,且增加剩余可选数最大幂和值仍不到 nn 位,则退出本层递归;
c、n 大于等于 2 时,根据后续 nb 个数字 s 的最大值,确定 tt 不受影响的高位部分,统计其中已选数字和未选数字的个数;已选数字个数大于选定个数的, 或者 0 到 (n-1) 未选数字个数大于 nb 的,提前剪枝,退出后续递归;根据高位已出现数字调整递归循环的上下限。此为提高效率的关键。


附代码:

! 搜寻所有水仙花数的快速算法
! 2024-07-18

! szw_sh@163.com

! fortran 90
! CVF编译项:DF /Ox
! FPS编译项:FL32 /Ox


module abc                                            ! 公共变量

   type big_integer                                   ! 定义大整数类型
      integer*1 a(63)
      integer*1 digit
   end type

   type(big_integer) s(9,60,0:60)
   type(big_integer) tt(10),t1,t2

   integer*1 b(0:9),b1
   integer*1 nn,mm
   integer*1 nb(10)
   integer*1 a(9)

   character*60 h

end module



use abc                                               ! 主程序
integer*1 n

call cpu_time(x)                                      ! 启动计时,初始化s
call init

do nn=1,60                                            ! 循环nn=1到60,调用递归子程序搜寻

   write(*,'(2x,a,i<(nn+110)/60>)') 'n = ',nn

   call equ(tt(10),0_1)
   nb(10)=nn
   n=9
   b=0
   b1=0
   call cc(n)

   if(nn.eq.39) then                                  ! 输出39位水仙花数后,输出计时
      call cpu_time(x)
      nt=x*1000
      write(*,'(/2x,a,i<log10(nt+0.9)+1>,a/)') 'time = ',nt,' ms'
   end if

end do

call cpu_time(x)
nt=x*1000                                             ! 输出总时耗
write(*,'(/2x,a,i<log10(nt+0.9)+1>,a)') 'time = ',nt,' ms'
write(*,'(2x,a,i2)') 'total = ',mm

end



recursive subroutine cc(n)                            ! 查找水仙花数的递归子程序

use abc
integer*1 n,i,j,k,ii

do i=nb(n+1)-b1+b(n),b(n),-1                          ! 循环+递归,构造9到1的数字个数组合

   a(n)=i
   nb(n)=nb(n+1)-i
   call add(tt(n+1),s(n,nn,i),tt(n))                  ! 计算数字nn次幂的和,只计算变化的位

   if(tt(n)%digit.gt.nn) then                         ! 位数超出nn,循环递减a(n)
      cycle
   else if(tt(n)%digit.lt.nn) then                    ! 位数不达nn,根据后续nb预测仍不达nn的,退出递归
      call add(tt(n),s(n-1,nn,nb(n)),t2)
      if(t2%digit.lt.nn) exit
   end if

   if(n.gt.1) then

      k=s(n-1,nn,nb(n))%digit                         ! 未选数字构成nn次幂和值的最大值产生进位的位置
      if(s(n-1,nn,nb(n))%a(k)+tt(n)%a(k).ge.9) k=k+1  ! 位置k数字之和大于9,一定进位;等于9,可能进位

      do ii=k,nn                                      ! 向高位检查是否存在连续9,避免出现进位漏查情形
         if(tt(n)%a(ii).ne.9) exit                    ! 经测试,有数百万个
      end do

      b=0
      b1=0

      do j=ii+1,nn                                    ! 统计tt高位不变化位置的数字

         k=tt(n)%a(j)
         b(k)=b(k)+1
         
         if(k.ge.n) then                              ! 已选数字,若tt中的大于已选,提前退出统计
            if(b(k).gt.a(k)) exit
         else                                         ! 0(n-1)的未选数字,若tt中的大于剩余未选个数的,提前退出
            b1=b1+1
            if(b1.gt.nb(n)) exit
         end if

      end do

      if(j.le.nn) cycle                               ! 判定tt存在已选、未选数字的矛盾的情形,提前剪枝

      call cc(n-1_1)                                  ! 继续递归调用

   else                                               ! 递归结束

      b=0                                             ! 统计tt各数字的数量
      b1=0

      do j=1,nn

         k=tt(n)%a(j)
         b(k)=b(k)+1

         if(k.gt.0) then
            if(b(k).gt.a(k)) exit                     ! 若超出已选的数量,矛盾,提前跳出
         else
            b1=b1+1
            if(b1.gt.nb(1)) exit
         end if

      end do

      if(j.le.nn) cycle                               ! 若有矛盾,跳过

      call chr(tt(n),h)                               ! tt转为字符串
      if(h.eq.'0') cycle                              ! 结果为0,非正整数,不计入水仙花数
      write(*,'(2x,a)') trim(h)                       ! 输出找到的水仙花数
      mm=mm+1                                         ! 水仙花数计数

   end if

end do

end



subroutine equ(aa,n)                                  ! 大整数赋值子程序,aa=n,n<10

use abc
type(big_integer) aa
integer*1 n

aa%a=0
aa%digit=1
aa%a(1)=n

end



subroutine mul(aa,bb,n)                               ! 大整数乘法子程序,bb=aa*n,n<100

use abc
type(big_integer) aa,bb
integer*2 k,k1,kn
integer*1 i,ii,n

if(n.eq.0.or.aa%digit.eq.1.and.aa%a(1).eq.0) then     ! n=0 或者 aa=0
   call equ(bb,0_1)
   return
else if(n.eq.1) then                                  ! n=1
   bb=aa
   return
end if

bb%a=0                                                ! 初始化bb,进位数k1
k1=0
ii=aa%digit
kn=n

do i=1,ii+3                                           ! 分段乘法,进位
   k=aa%a(i)*kn+k1
   bb%a(i)=mod(k,10)
   k1=k/10
end do

bb%digit=ii                                           ! 确定位数
if(bb%a(ii+1).ne.0) bb%digit=ii+1
if(bb%a(ii+2).ne.0) bb%digit=ii+2                     ! +2是针对9^60*60的最大情形

end



subroutine add(aa,bb,cc)                              ! 大整数加法子程序,cc=aa+bb

use abc
type(big_integer) aa,bb,cc
integer*1 k,k1,i,ii

cc%a=0                                                ! 初始化cc,进位数k1
k1=0
ii=max(aa%digit,bb%digit)

do i=1,ii+1                                           ! 分段加法,进位
   k=aa%a(i)+bb%a(i)+k1
   cc%a(i)=k
   k1=0
   if(k.ge.10) then
      cc%a(i)=k-10
      k1=1
   end if
end do

cc%digit=ii                                           ! 确定位数
if(cc%a(ii+1).gt.0) cc%digit=ii+1

end


subroutine chr(aa,hh)                                 ! 大整数转字符串子程序,hh=aa

use abc
character*(*) hh
type(big_integer) aa
integer*1 j

hh=' '

do j=1,nn
   hh(nn-j+1:nn-j+1)=char(48+aa%a(j))
end do

end



subroutine init

use abc                                               ! 初始化数组s
type(big_integer) s1,s2
integer*1 i,j,k

do i=1,9
   call equ(s1,1_1)
   do j=1,60
      call mul(s1,s2,i)
      s1=s2
      do k=0,60
         call mul(s1,s(i,j,k),k)
      end do
   end do
end do

end


附计算结果:

  n = 1
  9
  8
  7
  6
  5
  4
  3
  2
  1
  n = 2
  n = 3
  407
  371
  370
  153
  n = 4
  9474
  8208
  1634
  n = 5
  93084
  92727
  54748
  n = 6
  548834
  n = 7
  9926315
  9800817
  4210818
  1741725
  n = 8
  88593477
  24678051
  24678050
  n = 9
  912985153
  534494836
  472335975
  146511208
  n = 10
  4679307774
  n = 11
  94204591914
  82693916578
  49388550606
  44708635679
  42678290603
  40028394225
  32164049651
  32164049650
  n = 12
  n = 13
  n = 14
  28116440335967
  n = 15
  n = 16
  4338281769391371
  4338281769391370
  n = 17
  35875699062250035
  35641594208964132
  21897142587612075
  n = 18
  n = 19
  4929273885928088826
  4498128791164624869
  3289582984443187032
  1517841543307505039
  n = 20
  63105425988599693916
  n = 21
  449177399146038697307
  128468643043731391252
  n = 22
  n = 23
  35452590104031691935943
  28361281321319229463398
  27907865009977052567814
  27879694893054074471405
  21887696841122916288858
  n = 24
  239313664430041569350093
  188451485447897896036875
  174088005938065293023722
  n = 25
  4422095118095899619457938
  3706907995955475988644381
  3706907995955475988644380
  1553242162893771850669378
  1550475334214501539088894
  n = 26
  n = 27
  177265453171792792366489765
  174650464499531377631639254
  128851796696487777842012787
  121270696006801314328439376
  121204998563613372405438066
  n = 28
  n = 29
  23866716435523975980390369295
  19008174136254279995012734741
  19008174136254279995012734740
  14607640612971980372614873089
  n = 30
  n = 31
  2309092682616190307509695338915
  1927890457142960697580636236639
  1145037275765491025924292050346
  n = 32
  17333509997782249308725103962772
  n = 33
  186709961001538790100634132976991
  186709961001538790100634132976990
  n = 34
  1122763285329372541592822900204593
  n = 35
  12679937780272278566303885594196922
  12639369517103790328947807201478392
  n = 36
  n = 37
  1219167219625434121569735803609966019
  n = 38
  12815792078366059955099770545296129367
  n = 39
  115132219018763992565095597973971522401
  115132219018763992565095597973971522400

  time = 1630 ms
  (gfortran 1020 ms)

  n = 40
  n = 41
  n = 42
  n = 43
  n = 44
  n = 45
  n = 46
  n = 47
  n = 48
  n = 49
  n = 50
  n = 51
  n = 52
  n = 53
  n = 54
  n = 55
  n = 56
  n = 57
  n = 58
  n = 59
  n = 60

  time = 2850 ms
  (gfortran 1750 ms)
  total = 88
  
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值