ExcelHome技术论坛

 找回密码
 免费注册

QQ登录

只需一步,快速开始

快捷登录

搜索
EH技术汇-专业的职场技能充电站 妙哉!函数段子手趣味讲函数 Excel服务器-会Excel,做管理系统 Excel Home精品图文教程库
HR薪酬管理数字化实战 Excel 2021函数公式学习大典 Excel数据透视表实战秘技 打造核心竞争力的职场宝典
300集Office 2010微视频教程 数据工作者的案头书 免费直播课集锦 ExcelHome出品 - VBA代码宝免费下载
用ChatGPT与VBA一键搞定Excel WPS表格从入门到精通 Excel VBA经典代码实践指南
查看: 93606|回复: 247

[原创] 史上最酷的Excel VBA 组合算法 代码

  [复制链接]

TA的精华主题

TA的得分主题

发表于 2013-7-4 22:02 | 显示全部楼层 |阅读模式
本帖已被收录到知识树中,索引项:其他结构和算法
组合算法研究的人也很多了。

最简单的是直接For……Next 套用,需要抽n个元素就套用n次……但这样的代码并没有技术含量。
并且因为无法做到自动适应各种m,n数量的变化而不是很理想。

因此,VBA 代码进行任意m、n值的组合算法,也是很有意义的。

……
各种研究结果中,最慢的是二进制算法。因为计算冗余量很大。
普通就有各种数组循环(For……Next 或 Do……Loop)
但由于个人代码写法不同,结果差异也不小。

还有一类是递归算法。
但由于递归算法需要反复调用递归过程等,本质上应该没有直接VBA内数组循环速度快。


…………
其实我自己就写出了很多种不同的版本,最后达到了速度最快,至少我目前还没有发现比我的代码速度更快的组合算法。

更由于我认为我的算法已经是很高效了,达到了极限,无法更加进一步提高效率、缩短计算时间了,
所以今天拿出来分享。


上附件
附件是在Zamyi大侠的附件上,加入了我的代码做了比较。
http://club.excelhome.net/thread-746111-1-1.html
其实Zamyi的代码已经是比一般的代码快很多了(快1倍以上?)

但我的组合算法代码运行速度,比Zamyi大侠的还要快 将近 70%







补充内容 (2014-12-20 21:46):
2014年12月20日补充:最新高效代码在135楼:http://club.excelhome.net/forum. ... 055&pid=8004615

评分

14

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2013-7-4 22:04 | 显示全部楼层
本帖最后由 香川群子 于 2013-7-8 11:52 编辑

刚才附件加不上啊。

附件做了一些更新,把【组合过程】、【组合并生成组合结果】这两种的运算时间都算出来了。

VBA Combin by kagawa.rar (34.1 KB, 下载次数: 1485)

VBA组合Combin(50,5).jpg

VBA组合Combin(26,13).jpg


TA的精华主题

TA的得分主题

 楼主| 发表于 2013-7-4 22:05 | 显示全部楼层
本帖最后由 香川群子 于 2013-7-8 11:50 编辑

公布代码,有兴趣可以一起探讨:
  1. '=================================================================
  2. '史上最酷的Excel VBA组合算法 【香川Do循环组合】代码特点:
  3. '
  4. '每一次Do……Loop循环都高效地改变一次数组a中组合状态
  5. '用i变量作为元素 1 to m 中的位置指针
  6. '用j变量作为组合结果 1 to n 的数组a位置指针
  7. '用a(j) = i 变量直接赋值方法,比a(j) = a(j) + 1 方式的数组引用赋值要更快一些。
  8. '用If j=n 作为末位循环以及 j-1退位,提高效率。
  9. '并且增加了用 If i = m - n + j 作为退位条件,进一步提高了效率。
  10. '  因为如果j后面状态相同就不用再作计算、变更了所以效率更高一些。
  11. '=================================================================

  12. Sub Combin_kagawa()
  13.     Dim i&, j&, k&, l&, m&, n&, s$, tms#
  14.     tms = Timer
  15.    
  16.     m = [a1].End(4).Row  '获取原始数据最大行数m
  17.     sj0 = [a1].Resize(m) '获取原始数据
  18.     For i = 1 To m
  19.         If Len(sj0(i, 1)) > l Then l = Len(sj0(i, 1)) '找到原始数据各元素中Len的最大值以便统一字符串长度。
  20.     Next
  21.    
  22.     n = [b1] '获取组合抽取个数n
  23.     s = String(n * l, " ") '字符串变量s初始化为n*l长度的空白字符串s,以便在下面Do循环中进行Mid置换处理
  24.    
  25.     ReDim sj$(1 To m) '定义数组sj 写入统一字符长度的原始数据m个元素。定义为字符型数组可提高速度
  26.     For i = 1 To m
  27.         sj(i) = Right(s & sj0(i, 1), l) '原始数据调整至同样字符长度
  28.     Next
  29.    
  30.     If n = m Then
  31.         s = ""
  32.         For j = 1 To n
  33.             s = s & sj(j) 'n=m 时只有一种组合且需提取全部,所以可直接生成字符串结果s
  34.         Next
  35.     End If
  36.    
  37. '    k = Application.Combin(m, n) '计算组合结果总数
  38. '    ReDim jg$(1 To k, 1 To 1) '定义储存结果的数组jg
  39. '    k = 0
  40.    
  41.    
  42.     '下面才是组合算法的核心部分
  43.     ReDim a&(1 To n) '定义数组a存放1 to n各种组合状态位置的信息
  44.     a(n) = m: a(1) = 0 '数组a初始化(仅需对1和n两个位置进行初始化赋值,不需要赋值全部就能正常计算)
  45.    
  46.     '循环变量初始化
  47.     i = 0 'i含义为当前抽取到元素在原始数据中的序号
  48.     j = 1 'j含义为当前抽取到元素在组合结果中的位置序号
  49.     Do
  50.         i = i + 1 '从1 to m 依次向后抽取第i个元素
  51.         a(j) = i '抽取到的元素序号i写入当前组合结果状态数组a中对应j位置
  52.         Mid(s, (j - 1) * l + 1, l) = sj(i) '每循环一次都能对不同的状态位作一次有效的元素递增
  53.         '上面部分代码看上去特别简单,但这个就是香川组合算法高效的原因所在
  54.         
  55.         If j = n Then '当前抽取元素的组合状态位置到达末位n位置时
  56.             k = k + 1
  57. '            jg(k, 1) = s '输出结果
  58.             If i = m Then If j = 1 Then Exit Do Else j = j - 1: i = a(j) '退出或继续 解释略
  59.         ElseIf i = m - n + j Then '当前抽取元素的序号i已经满足该位置组合上限m-n+j时
  60.             k = k + 1
  61. '            jg(k, 1) = s '输出结果
  62.             If j = 1 Then Exit Do Else j = j - 1: i = a(j) '退出或继续 解释略
  63.         Else
  64.             j = j + 1 '不满足上述条件时,抽取元素位置j向后移动1
  65.         End If
  66.     Loop
  67.     '组合计算结束
  68.     [g65536].End(3).Offset(1) = Format(Timer - tms, "0.000")
  69.     [f65536].End(3).Offset(1) = "Combin(" & m & "," & n & ")= " & k
  70.     [e65536].End(3).Offset(1) = "kagawa"
  71.     If k < 65536 Then [d:d] = "": [d1].Resize(k) = jg '输出结果到工作表
  72. End Sub
复制代码
VBA Combin by kagawa.rar (34.1 KB, 下载次数: 1820)

点评

现在想想,其实这才是最优的作品,变换思路,只使用了一个Do循环,匪夷所思!!!  发表于 2014-10-21 20:53

评分

2

查看全部评分

TA的精华主题

TA的得分主题

发表于 2013-7-4 22:30 | 显示全部楼层
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助
确实很快,了不起,祝贺。

TA的精华主题

TA的得分主题

发表于 2013-7-4 22:41 | 显示全部楼层
{:soso_e179:}{:soso_e177:}

崇拜 学习

TA的精华主题

TA的得分主题

 楼主| 发表于 2013-7-4 22:43 | 显示全部楼层
这个是我的组合算法的核心部分代码

想要研究的话,看懂这部分就OK了。
  1. Function Combin_kagawa(m&, n&)
  2.     Dim i&, j&, k&, r&
  3.     tms = Timer
  4.    
  5.     ReDim a&(1 To n)
  6.     If n = m Then For j = 2 To n: a(j) = j: Next
  7.    
  8.     i = 0: j = 1: k = 0
  9.     Do
  10. '        r = r + 1 '统计循环计算总次数 实际计算时不需要
  11.         i = i + 1: a(j) = i '使用i变量,比单纯a(j) = a(j) + 1 方法赋值要快一些
  12.         
  13.         If j = n Then
  14.             k = k + 1
  15.             If i = m Then If j = 1 Then Exit Do Else j = j - 1: i = a(j)
  16.         ElseIf i = m - n + j Then
  17.             k = k + 1
  18.             If j = 1 Then Exit Do Else j = j - 1: i = a(j)
  19.         Else
  20.             j = j + 1
  21.         End If
  22.     Loop
  23.     Combin_kagawa = Format(Timer - tms, "0.000s") & " Do  Combin(" & m & "," & n & ")= " & k
  24.    
  25. End Function
复制代码

TA的精华主题

TA的得分主题

 楼主| 发表于 2013-7-4 22:47 | 显示全部楼层
不过,本质上速度最快的,无疑是简单的For……Next循环。

以下专门计算抽取5个元素的组合代码,单纯跑一遍(只进行位置组合,不整理组合结果)只要 0.06秒
但是如果需要整理结果,用直接字符串连接法就需要6秒了。 → 但我的组合算法就只要0.5秒
  1. Sub AutoCombin_5()
  2.     tms = Timer
  3.     Dim k&, m&, n&, s$
  4.     n = 5: m = 50
  5.    
  6.     Dim i1&, i2&, i3&, i4&, i5&
  7.     For i1 = 1 To m - 4
  8.     For i2 = i1 + 1 To m - 3
  9.     For i3 = i2 + 1 To m - 2
  10.     For i4 = i3 + 1 To m - 1
  11.     For i5 = i4 + 1 To m - 0
  12.       k = k + 1
  13. '      s = i1 & i2 & i3 & i4 & i5
  14.     Next i5, i4, i3, i2, i1
  15.     MsgBox k & vbCr & Format(Timer - tms, "0.000s")

  16. End Sub
复制代码

TA的精华主题

TA的得分主题

 楼主| 发表于 2013-7-4 22:52 | 显示全部楼层
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
标准For……Next循环计算组合,组合结果也用Mid方法进行置换的话,速度也还是相当地快,
Combin(50,5)=2118760 只要0.28秒
  1. Sub AutoCombin_Mid_5()
  2.    
  3.     tms = Timer
  4.     Dim k&, l&, m&, n&, s$
  5.     n = 5: m = [a1].End(4).Row
  6.     'AC = WorksheetFunction.Combin(m, n)
  7.     'ReDim jg(1 To AC, 0 To n)
  8.    
  9.     sj0 = [a1].Resize(m)
  10.     For i = 1 To m
  11.       If Len(sj0(i, 1)) > l Then l = Len(sj0(i, 1))
  12.     Next
  13.     s = String(l, " ")
  14.     ReDim sj$(1 To m)
  15.     For i = 1 To m
  16.       sj(i) = Right(s & sj0(i, 1), l)
  17.     Next
  18.     s = ""
  19.     For i = 1 To n
  20.       s = s & sj(i)
  21.     Next
  22.    
  23.     Dim i1%, i2%, i3%, i4%, i5%
  24.     For i1 = 1 To m - 4
  25.       Mid(s, 1, l) = sj(i1)
  26.     For i2 = i1 + 1 To m - 3
  27.       Mid(s, l * 1 + 1, l) = sj(i2)
  28.     For i3 = i2 + 1 To m - 2
  29.       Mid(s, l * 2 + 1, l) = sj(i3)
  30.     For i4 = i3 + 1 To m - 1
  31.       Mid(s, l * 3 + 1, l) = sj(i4)
  32.     For i5 = i4 + 1 To m - 0
  33.       Mid(s, l * 4 + 1, l) = sj(i5)
  34.       k = k + 1
  35.     '  jg(k, 0) = s
  36.     Next i5, i4, i3, i2, i1
  37.     MsgBox k & vbCr & Format(Timer - tms, "0.000s")
  38.    
  39. End Sub
复制代码

TA的精华主题

TA的得分主题

 楼主| 发表于 2013-7-4 23:09 | 显示全部楼层
另外,我的递归代码速度也很快……Combin(50,5)=2118760 只要0.6秒

我的递归代码看上去也非常简洁:
  1. Dim sj$(), jg$(), m&, n&, k&
  2. Sub 组合()
  3.     tms = Timer: m = [a1].End(4).Row: sj0 = [a1].Resize(m): n = [b1]
  4.     ReDim sj$(1 To m): For i = 1 To m: sj(i) = sj0(i, 1): Next
  5. '    ReDim jg$(1 To Application.Combin(m, n), 1 To 1)
  6.     k = 0: Call dgZH("", 0, 1)
  7.     MsgBox k & vbCr & Format(Timer - tms, "0.000s")
  8. End Sub
  9. Sub dgZH(s$, i&, t&)
  10.     Dim j&, ss$
  11.     For j = i + 1 To m
  12.         If t = n Then
  13.             k = k + 1
  14.             ss = s & sj(j)
  15. '            jg(k, 1) = s & sj(j)
  16.         Else
  17.             Call dgZH(s & sj(j), j, t + 1)
  18.         End If
  19.     Next
  20. End Sub
复制代码

TA的精华主题

TA的得分主题

 楼主| 发表于 2013-7-7 12:48 | 显示全部楼层
我的递归算法,也是最棒的。

尤其是,当Combin(m,n)中,抽取元素个数n远小于元素总数m时,我的递归算法效率相当高。

因为,我在代码中使用了组合元素个数=n时就直接循环输出,而不需要再走一遍递归的处理方法,所以效率提高不少。




您需要登录后才可以回帖 登录 | 免费注册

本版积分规则

关闭

最新热点上一条 /1 下一条

手机版|关于我们|联系我们|ExcelHome

GMT+8, 2024-4-19 07:55 , Processed in 0.051127 second(s), 15 queries , Gzip On, MemCache On.

Powered by Discuz! X3.4

© 1999-2023 Wooffice Inc.

沪公网安备 31011702000001号 沪ICP备11019229号-2

本论坛言论纯属发表者个人意见,任何违反国家相关法律的言论,本站将协助国家相关部门追究发言者责任!     本站特聘法律顾问:李志群律师

快速回复 返回顶部 返回列表