(***此文只在mobile01發表,如轉貼到其它論譠、blog,請附上來源網址,謝謝***)
(5/24 更新程式碼,請參考4樓)
(6/28 增加“重複組合功能”,請參考11樓)
這篇有人問,不過提問者自己也是高手,自己有寫程式解決了,我只是提供另一種寫法而己
(離上次回答問題,居然過3年了)
http://www.mobile01.com/topicdetail.php?f=511&t=3432008&p=1
這篇也有人問(雖然提問者,主要是要問規劃求解)
http://www.mobile01.com/topicdetail.php?f=511&t=5022284&p=1#62843370
因為2篇標題幾乎一樣
所以把3年前寫的程式,把2^n-1 ,c(n,m) 2個功能合併,
把計算c(n,m)的超多層迴圈,也用副程式代替,縮短程式碼
順便修改一些地方,提升計算速度
第一篇 20個數字,搜尋加總10000,可成立218個組合
計算次數1048575,程式運行時間約4秒
第二篇 23個數字,搜尋加總7143,可成立180個組合,
計算次數8388607,程式運行時間約30秒(未修正前約80秒)
如果不找全部組合,只找一組,或限定搜尋組數時,剛好小於最大成立組合
或限定每次取出數字個數(3個 or 5個 or ...其它)
基本上,秒解
不過,那種幾百個數字的,還是別用了
我這個 excel vba小品程式,寫的爛,計算速度太慢,電腦跑不動的…
有興趣的拿去玩吧,資料量不多的話,用vba會比規劃求解快很多
程式功能,找出這2個排列組合公式,加總條件成立的組合
2^n-1
c(n,m)=n!/((n-m)!m!)
=========================================================================
'程式用法:
'a1~a??? 放數字(不用排序)
'b1 目標值
'b2 限定每組個數(不填,找所有組合)
'b3 限定最大搜尋組數(不填,找所有組合)
建議先填入一個小一點的數字測試
或自行如入計時功能超過時間就自動結束程式
避免資料太多跑不完
Sub test()
Dim data As Variant, targetsum As Variant, total As Long, i As Long, ok As Long, target As Long, limited As Integer, maxcombin As Integer, start As Integer, startend As Integer
Columns("d:z").Clear'這行視情況,請自行增加清除範圍
data = Application.Transpose(Range("A1", Range("A1").End(xlDown)))
If Range("b1") = 0 Then End
target = Range("b1"): limited = Range("b2"): maxcombin = Range("b3"): Range("b4") = "": Range("b5") = "": Range("d1") = Format(Now(), "hh:mm:ss")
If limited = 0 Then
start = 1: startend = UBound(data)
Else
start = Range("b2"): startend = start
End If
For i = start To startend
ReDim targetsum(1 To i)
Call findCombin(data, targetsum, i, total, 1, 1, ok, target, limited, maxcombin)
If start <> startend Then Range("b5") = Int((i / startend * 100) + 0.5) & "%"
Next i
Range("b5") = "100%": Range("d2") = Format(Now(), "hh:mm:ss"): Debug.Print total
End Sub
Sub findCombin(data As Variant, targetsum As Variant, j As Long, total As Long, k As Long, n As Long, ok As Long, target As Long, limited As Integer, maxcombin As Integer)
Dim i As Long, tempsum As Long
'(20180813更新,注意,如果數值有小數點,請把上面這行改成=> Dim i As Long, tempsum,如果目標值只是一般數字改成tempsum As Double 或是 tempsum As single也可以)
For i = k To UBound(data)
targetsum(n) = data(i): tempsum = 0
If n = j Then
total = total + 1
For s = 1 To UBound(targetsum)
tempsum = tempsum + targetsum(s)
Next s
If tempsum = target Then
'如果目標值有範圍限制
'例如:10<目標值<88
'整個程式只需修改上面那1行if判斷句即可
'至於怎麼改,請自行練習
ok = ok + 1: Range("e" & ok).Resize(1, j) = targetsum
If (ok = maxcombin And maxcombin <> 0) Then
Range("b4") = ok: Range("b5") = "100%": Range("d2") = Format(Now(), "hh:mm:ss"): Debug.Print total
End
End If
Range("b4") = ok
End If
Else
Call findCombin(data, targetsum, j, total, i + 1, n + 1, ok, target, limited, maxcombin)
End If
Next i
End Sub
附加壓縮檔: 201612/mobile01-f62b004a35b1e9bce6be0061f7b78b63.zip
如果沒有目標值,只想看所有的不重覆排列組合,目標值保持空白,程式碼修改如下
注意:組合太多,請先把結果放陣列,最後再放到儲存格,不然計算時間會很恐怖
(點下可看大圖)
Wei_1144 wrote:
如果數字可重複選取,例如 6 = 3 + 3,可解嗎?...(恕刪)
不太懂耶??
如果您問的是 3,3,1,2,3,3 這串數字中有重複項目,找加總為6的組合,這個範例可解
因為 2^n-1 , c(n,m) 這2個公式,是看位置來組合的,不管內容
如果您問的是 3,3,1,2,3,3 這串數字,假設每次取2個的話 11,22,31,32……
每個字都可以重複選,這個範例不可解,那是另一個公式。
又翻一下以前的統計學課本,才發現還細分成排列公式,組合公式
雖然確定程式的結果是正確的
但臨時抱佛腳看課本的說明是不是正確我就不確定了
離開學校太久了,現在看起來那些內容像天書一樣,有錯請原諒我
真搞不懂我以前我以前是怎麼畢業的,為什麼看的懂那些內容
這樣可以多一個排列方式的選擇
'=========================================================
Sub test()
Dim data(), targetsum() As Single, item_name() As String, lastrow As Integer
Dim total As Long, i As Integer, ok As Integer, target As Single, limited As Integer, maxcombin As Integer, start As Integer, startend As Integer, tolerance As Single
Columns("E:Z").Clear
On Error GoTo Esc_Stop
Application.EnableCancelKey = xlErrorHandler
ttt = Timer
lastrow = Range("A2").End(xlDown).Row
ReDim data(1 To 2, 1 To lastrow - 1)
For d = 1 To lastrow - 1
data(1, d) = Cells(d + 1, 2).Value
' data(2, d) = Cells(d + 1, 1).Value
Next d
If Range("C1") = 0 Then End
target = Range("C1"): tolerance = Range("C2"): limited = Range("C3"): maxcombin = Range("C4")
Range("C5") = "": Range("C6") = "": Range("E1") = Format(Now(), "hh:mm:ss")
If limited = 0 Then
start = 1: startend = UBound(data, 2)
Else
start = limited: startend = start
End If
For i = start To startend
ReDim targetsum(1 To i)
' ReDim item_name(1 To i)
Call findCombin(data, targetsum, item_name, i, total, 1, 1, ok, target, limited, maxcombin, tolerance)
If start <> startend Then Range("C6") = Int((i / startend * 100) + 0.5) & "%請耐心等待"
Next i
'20190524 更新,無解時,find_name副程式會出錯(另一種方式沒問題)
If ok <> 0 Then Call find_name '20190524 修正
Range("C6") = "100%計算結束": Range("E2") = Format(Now(), "hh:mm:ss")
Debug.Print total, Timer - ttt
Exit Sub
Esc_Stop:
If Err = 18 Then
MsgBox "stop"
End If
End Sub
Sub findCombin(data(), targetsum() As Single, item_name() As String, j As Integer, total As Long, k As Integer, n As Integer, ok As Integer, target As Single, limited As Integer, maxcombin As Integer, tolerance As Single)
Dim i As Integer, tempsum As Double
For i = k To UBound(data, 2)
targetsum(n) = data(1, i): tempsum = 0
' item_name(n) = data(2, i)
If n = j Then
total = total + 1
For s = 1 To UBound(targetsum)
tempsum = tempsum + targetsum(s)
Next s
If tempsum = target Then
ok = ok + 1
Range("G" & ok).Resize(1, j) = targetsum
' Range("F" & ok) = Join(item_name, ",")
If (ok = maxcombin And maxcombin <> 0) Then
Range("C5") = ok: Range("C6") = "100%計算結束": Range("E2") = Format(Now(), "hh:mm:ss"): Debug.Print total
If ok <> 0 Then Call find_name '20190524 修正
End
End If
Range("c5") = ok
End If
Else
Call findCombin(data, targetsum, item_name, j, total, i + 1, n + 1, ok, target, limited, maxcombin, tolerance)
End If
Next i
End Sub
Sub find_name()
Dim lastrow As Integer, lastcol As Integer, n As Object, name_data() As String
Set n = CreateObject("Scripting.Dictionary")
Application.ScreenUpdating = False
lastrow = Range("A1").End(xlDown).Row
For i = 2 To lastrow
n.Add Str(Cells(i, 2)), Cells(i, 1)
Next i
lastrow = Range("g1").CurrentRegion.Rows.Count '20190524 修正
For i = 1 To lastrow
lastcol = Range("G" & i).End(xlToRight).Column
ReDim name_data(1 To lastcol - 6)
For j = 7 To lastcol
name_data(j - 6) = n(Str(Cells(i, j)))
Next j
Cells(i, 6) = Join(name_data, ",")
Next i
Set n = Nothing
Application.ScreenUpdating = True
End Sub
'=========================================================
不過編號有2種處理方式,預設是用 find_name() 副程式
排列組合完之後,再找編號,程式比較快
以這個範例中的23個數字來說,8百多萬次計算
先排列後找編號,約30~40秒
排列過程中順便排列編號,約50~60秒
什麼時候要用比較慢的方法找編號呢?
就是當編號 或 數值 中,其中一個有重覆的資料時
例如
同編號有2個(或以上)相同數值
這在材料、原料,選擇上常常發生
例如:鐵管,3公尺、鋁管,3公尺
改成另一種慢的方式很簡單,只要把以下四行的禁用取消
然後再把 call find_name 2行禁用,就可以了
各位在圖片看到的誤差值(+ -)功能
在上傳的程式碼、夾檔中,我改掉了,沒作用
只有保留變數值tolerance
要誤差值功能,只需要改一行 if (如下圖),其它都不用改
可自行把tolerance變數,代入程式碼中,或直接用range("c2")的值也可以(但會變慢一些)
另外自從2016上傳舊範例後,有不少人在私訊問我,幫忙改出誤差值,甚至願意付費
我寫這些亂七八糟的範例,不是為了打廣告、不是為了賺錢
如果真的想打廣告、賺錢,就會在簽名檔中加上個人blog、網址,別在私訊問我了
我寫的範例基本上功能完整,就是介面醜了點
要加上自行寫的表單、輸入介面…等等的,不會太難
像誤差值,就只要改1行,我還特別標出來
希望在看範例的同時,請自行思考一下如何改寫
(20200524 修正3行程式碼,順便加入esc中斷功能,請參考上面的註解)
[點擊下載]
heavenweaver worte:
如果我想找出總和的目標值是介於某個數值區間,例如:8000~10000,請問如何修改程式?在此先謝謝您!
真的很棒,如果能找某個數值區間總和的目標值之所有可能組合就更加完美了!...(恕刪)
讓程式不完美,是我故意的
請參考4樓,有說明改那一行程式碼
if 判斷式在excel中,不管是vba,還是公式,都是很基本、很重要的功能
請試著參考下面文章(或自行google 其它 if 範例)
https://docs.microsoft.com/zh-tw/dotnet/visual-basic/programming-guide/language-features/operators-and-expressions/logical-and-bitwise-operators
https://docs.microsoft.com/zh-tw/office/vba/language/reference/user-interface-help/comparison-operators
試著把下面這個小程式,從6改成3~9,一樣只需改1行程式碼
如果成功,那您就會改上面的程式了
(請使用f8逐行執行,了解程式的流程)
Sub test()
data = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
For Each target In data
If target = 6 Then MsgBox "find"
Next
End Sub
想請問~
如果我想要尋找加總為100的組合中,由8、10、12、14、16、18、20組合,且數字可重複的話(不限由多少個數字組成),我該怎麼修改程式碼呢?
例如:
100 = 20 + 20 + 20 + 20 + 20
100 = 8 + 8 + 8 + 8 + 8 + 8 + 8 + 8 + 8 + 8 + 20
100 = 8 + 8 + 8 + 8 + 8 + 8 + 8 + 8 + 8 + 8 + 10 + 10
.
.
.
.
等等非常多的組合
如果原先您所提供的檔案中,只輸入8、10、12、14、16、18、20這七個數字而已的話,並沒有辦法讓數字可以重複
又或者我嘗試將所有數字在組合中最多可以重複到幾個,而在第一行就輸入多少個那個數字
例如,我知道8在所有可能的組合中,最多只會有11個,我就輸入了11個8在第一行
其他數字10、12、14、16、18、20也用相同概念輸入在第一行
但是這樣會導致跑出的組合結果會重複非常多次......
請大家幫幫小弟我了!!!
kore5657 wrote:
如果我想要尋找加總為100的組合中,由8、10、12、14、16、18、20組合,且數字可重複的話(不限由多少個數字組成),我該怎麼修改程式碼呢?
雖然很久很久以前在學校學過,但忘光了,如果我沒記錯的話
這是排列公式 n 取 m ,可重複選取,直接算排列數是很簡單
但因為可重複選取且不限個數,計算結果會是天文數字
看看您這組排列 7 取12
100 = 8 + 8 + 8 + 8 + 8 + 8 + 8 + 8 + 8 + 8 + 10 + 10
在不限個數的情況下,想要拿到這組排列
程式碼就要從7取1、7取2、…一直跑到7取12
然後在排列數170多億種,選出正確的排列
7取17,就變成=>232630513987207種排列
想用程式碼在所有排列中選出所有解,正常電腦是跑不完的
(不重複才勉強可用程式計算)
kore5657 wrote:
例如,我知道8在所有可能的組合中,最多只會有11個
這是因為您用8、10、12、14、16、18、20,這組數字來算
但這樣在可重複選取的條件下,是不行的
如果數字變這樣呢?
1、10、12、14、16、18、20
那就會出現 7 取 100 (100個1)的情況喔
算出來的數字我都不知道要怎麼念了
關閉廣告