पाठ · #11 / 11

डायनामिक प्रोग्रामिंग (Dynamic Programming)

समय के बदले मेमोरी — एक बार हल करो, हमेशा के लिए याद रखो

Dynamic programming · 1D table fill

step 0 / 0

आज़माएँ: 1-D टेबल को बाएँ से दाएँ भरते हुए देखें — हर सेल बस अपने पहले की दो सेल का योग है। जिस मान को नैव रिकर्शन अरबों बार दोबारा गिनता, वह बग़ल वाली सेल से एक ही बार पढ़ लिया जाता है। यही पूरा विचार है: दोबारा गिनना → देख लेना।

यह क्यों मायने रखता है

हैरतअंगेज़ रूप से बहुत-सी "घातांकी (exponential)" समस्याएँ दरअसल छुपे तौर पर बहुपदी (polynomial) होती हैं — वे सिर्फ़ घातांकी लगती हैं क्योंकि उनकी सीधी रिकर्शन एक ही उप-समस्या को लाखों बार हल करती है। डायनामिक प्रोग्रामिंग वह तकनीक है जो दोहराव को पहचानकर उसे कैश कर लेती है। एक बार पैटर्न दिख जाए, तो जिन समस्याओं में सदियाँ लगतीं, वे ढहकर मिलीसेकंड में सिमट जाती हैं।

रोबोटिक्स में यह value iteration, policy iteration, Bellman रिकर्शन, और उन सबसे छोटे-पथ एल्गोरिथ्म की बुनियाद है जिनसे आप पहले मिल चुके हैं। string में यह वह diff एल्गोरिथ्म है जो git इस्तेमाल करता है। bioinformatics में हर sequence-alignment औज़ार इसी तरह काम करता है।

मूल विचार

DP-आकार की समस्या में दो गुण होते हैं:

  1. Optimal substructure — पूरी समस्या का इष्टतम (optimal) जवाब उसी समस्या के छोटे संस्करणों के इष्टतम जवाबों से बनाया जा सकता है।
  2. Overlapping subproblems — वे छोटे संस्करण दोहराते हैं। भोली रिकर्शन एक ही को कई बार हल करेगी।

जब दोनों टिकते हैं, तो तरकीब है हर उप-समस्या को ठीक एक बार हल करना और नतीजा सहेज लेना। दो रूप:

दोनों O(states × per-state-work) हैं। Memoization स्वाभाविक पुनरावृत्ति (recurrence) के क़रीब है; tabulation आपको जगह और क्रम पर ज़्यादा बारीक नियंत्रण देता है।

मुख्य बातें

और गहराई में

DP आकार पहचानना

किसी भी समस्या से, जिस पर DP का शक हो, ये पाँच सवाल पूछिए:

  1. क्या मैं जवाब को रिकर्सिव रूप से बता सकता हूँ? "state X तक पहुँचने की न्यूनतम लागत, Y विकल्पों के न्यूनतम के बराबर है …"
  2. state क्या है? एक अकेला पूर्णांक? एक जोड़ा (i, j)? एक इंडेक्स और एक झंडा (flag)?
  3. recurrence क्या है? f(state) कैसे f(smaller-state) में विघटित होता है?
  4. base case क्या हैं? वे सबसे छोटे state जिनका जवाब बिना रिकर्शन के ज्ञात है।
  5. टेबल किस क्रम में भरूँ? कोई भी क्रम जहाँ हर कोशिका की निर्भरताएँ उससे पहले भर जाएँ।

अगर आप पाँचों का जवाब दे सकें, तो आप DP लिख सकते हैं। अगर नहीं, तो आपके पास अभी DP समस्या है ही नहीं — दोबारा बयान कीजिए।

Memoization बनाम tabulation — किसे कब चुनें

| | Memoization | Tabulation | |---|---|---| | मानसिक मॉडल | Top-down: रिकर्शन पर भरोसा करो | Bottom-up: टुकड़ा-टुकड़ा बनाओ | | क्रियान्वयन | रिकर्सिव फ़ंक्शन + कैश | एक ऐरे भरता लूप | | हल हुई उप-समस्याएँ | सिर्फ़ वे जो पहुँच में हैं | सब की सब | | स्टैक का ख़तरा | गहरी रिकर्शन → स्टैक ओवरफ़्लो | कोई नहीं | | जगह-अनुकूलन की संभावना | मुश्किल | आसान (rolling पंक्तियाँ / "आख़िरी दो मान") |

Memoization चुनिए जब state space विरल (sparse) हो — कई state मौजूद हों पर जवाब सिर्फ़ एक छोटे पहुँच-योग्य समुच्चय पर निर्भर हो। Tabulation चुनिए जब आप वैसे भी ज़्यादातर state space छूने वाले हों, या जब आपको जगह-अनुकूलन करना हो।

जगह-अनुकूलन — "rolling" तरकीब

ज़्यादातर DP recurrence सिर्फ़ एकदम पिछली पंक्ति या स्थिर-कितनी पिछली कोशिकाओं पर निर्भर करते हैं। तो आपको पूरी टेबल की ज़रूरत नहीं।

recurrence पर नज़र रखिए: अगर छोटे k के लिए f(i) सिर्फ़ f(i-1), f(i-2), …, f(i-k) इस्तेमाल करता है, तो आप बस उन्हीं k मानों को रखकर सरकते रह सकते हैं।

रोबोटिक्स लंगर — DP ही Bellman है, ही value iteration है

रीइन्फ़ोर्समेंट लर्निंग में Bellman समीकरण सचमुच एक DP recurrence है:

V*(s) = max over actions a of [ R(s, a) + γ · Σ P(s' | s, a) · V*(s') ]

state s का इष्टतम मान, तत्काल इनाम (reward) जमा सबसे अच्छे अगले state के घटाए (discounted) भावी मान के बराबर है। Value iteration बस state space पर Bellman recurrence का tabulation है, जो अभिसरण (convergence) तक दोहराया जाता है। हर gridworld solver, हर path-planner जो लागतें सँभालता है, पाठ्यपुस्तक का हर MPC नियंत्रक — सब DP हैं।

जब DP नाकाम होता है

DP को optimal substructure चाहिए। कुछ समस्याएँ इसका उल्लंघन करती हैं:

अगर आपको ऐसा कोई परिमित (finite) state न मिले जो वह सब समेट ले जो भावी क़दमों को जानना ज़रूरी है, तो DP लागू नहीं होता।

असल दुनिया में

गणितीय ब्यौरा

Time
O(states × work-per-state) — एक बार आपने state निरूपण चुन लिया, तो यह ठीक-ठीक यही है।
Space (full table)
O(states)
Space (rolling)
अक्सर O(d) जहाँ d recurrence की "lookback depth" है — आम तौर पर एक छोटा स्थिरांक या एक पंक्ति।
Memoization overhead
HashMap lookup + रिकर्शन स्टैक — एक तंग tabulation लूप के मुक़ाबले 2-3× का स्थिर गुणांक। विरल state space के लिए चुकाने लायक़।

क्रियान्वयन (Implementation)

कंकाल — हर DP इस टेम्पलेट में अँटता है

def solve():
    memo = {}
    def f(state):
        if state in BASE_CASES: return base_answer
        if state in memo: return memo[state]
        result = combine(f(smaller_state_1), f(smaller_state_2), ...)
        memo[state] = result
        return result
    return f(initial_state)

या, tabulated:

def solve():
    dp = [base_answer for _ in range(N + 1)]
    for state in range(SMALLEST + 1, LARGEST + 1):
        dp[state] = combine(dp[smaller_1], dp[smaller_2], ...)
    return dp[LARGEST]

चिर-परिचित समस्या-आकार

अभ्यास

तीन समस्याएँ जो तीन बुनियादी DP आकारों को पक्का कर देती हैं:

सभी अभ्यास सवाल देखें