Python數(shù)據(jù)結(jié)構(gòu)與算法34:遞歸:找零兌換問題的遞歸解法

:本文如涉及到代碼,均經(jīng)過Python 3.7實際運行檢驗,保證其嚴(yán)謹(jǐn)性。

本文閱讀時間約為7分鐘。

上一節(jié)提到,貪心策略會有失效的情況,如何在不依賴某種硬幣體系的情況下找到找零硬幣最少個數(shù)的最優(yōu)解?

這就要求我們找一種“能行”的找到最優(yōu)解的方法。所謂“能行”,是指不依賴于類似于美元那種固定的四種面值的硬幣體系。

遞歸就是這么一種“能行”的方法。

遞歸解決找零問題的最優(yōu)解

套用遞歸的三個條件:

兌換¢63的例子。

1)確定基本結(jié)束條件:需要兌換的找零,其面值正好等于某個貨幣單位,比如找零是¢25,就給1個¢25的硬幣。

2)其次是減小問題的規(guī)模,我們要對每種硬幣嘗試1次,例如美元硬幣體系:

  • 找零減去¢1后,求兌換硬幣最少數(shù)量(遞歸調(diào)用自身,第3個條件);
  • 找零減去¢5后,求兌換硬幣最少數(shù)量;
  • 找零減去¢10后,求兌換硬幣最少數(shù)量;
  • 找零減去¢25后,求兌換硬幣最少數(shù)量;

最后選擇上面結(jié)果中最小的一個。

numCoins = min(1+numCoins(originalamout-1), 1+numCoins(originalamount-5), 1+numCoins(original-10), 1+nuCoins(originalamount-25))。
具體代碼實現(xiàn)如下:

# 遞歸解決找零問題v1。
import time
start =  time.time()
def recMC(coinValueList, change):
    minCoins = change
    if change in coinValueList:
        return 1  # 最小規(guī)模,直接返回。
    else:
        for i in [c for c in coinValueList if c <= change]:
            numCoins = 1 + recMC(coinValueList, change - i)  # 調(diào)用自身和減少規(guī)模。
            if numCoins < minCoins:
                minCoins = numCoins
    return minCoins

print(recMC([1, 5, 10, 25], 63))
end = time.time()
print(f"運行本遞歸程序一共用了{(lán)end - start}秒。")

<<<
6
運行本遞歸程序一共用了37.6766254901886秒。
<<<
遞歸解決之道的問題

遞歸方法雖然“能行”,也就是能解決問題,但是其最大的問題在于:極其低效?。?!

上述案例中,兌換¢63,需要進行67,716,925次遞歸調(diào)用。本計算機花了37秒時間才得到答案。

以26分兌換硬幣為例,看看遞歸調(diào)用過程(377次遞歸的一小部分)。結(jié)果發(fā)現(xiàn),重復(fù)計算太多,例如找零15分的出現(xiàn)了三次,而最終解決還要52次遞歸調(diào)用。而實際上一次就夠了。所以遞歸算法的致命缺點就是重復(fù)的計算太多了。

遞歸解法的改進

對遞歸解法進行改進,關(guān)鍵之處就在于消除重復(fù)計算:

用一個表將計算過的中間結(jié)果保存起來,在計算之前查看表,看是否已經(jīng)計算過。所謂中間結(jié)果,就是部分找零的最優(yōu)解,在遞歸調(diào)用過程中已經(jīng)得到的最優(yōu)解被記錄下來。在遞歸調(diào)研之前先查表是否已有部分找零的最優(yōu)解,如果有,直接返回最優(yōu)解而不是進行遞歸調(diào)用;如果沒有,才進行遞歸調(diào)用。

# 遞歸解決找零問題v2。
import time
start =  time.time()
def recMC(coinValueList, change, knownResults):
    minCoins = change
    if change in coinValueList:
        knownResults[change] = 1  # 記錄最優(yōu)解。
        return 1  # 最小規(guī)模,直接返回。
    elif knownResults[change] > 0:
        return knownResults[change]  # 查表成功,直接用最優(yōu)解。
    else:
        for i in [c for c in coinValueList if c <= change]:
            numCoins = 1 + recMC(coinValueList, change - i, knownResults)  # 調(diào)用自身和減少規(guī)模。
            if numCoins < minCoins:
                minCoins = numCoins
                # 找到最優(yōu)解,記錄到表中。
                knownResults[change] = minCoins
    return minCoins

print(recMC([1, 5, 10, 25], 63, [0] * 64))
end = time.time()
print(f"運行本遞歸程序一共用了{(lán)end - start}秒。")

<<<
6
運行本遞歸程序一共用了0.0009992122650146484秒。
<<<

可以看到,優(yōu)化過后的程序運行時間從約38秒降低至1毫秒。
改進之后的解法,極大地減少了遞歸調(diào)用次數(shù)。對63分兌換硬幣問題,僅僅需要221次遞歸調(diào)用,是改進之前的三十萬分之一,程序效率得到極大提升。

To be continued.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容