Excel精英培训网

 找回密码
 注册
数据透视表40+个常用小技巧,让你一次学会!
楼主: hyqzzs

[已解决]枚举不重复排列的问题,我的方法效率低,求指点

[复制链接]
发表于 2012-11-7 22:26 | 显示全部楼层
本帖最后由 wcymiss 于 2012-11-7 22:36 编辑
  1. Sub 全排列()
  2.     Dim n As Integer, arr(), brr()
  3.     Dim d() As Integer, e() As Integer, Temp As String
  4.     Dim i As Integer, j As Integer, k As Integer, m As Long
  5.     Dim t
  6.     t = Timer
  7.     n = Cells(Rows.Count, 1).End(xlUp).Row
  8.     arr = Application.Transpose(Range("a1:a" & n).Value)
  9.     m = 1
  10.     For i = 1 To n: m = m * i: Next '求出总个数
  11.     ReDim brr(1 To m, 1 To 1), d(1 To n), e(1 To n)
  12.     For i = 1 To n: d(i) = i: e(i) = -1: Next '初始化
  13.     m = 0
  14. line: m = m + 1: brr(m, 1) = Join(arr, "")
  15.     k = 0
  16.     For i = n To 2 Step -1
  17.         d(i) = d(i) + e(i)
  18.         If d(i) = i Then
  19.             e(i) = -1
  20.         ElseIf d(i) = 0 Then
  21.             k = k + 1: e(i) = 1
  22.         Else
  23.             j = d(i) + k '定位
  24.             Temp = arr(j): arr(j) = arr(j + 1): arr(j + 1) = Temp '交换位置
  25.             GoTo line
  26.         End If
  27.     Next
  28.     MsgBox Timer - t
  29.     Range("e:e").ClearContents
  30.     Range("e1").Resize(UBound(brr), 1) = brr
  31. End Sub
复制代码

评分

参与人数 1 +18 收起 理由
xdwy81129 + 18 很给力!,很很很厉害

查看全部评分

excel精英培训的微信平台,每天都会发送excel学习教程和资料。扫一扫明天就可以收到新教程
回复

使用道具 举报

 楼主| 发表于 2012-11-8 17:25 | 显示全部楼层
wcymiss 发表于 2012-11-7 22:26

呵呵,香川的代码我还没看懂,又来了个高手,保存,再研究。谢谢指点
回复

使用道具 举报

发表于 2012-12-23 19:49 | 显示全部楼层
wcymiss 发表于 2012-11-7 22:26

吴姐你好!

你的全排列算法代码我看了,很牛。

算法原理我理解是通过【两两置换】来产生所有不同的排列结果。

但是具体算法实现方法,即位置参数的调整算法,保证既不重复也无遗漏的算法原理,到现在也没有理解。


不过,受【两两置换】方法思路的启发,我直接改写成了递归算法代码,结果还算成功,计算时间反而有所提高。
  1. Dim sj, jg, l%, m%, k&
  2. Sub 递归排列2()

  3. m = [a1].End(4).Row
  4. sj = [a1].Resize(m)
  5. '以上为获取A列元素作为待排列的原始数据到数组sj

  6. l = Len(sj(1, 1))
  7. For i = 2 To m
  8. If Len(sj(i, 1)) > l Then l = Len(sj(i, 1))
  9. Next
  10. ' l = l + 1
  11. For i = 1 To m
  12. sj(i, 1) = String(l - Len(sj(i, 1)), " ") & sj(i, 1)
  13. Next
  14. '递归函数将直接传递各种排列的字符串结果,因此以上过程是为了统一原始数据字符串长度。

  15. AP = WorksheetFunction.Permut(m, n)
  16. ReDim jg(AP, 0) '定义储存结果的数组jg

  17. k = 0: tms = Timer
  18. Call pldg2("", 0, 1) '开始递归过程

  19. MsgBox Format(Timer - tms, "0.000s")
  20. [d:d] = ""
  21. [d1].Resize(AP) = jg '输出结果

  22. End Sub

  23. Sub pldg2(s$, i%, t%)
  24.     If t > m Then jg(k, 0) = s: k = k + 1 '递归到指定个数m时把排列结果写入结果数组jg
  25.     If t <= m Then Call pldg2(s & sj(t, 1), t, t + 1) '如果不到指定个数m时继续调用递归过程计算以增加元素个数
  26.     If i > 1 Then '抽取元素个数超过1个时(两个以上时),就从末位开始降序进行逐个【两两交换】
  27.         If i > 2 Then t1 = Mid(s, 1, (i - 2) * l) Else t1 = ""  '计算确定交换前字符串t1
  28.         t2 = Mid(s, (i - 1) * l + 1, l) '计算确定要向前交换的字符t2
  29.         t3 = Mid(s, (i - 2) * l + 1, l) '计算确定要向后交换的字符t3
  30.         t4 = Mid(s, i * l + 1)  '计算确定不需要交换的剩余字符t4
  31.         s = t1 & t2 & t3 & t4 '最后合并,完成交换。 即从t1-t3-t2-t4,交换为t1-t2-t3-t4的结果
  32.         Call pldg2(s, i - 1, t) '对交换完成的结果,继续调用进入下一层递归计算。
  33.     End If
  34. End Sub
复制代码

评分

参与人数 1 +18 收起 理由
xdwy81129 + 18 很给力!代码高深了

查看全部评分

回复

使用道具 举报

发表于 2012-12-23 20:12 | 显示全部楼层
wcymiss 发表于 2012-11-7 22:26

吴姐你好!

按上面递归同样的思路,也写了一个数组循环的算法。
不过很勉强,用到了辅助数组来储存【两两交换】后的中间计算结果,算法效率较低。

但是,思路比较容易理解。

比如: 1234共4个元素的全排列计算:
一.  从元素1开始;
二.  从For i = 2 to m 开始循环:
   1. 添加后面元素2,那么就生成了 1&2=12;
   2. 然后把这个结果进行交换,就有了 2&1=21;
   3. Next 继续循环……
三. 多重循环直到凑满m个元素时输出结果。

上面过程的实例:
1 →12 → 21 → 【1添加2得到12,然后换位得到21】
→12→123→132→312→【12继续添加3得到123,然后换位得到132,312】
→21→213→231→321→【21继续添加3得到213,然后换位得到231,321】

接下来就可以得到最终结果了。
→123添加4  →1234→1243→1423→4123
→132添加4  →1324→1342→1432→4132
→312添加4  →3124→3142→3412→4312
→213添加4  →2134→2143→2413→4213
→231添加4  →2314→2341→2431→4231
→321添加4  →3214→3241→3421→4321
  


回复

使用道具 举报

发表于 2012-12-23 20:14 | 显示全部楼层
这个是我写的【两两交换】数组法代码:
  1. Sub GetPermutReplace()
  2.     Dim AP&, i%, j&, k&, l%, m%, n%
  3.     tms = Timer
  4.    
  5.     m = [a1].End(4).Row
  6.     arr = [a1].Resize(m)
  7.    
  8.     ReDim a(1 To n)
  9.     a(1) = arr(1, 1)
  10.    
  11.     AP = 1
  12.     ReDim x(1 To AP)
  13.     ReDim y(1 To AP)
  14.     y(1) = a
  15.    
  16.     tms = Timer
  17.     For i = 2 To m
  18.         x = y
  19.         ReDim Preserve y(1 To AP * i)
  20.         k = 0
  21.         For j = 1 To AP
  22.             t = x(j)
  23.             t(i) = arr(i, 1)
  24.             k = k + 1
  25.             If i < m Then y(k) = t Else y(k) = Join(t, "")
  26.             For l = i To 2 Step -1
  27.                 tmp = t(l - 1): t(l - 1) = t(l): t(l) = tmp
  28.                 k = k + 1
  29.                 If i < m Then y(k) = t Else y(k) = Join(t, "")
  30.             Next
  31.         Next
  32.         AP = AP * i
  33.     Next
  34.     MsgBox Format(Timer - tms, "0.000s")
  35.    
  36.     [d:d] = ""
  37.     [d1].Resize(AP) = Application.Transpose(y)
  38.    
  39. End Sub
复制代码
回复

使用道具 举报

发表于 2012-12-23 20:15 | 显示全部楼层
香川群子 发表于 2012-12-23 19:49
吴姐你好!

你的全排列算法代码我看了,很牛。

没有仔细看代码,原来的代码计算准确,而且比宏“全排列”快呀

.078125秒  全排列
.046875秒  原代码


可是我执行新代码却出现错误,奇怪了


回复

使用道具 举报

发表于 2012-12-23 21:24 | 显示全部楼层
wcymiss 发表于 2012-11-7 22:26

最后是我的压箱底任意取数排列数组循环算法代码:
  1. Sub GetPermutArray()
  2. Dim AP&, i&, j%, k%, m%, n%
  3. tms = Timer

  4. m = [a1].End(4).Row
  5. arr = [a1].Resize(m)

  6. n = [b1]
  7. AP = WorksheetFunction.Permut(m, n)
  8. [b3] = AP

  9. ReDim a%(1 To n)
  10. ReDim b%(1 To m)

  11. For i = 1 To n - 1
  12. a(i) = i
  13. b(a(i)) = 1
  14. Next
  15. a(n) = n

  16. ReDim crr(1 To AP, 1 To 1)
  17. For i = 1 To AP
  18. crr(i, 1) = arr(a(1), 1)
  19. For j = 2 To n
  20. crr(i, 1) = crr(i, 1) & arr(a(j), 1)
  21. Next j

  22. If a(n) < m Then
  23. For j = a(n) + 1 To m
  24. If b(j) = 0 Then
  25. a(n) = j
  26. GoTo NxtI
  27. End If
  28. Next
  29. Else
  30. If n = 1 Then GoTo NxtI 'n=1
  31. End If


  32. For j = n - 1 To 1 Step -1
  33. b(a(j)) = 0
  34. If a(j) < m Then
  35. For k = a(j) + 1 To m
  36. If b(k) = 0 Then
  37. a(j) = k
  38. b(k) = 1
  39. GoTo NxtJ
  40. End If
  41. Next
  42. End If
  43. Next
  44. NxtJ:
  45. For j = j + 1 To n
  46. For k = 1 To m
  47. If b(k) = 0 Then
  48. a(j) = k
  49. If j < n Then b(k) = 1
  50. Exit For
  51. End If
  52. Next
  53. Next
  54. NxtI:
  55. Next

  56. MsgBox Format(Timer - tms, "0.000s")
  57. [d:d] = ""
  58. [d1].Resize(AP) = crr
  59. End Sub
复制代码
回复

使用道具 举报

发表于 2012-12-23 21:28 | 显示全部楼层
xdwy81129 发表于 2012-12-23 20:15
没有仔细看代码,原来的代码计算准确,而且比宏“全排列”快呀

.078125秒  全排列

新代码出错?

需要提醒你注意的是,
全排列代码仅仅能够计算n个元素的n!的全部排列结果代码。

而不能计算n个元素中任取m个组合,然后组合结果再进行“全”排列的排列组合结果。

……
相应的,我原来的代码可以这样计算。
回复

使用道具 举报

发表于 2012-12-23 23:33 | 显示全部楼层
用递归组合嵌套递归排列(置换算法)后的可计算任意组合的代码:
  1. Dim sj, sj2, jg, l%, m%, n%, k&
  2. Sub 递归排列组合()
  3.     tms = Timer
  4.    
  5.     m = [a1].End(4).Row:    sj = [a1].Resize(m)
  6.     l = Len(sj(1, 1))
  7.     For i = 2 To n
  8.         If Len(sj(i, 1)) > l Then l = Len(sj(i, 1))
  9.     Next
  10. '    l = l + 1
  11.     For i = 1 To n
  12.         sj(i, 1) = String(l - Len(sj(i, 1)), " ") & sj(i, 1)
  13.     Next
  14.    
  15.     n = [b1]: [b3] = WorksheetFunction.Combin(m, n):
  16.     ACP = WorksheetFunction.Permut(m, n): [b4] = ACP
  17.     ReDim jg(ACP, 0)
  18.    
  19.     k = 0: Call plzhdg("", 0, 0)
  20.    
  21.     [d:d] = "":    [d1].Resize(ACP) = jg
  22. End Sub
  23. Sub plzhdg(s$, i%, t%)
  24.     Dim j%
  25.     For j = i + 1 To m
  26.         If t + 1 < n Then
  27.             Call plzhdg(s & "," & sj(j, 1), j, t + 1)
  28.         Else
  29.             sj2 = Split(s & "," & sj(j, 1), ",")
  30.             Call pldg2("", 0, 1)
  31.         End If
  32.     Next
  33. End Sub
  34. Sub pldg2(s$, i%, t%)
  35.     If t > n Then jg(k, 0) = s: k = k + 1
  36.     If t <= n Then Call pldg2(s & sj2(t), t, t + 1)
  37.     If i > 1 Then
  38.         If i > 2 Then t1 = Mid(s, 1, (i - 2) * l) Else t1 = ""
  39.         t2 = Mid(s, (i - 1) * l + 1, l)
  40.         t3 = Mid(s, (i - 2) * l + 1, l)
  41.         t4 = Mid(s, i * l + 1)
  42.         s = t1 & t2 & t3 & t4
  43.         Call pldg2(s, i - 1, t)
  44.     End If
  45. End Sub
复制代码
回复

使用道具 举报

发表于 2012-12-24 11:20 | 显示全部楼层
呵呵,经过研究,两两交换可以用mid()函数直接操作,省去了一步……

因此,目前我的代码速度已经超过了吴姐的速度了。

下面是数组循环方式的【两两置换】算法应用。
但是采用了字符串方式操作,和吴姐的直接内存数组内交换方法不同。
  1. Sub GetPermutReplaceStr()
  2.     Dim AP&, i%, j&, k&, l%, m%, n%, s$, t$
  3.     tms = Timer
  4.    
  5.     n = [a1].End(4).Row
  6.     sj = [a1].Resize(n)
  7.     l = Len(sj(1, 1))
  8.     For i = 2 To n
  9.         If Len(sj(i, 1)) > l Then l = Len(sj(i, 1))
  10.     Next
  11. '    l = l + 1
  12.     For i = 1 To n
  13.         sj(i, 1) = String(l - Len(sj(i, 1)), " ") & sj(i, 1)
  14.     Next
  15.     '以上为统一原始数据中字符串长度,以便今后mid置换操作时能够准确定位   

  16.     AP = 1
  17.     ReDim x(1 To 1)
  18.     ReDim y(1 To 1)
  19.     y(1) = sj(1, 1)
  20.     '以上为建立辅助用的数组x、数组y,用来存放中间计算结果。(和递归作用同样的意思)

  21. '    tms = Timer '正式开始【两两交换】计算
  22.     For i = 2 To n
  23.         x = y '把上次计算的储存结果y导入辅助x中,开始新一轮遍历处理计算
  24.         ReDim Preserve y(1 To AP * i) '扩大辅助y行数以便储存新的结果
  25.         k = 0 '定位指针归零
  26.         For j = 1 To AP
  27.             t = sj(i, 1) '本轮元素作为置换字符t
  28.             s = x(j) & t '从上轮次结果x中依次获取结果并添加t
  29.             k = k + 1 : y(k) = s '结果存入y
  30.             For p = i - 1 To 1 Step -1 '刚才获取新组合结果进行【倒序置换】
  31.                 Mid(s, p * l + 1, l) = Mid(s, (p - 1) * l + 1, l) 'p位置用前一位置字符置换
  32.                 Mid(s, (p - 1) * l + 1, l) = t 'p的前一位置直接用本轮新添元素t字符置换
  33.                 k = k + 1 : y(k) = s '结果存入y
  34.             Next
  35.         Next
  36.         AP = AP * i  '计算下一轮次结果总数,作为数组y大小扩大的依据。
  37.     Next
  38.     MsgBox Format(Timer - tms, "0.000s") '计算完成
  39.    
  40.     [f:f] = ""
  41.     [f1].Resize(AP) = Application.Transpose(y) '输出结果
  42.    
  43. End Sub
复制代码
回复

使用道具 举报

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

本版积分规则

小黑屋|手机版|Archiver|Excel精英培训 ( 豫ICP备11015029号 )

GMT+8, 2024-4-23 23:42 , Processed in 0.392531 second(s), 14 queries , Gzip On, Yac On.

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

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