गतिशील प्रोग्रामिंग क्या है? [बन्द है]


276

गतिशील प्रोग्रामिंग क्या है ?

यह पुनरावृत्ति, संस्मरण आदि से कैसे भिन्न है?

मैंने इस पर विकिपीडिया लेख पढ़ा है, लेकिन मैं अभी भी इसे वास्तव में नहीं समझता हूं।


1
यहाँ CMU से माइकल ए। ट्रिक का एक ट्यूटोरियल है जो मुझे विशेष रूप से मददगार लगा है: mat.gsia.cmu.edu/classes/dynamic/dynamic.html यह निश्चित रूप से सभी संसाधनों के अतिरिक्त दूसरों द्वारा अनुशंसित (अन्य सभी संसाधन, विशेष रूप से CLR) है और क्लेनबर्ग, टार्डोस बहुत अच्छे हैं!)। इस ट्यूटोरियल को पसंद करने का कारण यह है क्योंकि यह उन्नत अवधारणाओं को काफी धीरे-धीरे पेश करता है। यह थोड़ी पुरानी सामग्री है, लेकिन यह यहां प्रस्तुत संसाधनों की सूची के लिए एक अच्छा अतिरिक्त है। स्टीवन स्कीना के पेज और डायनामिक प्रोग्रामिंग पर व्याख्यान भी देखें: cs.sunysb.edu/alalgorith/video-lectures http:
Edmon

11
मैंने हमेशा "डायनामिक प्रोग्रामिंग" को एक भ्रमित शब्द पाया है - "डायनामिक" का सुझाव है कि स्थैतिक नहीं, लेकिन "स्टेटिक प्रोग्रामिंग" क्या है? और "... प्रोग्रामिंग" "ऑब्जेक्ट ओरिएंटेड प्रोग्रामिंग" और "फंक्शनल प्रोग्रामिंग" को ध्यान में लाता है, डीपी का सुझाव देना एक प्रोग्रामिंग प्रतिमान है। मेरे पास वास्तव में एक बेहतर नाम नहीं है (शायद "डायनामिक एल्गोरिदम")? लेकिन यह बहुत बुरा है हम इस एक के साथ फंस गए हैं।
17 दिसंबर को dimo414

3
@ dimo414 "प्रोग्रामिंग" यहाँ "रैखिक प्रोग्रामिंग" से अधिक संबंधित है जो गणितीय अनुकूलन विधियों के एक वर्ग के अंतर्गत आता है। अन्य गणितीय प्रोग्रामिंग विधियों की सूची के लिए लेख गणितीय अनुकूलन देखें ।
syockit

1
इस संदर्भ में @ dimo414 "प्रोग्रामिंग" एक सारणीबद्ध पद्धति को संदर्भित करता है, कंप्यूटर कोड लिखने के लिए नहीं। - कॉर्मेन
user2618142

Cs.stackexchange.com/questions/59797/… में वर्णित बस टिकट की लागत को कम से कम करने की समस्या को गतिशील प्रोग्रामिंग में सबसे अच्छा हल किया गया है।
truthadjustr

जवाबों:


210

डायनेमिक प्रोग्रामिंग तब होती है जब आप भविष्य की समस्या को हल करने के लिए पिछले ज्ञान का उपयोग करते हैं।

एक अच्छा उदाहरण n = 1,000,002 के लिए फाइबोनैचि अनुक्रम को हल कर रहा है।

यह एक बहुत लंबी प्रक्रिया होगी, लेकिन क्या होगा अगर मैं आपको n = 1,000,000 और n = 1,000,001 के लिए परिणाम दूं? अचानक समस्या बस अधिक प्रबंधनीय हो गई।

स्ट्रिंग प्रोग्रामिंग में डायनामिक प्रोग्रामिंग का बहुत उपयोग किया जाता है, जैसे कि स्ट्रिंग एडिट की समस्या। आप समस्या का एक सबसेट (ओं) को हल करते हैं और फिर उस जानकारी का उपयोग अधिक कठिन मूल समस्या को हल करने के लिए करते हैं।

गतिशील प्रोग्रामिंग के साथ, आप अपने परिणामों को किसी प्रकार की तालिका में आम तौर पर संग्रहीत करते हैं। जब आपको किसी समस्या के उत्तर की आवश्यकता होती है, तो आप तालिका का संदर्भ लेते हैं और देखते हैं कि क्या आप पहले से ही जानते हैं कि यह क्या है। यदि नहीं, तो आप अपने आप को उत्तर की ओर एक कदम रखने के लिए अपनी तालिका में डेटा का उपयोग करते हैं।

कॉर्मेन एल्गोरिदम पुस्तक में गतिशील प्रोग्रामिंग के बारे में एक महान अध्याय है। और यह Google Books पर मुफ़्त है! इसे यहां देखें।


50
क्या आपने अभी संस्मरण का वर्णन नहीं किया है?
dreadwail

31
मैं कहूंगा कि संस्मरण गतिशील प्रोग्रामिंग का एक रूप है, जब संस्मरण समारोह / विधि एक पुनरावर्ती है।
डेनियल हैकस्टैप

6
अच्छा उत्तर, केवल इष्टतम उप-संरचना के बारे में एक उल्लेख जोड़ देगा (उदाहरण के लिए, ए से बी तक कम से कम पथ के किसी भी उपसमुच्चय 2 अंत बिंदुओं के बीच सबसे छोटा रास्ता है जो एक दूरी की मीट्रिक है जो त्रिकोण असमानता का निरीक्षण करता है)।
शिया

5
मैं "आसान" नहीं कहूंगा, लेकिन तेजी से। एक आम गलतफहमी यह है कि dp उन समस्याओं को हल करता है जो भोले एल्गोरिदम नहीं कर सकते हैं और ऐसा नहीं है। कार्यक्षमता का नहीं बल्कि प्रदर्शन का विषय है।
andandand

6
संस्मरण का उपयोग करते हुए, गतिशील प्रोग्रामिंग समस्याओं को शीर्ष डाउन तरीके से हल किया जा सकता है। यानी अंतिम मूल्य की गणना करने के लिए फ़ंक्शन को कॉल करना, और वह फ़ंक्शन बदले में उपप्रोम्बल्स को हल करने के लिए इसे स्वयं पुनरावर्ती कहता है। इसके बिना, गतिशील प्रोग्रामिंग समस्याओं को केवल नीचे के तरीके से हल किया जा सकता है।
प्रणव

175

डायनामिक प्रोग्रामिंग एक ऐसी तकनीक है जिसका उपयोग एक पुनरावर्ती एल्गोरिथ्म में एक ही उपप्रक्रम में कई बार कंप्यूटिंग से बचने के लिए किया जाता है।

आइए, फाइबोनैचि संख्याओं का सरल उदाहरण लेते हैं: द्वारा परिभाषित n वें फाइबोनैचि संख्याओं का पता लगाना

F n = F n-1 + F n-2 और F 0 = 0, F 1 = 1

प्रत्यावर्तन

ऐसा करने का स्पष्ट तरीका पुनरावर्ती है:

def fibonacci(n):
    if n == 0:
        return 0
    if n == 1:
        return 1

    return fibonacci(n - 1) + fibonacci(n - 2)

गतिशील प्रोग्रामिंग

  • शीर्ष नीचे - संस्मरण

पुनरावृत्ति अनावश्यक गणनाओं का एक बहुत करता है क्योंकि किसी दिए गए फाइबोनैचि संख्या की कई बार गणना की जाएगी। इसे सुधारने का एक आसान तरीका परिणामों को कैश करना है:

cache = {}

def fibonacci(n):
    if n == 0:
        return 0
    if n == 1:
        return 1
    if n in cache:
        return cache[n]

    cache[n] = fibonacci(n - 1) + fibonacci(n - 2)

    return cache[n]
  • नीचे से ऊपर

ऐसा करने का एक बेहतर तरीका यह है कि सही क्रम में परिणामों का मूल्यांकन करके सभी को एक साथ पुन: प्राप्त किया जाए:

cache = {}

def fibonacci(n):
    cache[0] = 0
    cache[1] = 1

    for i in range(2, n + 1):
        cache[i] = cache[i - 1] +  cache[i - 2]

    return cache[n]

हम भी निरंतर स्थान का उपयोग कर सकते हैं और रास्ते में केवल आवश्यक आंशिक परिणामों को संग्रहीत कर सकते हैं:

def fibonacci(n):
  fi_minus_2 = 0
  fi_minus_1 = 1

  for i in range(2, n + 1):
      fi = fi_minus_1 + fi_minus_2
      fi_minus_1, fi_minus_2 = fi, fi_minus_1

  return fi
  • गतिशील प्रोग्रामिंग कैसे लागू करें?

    1. समस्या में पुनरावृत्ति का पता लगाएं।
    2. टॉप-डाउन: प्रत्येक सबप्रॉब्लम के उत्तर को स्टोर करने के लिए उन्हें फिर से रखने से बचने के लिए स्टोर करें।
    3. बॉटम-अप: परिणामों का मूल्यांकन करने के लिए सही क्रम का पता लगाएं ताकि जरूरत पड़ने पर आंशिक परिणाम उपलब्ध हों।

डायनेमिक प्रोग्रामिंग आम तौर पर उन समस्याओं के लिए काम करती है जिनके पास अंतर्निहित, पेड़ या पूर्णांक अनुक्रमों जैसे दाहिने क्रम में बाएं से होते हैं। यदि भोले पुनरावर्ती एल्गोरिथ्म कई बार एक ही सबप्रोब्लेम की गणना नहीं करते हैं, तो गतिशील प्रोग्रामिंग मदद नहीं करेगा।

मैंने तर्क समझने में मदद करने के लिए समस्याओं का एक संग्रह बनाया: https://github.com/tristanguigue/dynamic-programing


3
यह एक बेहतरीन उत्तर है और गितुब पर समस्या संग्रह भी बहुत उपयोगी है। धन्यवाद!
p4sh4

चीजों को स्पष्ट करने की जिज्ञासा से बाहर - आपकी राय में, पुनरावृत्ति संबंध और संस्मरण का उपयोग करते हुए एक पुनरावर्ती कार्यान्वयन गतिशील प्रोग्रामिंग है?
कोडर

स्पष्टीकरण के लिए धन्यवाद। क्या नीचे से ऊपर की ओर एक शर्त गायब है: if n in cacheजैसा कि ऊपर नीचे उदाहरण के साथ या क्या मैं कुछ याद कर रहा हूं।
डेविड जेएन

क्या मैं सही ढंग से समझता हूं कि बाद में पुनरावृत्तियों में उपयोग किए जाने वाले प्रत्येक लूप में गणना किए गए किसी भी लूप को गतिशील प्रोग्रामिंग का एक उदाहरण है?
एलेक्सी

क्या आप अपने द्वारा दी गई व्याख्या के लिए कोई संदर्भ दे सकते हैं, जिसमें टॉप-डाउन और बॉटम-अप विशेष मामले शामिल हैं?
एलेक्सी

37

संस्मरण वह है जब आप एक फ़ंक्शन कॉल के पिछले परिणामों को संग्रहीत करते हैं (एक वास्तविक फ़ंक्शन हमेशा एक ही चीज़ देता है, उसी इनपुट को दिया जाता है)। परिणामों को संग्रहीत करने से पहले यह एल्गोरिथम जटिलता के लिए कोई अंतर नहीं करता है।

रिकर्सियन एक फ़ंक्शन की विधि है, जो आमतौर पर छोटे डेटासेट के साथ होती है। चूंकि अधिकांश पुनरावर्ती कार्यों को समान पुनरावृत्ति कार्यों में परिवर्तित किया जा सकता है, इसलिए यह एल्गोरिथम जटिलता के लिए कोई अंतर नहीं करता है।

डायनेमिक प्रोग्रामिंग आसान-से-हल करने वाली उप-समस्याओं को हल करने और उस से उत्तर बनाने की प्रक्रिया है। अधिकांश डीपी एल्गोरिदम एक लालची एल्गोरिथ्म (यदि मौजूद है) और एक घातांक (सभी संभावनाओं को मानने और सबसे अच्छा एक खोजने के बीच) के चल रहे समय में होगा।

  • डीपी एल्गोरिदम को पुनरावृत्ति के साथ लागू किया जा सकता है, लेकिन उन्हें होना जरूरी नहीं है।
  • डीपी एल्गोरिदम को संस्मरण द्वारा नहीं देखा जा सकता है, क्योंकि प्रत्येक उप-समस्या को केवल एक बार हल किया जाता है (या "हल" फ़ंक्शन कहा जाता है)।

बहुत स्पष्ट रूप से रखा। काश एल्गोरिदम इंस्ट्रक्टर इसे अच्छे से समझा पाते।
केली एस। फ्रेंच

21

यह आपके एल्गोरिथ्म का एक अनुकूलन है जो चल रहे समय में कटौती करता है।

जबकि एक लालची एल्गोरिथ्म को आमतौर पर भोला कहा जाता है , क्योंकि यह डेटा के एक ही सेट पर कई बार चल सकता है, डायनेमिक प्रोग्रामिंग इस नुकसान को आंशिक परिणामों की गहरी समझ के माध्यम से बचाती है जिसे अंतिम समाधान बनाने में मदद करने के लिए संग्रहीत किया जाना चाहिए।

एक सरल उदाहरण एक पेड़ या ग्राफ को केवल नोड्स के माध्यम से ट्रेस करना है जो समाधान के साथ योगदान देगा, या एक टेबल में समाधानों को डाल देगा जो आपने अब तक पाया है आप एक ही नोड को बार-बार ट्रेस करने से बच सकते हैं।

यह एक समस्या का एक उदाहरण है जो UVA के ऑनलाइन जज से डायनामिक प्रोग्रामिंग के लिए अनुकूल है: स्टेप्स स्टेप लैडर।

मैं इस समस्या के विश्लेषण के महत्वपूर्ण हिस्से की त्वरित ब्रीफिंग करने जा रहा हूं, जो कि प्रोग्रामिंग प्रोग्रामिंग चुनौतियों से लिया गया है, मेरा सुझाव है कि आप इसकी जांच करें।

उस समस्या पर एक अच्छी नज़र डालें, अगर हम एक लागत समारोह को परिभाषित करते हैं जो हमें बताता है कि दो तार कितने दूर हैं, तो हम दो प्राकृतिक परिवर्तनों के तीन प्रकारों पर विचार करते हैं:

प्रतिस्थापन - पाठ "t" में एक एकल वर्ण को पैटर्न "s" से भिन्न वर्ण में बदलें, जैसे "शॉट" को "स्पॉट" में बदलना।

निवेशन - टेक्स्ट "t" से मेल खाने के लिए पैटर्न "s" में एक एकल वर्ण डालें, जैसे कि "पहले" को "agog" में बदलना।

विलोपन - पाठ "t" से मेल खाने में पैटर्न "s" से एकल वर्ण को हटाएं, जैसे "घंटे" को "हमारे" में बदलना।

जब हम एक-एक कदम उठाने के लिए इस प्रत्येक ऑपरेशन को सेट करते हैं तो हम दो तारों के बीच की दूरी को परिभाषित करते हैं। तो हम इसकी गणना कैसे करते हैं?

हम एक पुनरावर्ती एल्गोरिथ्म को इस अवलोकन का उपयोग करके परिभाषित कर सकते हैं कि स्ट्रिंग में अंतिम वर्ण या तो मिलान, प्रतिस्थापित, सम्मिलित या हटाया जाना चाहिए। अंतिम संपादन ऑपरेशन में पात्रों को काटकर एक जोड़ी ऑपरेशन छोड़ देता है एक जोड़ी छोटे तारों को छोड़ देता है। आइए और जम्मू, क्रमशः और टी के प्रासंगिक उपसर्ग के अंतिम चरित्र हैं। पिछले ऑपरेशन के बाद मैच / प्रतिस्थापन, सम्मिलन या विलोपन के बाद स्ट्रिंग के अनुरूप तीन जोड़े छोटे तार होते हैं। यदि हम तीन तारों के छोटे तारों को संपादित करने की लागत जानते हैं, तो हम यह तय कर सकते हैं कि कौन सा विकल्प सबसे अच्छा समाधान की ओर जाता है और तदनुसार उस विकल्प को चुनें। हम इस लागत को जान सकते हैं, भयानक चीज़ के माध्यम से जो पुनरावृत्ति है:

#define MATCH 0 /* enumerated type symbol for match */
#define INSERT 1 /* enumerated type symbol for insert */
#define DELETE 2 /* enumerated type symbol for delete */


int string_compare(char *s, char *t, int i, int j)

{

    int k; /* counter */
    int opt[3]; /* cost of the three options */
    int lowest_cost; /* lowest cost */
    if (i == 0) return(j * indel(’ ’));
    if (j == 0) return(i * indel(’ ’));
    opt[MATCH] = string_compare(s,t,i-1,j-1) +
      match(s[i],t[j]);
    opt[INSERT] = string_compare(s,t,i,j-1) +
      indel(t[j]);
    opt[DELETE] = string_compare(s,t,i-1,j) +
      indel(s[i]);
    lowest_cost = opt[MATCH];
    for (k=INSERT; k<=DELETE; k++)
    if (opt[k] < lowest_cost) lowest_cost = opt[k];
    return( lowest_cost );

}

यह एल्गोरिथ्म सही है, लेकिन असंभव भी धीमा है।

हमारे कंप्यूटर पर चल रहा है, दो 11-वर्ण स्ट्रिंग्स की तुलना करने के लिए कई सेकंड लगते हैं, और गणना कभी भी-कभी भी भूमि में गायब हो जाती है।

एल्गोरिथ्म इतना धीमा क्यों है? यह घातीय समय लेता है क्योंकि यह बार-बार और फिर से मूल्यों को फिर से प्रदर्शित करता है। स्ट्रिंग में हर स्थिति में, पुनरावर्तन शाखाएं तीन तरीके से होती हैं, जिसका अर्थ है कि यह कम से कम 3 ^ n - की दर से बढ़ता है, वास्तव में, यहां तक ​​कि ज्यादातर कॉल केवल दो सूचकांकों में से एक को कम करते हैं, न कि दोनों को।

तो हम एल्गोरिदम को व्यावहारिक कैसे बना सकते हैं? महत्वपूर्ण अवलोकन यह है कि इनमें से अधिकांश पुनरावर्ती कॉल उन चीजों की गणना कर रहे हैं जिनकी गणना पहले ही की जा चुकी है।हम कैसे जानते हैं? खैर, वहाँ केवल हो सकता है | · | टी | संभव अद्वितीय पुनरावर्ती कॉल, चूंकि केवल वही हैं जो पुनरावर्ती कॉल के मापदंडों के रूप में सेवा करने के लिए कई अलग-अलग (i, j) जोड़े हैं।

इन (i, j) जोड़ियों में से प्रत्येक के लिए मानों को एक तालिका में संग्रहीत करके, हम उन्हें पुन: प्रकाशित करने से रोक सकते हैं और उन्हें आवश्यकतानुसार देख सकते हैं।

तालिका एक दो आयामी मैट्रिक्स मीटर है जहां प्रत्येक | s | · | t | कोशिकाओं में इस उपप्रकार के इष्टतम समाधान की लागत होती है, साथ ही माता-पिता सूचक बताते हैं कि हमें इस स्थान पर कैसे मिला:

typedef struct {
int cost; /* cost of reaching this cell */
int parent; /* parent cell */
} cell;

cell m[MAXLEN+1][MAXLEN+1]; /* dynamic programming table */

गतिशील प्रोग्रामिंग संस्करण में पुनरावर्ती संस्करण से तीन अंतर हैं।

प्रथम, यह पुनरावर्ती कॉल के बजाय टेबल लुकअप का उपयोग करके अपने मध्यवर्ती मूल्यों को प्राप्त करता है।

** दूसरा, ** यह प्रत्येक सेल के मूल क्षेत्र को अद्यतन करता है, जो हमें बाद में संपादन अनुक्रम को फिर से बनाने में सक्षम करेगा।

** तीसरा, ** तीसरा, यह cell()केवल एम वापस करने के बजाय अधिक सामान्य लक्ष्य फ़ंक्शन का उपयोग करने के लिए यंत्रीकृत है [| s |] | tost .cost यह हमें इस दिनचर्या को समस्याओं के व्यापक वर्ग में लागू करने में सक्षम करेगा।

यहां, सबसे इष्टतम आंशिक परिणामों को इकट्ठा करने के लिए जो कुछ भी होता है उसका एक विशेष विश्लेषण, वह है जो समाधान को "गतिशील" बनाता है।

यहां एक ही समस्या का एक वैकल्पिक, पूर्ण समाधान है। यह एक "गतिशील" भी है, हालांकि इसका निष्पादन अलग है। मेरा सुझाव है कि आप इसे यूवीए के ऑनलाइन जज को सौंपकर समाधान कितना कुशल है, इसकी जांच करें। मुझे आश्चर्यजनक लगता है कि इतनी भारी समस्या को इतनी कुशलता से कैसे निपटाया गया।


क्या वास्तव में गतिशील प्रोग्रामिंग के लिए भंडारण की आवश्यकता है? क्या स्किपिंग का कोई भी काम गतिशील रूप में एक एल्गोरिथ्म को योग्य नहीं करेगा?
नटखट

आप है इष्टतम इकट्ठा करने के लिए चरण दर चरण परिणाम एक एल्गोरिथ्म "डायनामिक" बनाने के लिए। डायनेमिक प्रोग्रामिंग बेलमैन के काम से उपजा है या, यदि आप कहते हैं कि "किसी भी राशि के शब्द को छोड़ना डायनेमिक प्रोग्रामिंग है" तो आप इस शब्द का अवमूल्यन कर रहे हैं, क्योंकि कोई भी सर्च हेयुरिस्टिक डायनामिक प्रोग्रामिंग होगा। en.wikipedia.org/wiki/Dynamic_programming
andandandand

12

गतिशील प्रोग्रामिंग के प्रमुख बिट्स "ओवरलैपिंग उप-समस्याएं" और "इष्टतम उपस्ट्रक्चर" हैं। एक समस्या के इन गुणों का मतलब है कि एक इष्टतम समाधान इसकी उप-समस्याओं के इष्टतम समाधान से बना है। उदाहरण के लिए, सबसे छोटी पथ की समस्याएं इष्टतम उप-संरचना का प्रदर्शन करती हैं। A से C तक का सबसे छोटा रास्ता A से कुछ नोड B का सबसे छोटा रास्ता है और उसके बाद उस नोड B से C तक का सबसे छोटा रास्ता है।

अधिक से अधिक विवरण में, एक छोटी-सी समस्या को हल करने के लिए:

  • शुरुआती नोड से इसे छूने वाले प्रत्येक नोड तक की दूरी खोजें (ए से बी और सी से कहें)
  • उन नोड्स से उन्हें स्पर्श करने वाले नोड्स से दूरी खोजें (बी से डी और ई तक, और सी से ई और एफ तक)
  • अब हम A से E तक के सबसे छोटे मार्ग को जानते हैं: यह कुछ नोड x के लिए कुल्हाड़ी और XE का सबसे छोटा योग है जिसे हमने (या तो B या C) देखा है
  • इस प्रक्रिया को तब तक दोहराएं जब तक हम अंतिम गंतव्य नोड तक नहीं पहुंच जाते

क्योंकि हम नीचे-ऊपर काम कर रहे हैं, हमारे पास पहले से ही उप-समस्याओं के समाधान हैं जब उन्हें याद करने के लिए, उनका उपयोग करने का समय आता है।

याद रखें, डायनेमिक प्रोग्रामिंग समस्याओं में ओवरलैपिंग उप-समस्याएं और इष्टतम सबस्ट्रक्चर दोनों होने चाहिए। फाइबोनैचि अनुक्रम उत्पन्न करना एक गतिशील प्रोग्रामिंग समस्या नहीं है; यह संस्मरण का उपयोग करता है, क्योंकि इसमें उप-समस्याओं का अतिव्यापी होता है, लेकिन इसमें इष्टतम सबस्ट्रक्चर नहीं होता है (क्योंकि कोई अनुकूलन समस्या नहीं है)।


1
IMHO, यह एकमात्र उत्तर है जो गतिशील प्रोग्रामिंग के संदर्भ में समझ में आता है। जब से लोगों ने डीपी को फाइबोनैचि संख्याओं (शायद ही प्रासंगिक) का उपयोग करके समझाना शुरू किया है, मैं उत्सुक हूं।
टेरी ली

@ टेरीली, यह "समझ" बना रहा है, लेकिन इसे समझना आसान नहीं है। फाइबोनैचि संख्या समस्या ज्ञात और समझने में आसान है।
अजय

5

गतिशील प्रोग्रामिंग

परिभाषा

ओवरलैपिंग उप-समस्याओं के साथ समस्याओं को हल करने के लिए डायनामिक प्रोग्रामिंग (डीपी) एक सामान्य एल्गोरिदम डिजाइन तकनीक है। इस तकनीक का आविष्कार अमेरिकी गणितज्ञ "रिचर्ड बेलमैन" ने 1950 के दशक में किया था।

कुंजी विचार

प्रमुख विचार पुनर्संयोजन से बचने के लिए छोटी उप-समस्याओं के अतिव्यापी जवाबों को बचाने के लिए है।

गतिशील प्रोग्रामिंग गुण

  • एक उदाहरण छोटे उदाहरणों के समाधान का उपयोग करके हल किया जाता है।
  • एक छोटे से उदाहरण के लिए कई बार समाधान की आवश्यकता हो सकती है, इसलिए अपने परिणामों को एक तालिका में संग्रहीत करें।
  • इस प्रकार प्रत्येक छोटा उदाहरण केवल एक बार हल किया जाता है।
  • अतिरिक्त स्थान का उपयोग समय बचाने के लिए किया जाता है।

4

मैं डायनेमिक प्रोग्रामिंग (विशेष प्रकार की समस्याओं के लिए एक शक्तिशाली एल्गोरिदम) के लिए बहुत नया हूं

सबसे सरल शब्दों में, बस पिछले ज्ञान का उपयोग करने के साथ एक पुनरावर्ती दृष्टिकोण के रूप में गतिशील प्रोग्रामिंग को सोचें

पिछला ज्ञान यह है कि यहां सबसे ज्यादा क्या मायने रखता है, आपके पास पहले से मौजूद उप-समस्याओं के समाधान का ध्यान रखें।

इस पर विचार करें, विकिपीडिया से dp के लिए सबसे बुनियादी उदाहरण

रिट्रेसमेंट अनुक्रम ढूँढना

function fib(n)   // naive implementation
    if n <=1 return n
    return fib(n − 1) + fib(n − 2)

फ़ंक्शन कॉल को n = 5 के साथ तोड़ दें

fib(5)
fib(4) + fib(3)
(fib(3) + fib(2)) + (fib(2) + fib(1))
((fib(2) + fib(1)) + (fib(1) + fib(0))) + ((fib(1) + fib(0)) + fib(1))
(((fib(1) + fib(0)) + fib(1)) + (fib(1) + fib(0))) + ((fib(1) + fib(0)) + fib(1))

विशेष रूप से, फ़ाइबर (2) की गणना खरोंच से तीन बार की गई थी। बड़े उदाहरणों में, कई और अधिक मूल्य के फ़िब, या उप-समस्याएं, पुनर्गणना की जाती हैं, जो एक घातीय समय एल्गोरिथ्म के लिए अग्रणी हैं।

अब, हम डेटा-संरचना में पहले से पाए गए मान को एक मानचित्र कहते हैं, इसे संग्रहीत करने का प्रयास करते हैं

var m := map(0 → 0, 1 → 1)
function fib(n)
    if key n is not in map m 
        m[n] := fib(n − 1) + fib(n − 2)
    return m[n]

यहां हम मानचित्र में उप-समस्याओं के समाधान को सहेज रहे हैं, अगर हमारे पास यह पहले से नहीं है। मूल्यों को सहेजने की यह तकनीक जिसे हमने पहले से ही गणना की थी, को मेमोइज़ेशन कहा जाता है।

अंत में, एक समस्या के लिए, पहले राज्यों को खोजने का प्रयास करें (संभव उप-समस्याओं और बेहतर पुनरावर्तन दृष्टिकोण के बारे में सोचने की कोशिश करें ताकि आप पिछली उप-समस्या के समाधान का उपयोग आगे कर सकें)।


विकिपीडिया से सीधे चीर-फाड़। Downvoted !!
सॉलिडैक

3

डायनेमिक प्रोग्रामिंग अतिव्यापी समस्याओं के साथ समस्याओं को हल करने के लिए एक तकनीक है। एक गतिशील प्रोग्रामिंग एल्गोरिथ्म बस एक बार हर उप समस्या को हल करता है और फिर एक तालिका (सरणी) में इसका उत्तर बचाता है। हर बार उप-समस्या का सामना करने पर उत्तर की पुन: गणना के काम से बचना। गतिशील प्रोग्रामिंग का अंतर्निहित विचार यह है: आमतौर पर उप समस्याओं के ज्ञात परिणामों की तालिका रखते हुए, एक ही सामान की दो बार गणना करने से बचें।

गतिशील प्रोग्रामिंग एल्गोरिदम के विकास में सात चरण निम्नानुसार हैं:

  1. एक पुनरावर्ती गुण स्थापित करें जो समस्या के एक उदाहरण के लिए समाधान देता है।
  2. पुनरावर्ती गुण के अनुसार एक पुनरावर्ती एल्गोरिथम विकसित करें
  3. देखें कि क्या समस्या का एक ही उदाहरण फिर से पुनरावर्ती कॉल में हल किया जा रहा है
  4. एक पुनरावर्ती एल्गोरिदम विकसित करें
  5. मेमोरी में डेटा स्टोर करने में पैटर्न देखें
  6. पुनरावृत्त एल्गोरिदम को पुनरावृत्त एल्गोरिथ्म में परिवर्तित करें
  7. आवश्यकतानुसार भंडारण (स्टोरेज ऑप्टिमाइज़ेशन) का उपयोग करके पुनरावृत्त एल्गोरिथ्म का अनुकूलन करें

है 6. Convert the memoized recursive algorithm into iterative algorithmएक अनिवार्य कदम? इसका मतलब यह होगा कि इसका अंतिम रूप गैर-पुनरावर्ती है?
truthadjustr

यह अनिवार्य नहीं है, इसका वैकल्पिक
अदनान कुरैशी

इसका उद्देश्य स्टोर किए गए मूल्यों पर पुनरावृत्ति के साथ डेटा को मेमोरी में स्टोर करने के लिए उपयोग किए जाने वाले पुनरावर्ती एल्गोरिदम को बदलना है क्योंकि एक पुनरावृत्त समाधान किए गए प्रत्येक पुनरावर्ती कॉल के लिए फ़ंक्शन स्टैक के निर्माण को बचाता है।
डेविड सी। रंकिन

1

संक्षेप में पुनरावृत्ति ज्ञापन और गतिशील प्रोग्रामिंग के बीच अंतर

डायनामिक प्रोग्रामिंग जैसा कि नाम से पता चलता है कि पिछले गणना मूल्य का उपयोग गतिशील रूप से अगले नए समाधान का निर्माण करने के लिए किया गया है

डायनेमिक प्रोग्रामिंग कहां लागू करें: यदि आप समाधान इष्टतम सबस्ट्रक्चर और ओवरलैपिंग उप समस्या पर आधारित हैं, तो उस स्थिति में पहले की गणना की गई वैल्यू का उपयोग करना उपयोगी होगा, इसलिए आपको इसे रीकंप्यूट करने की आवश्यकता नहीं है। यह नीचे की ओर दृष्टिकोण है। मान लीजिए कि आपको फ़ाइब (n) की गणना करने की आवश्यकता है, उस स्थिति में आपको फ़ाइब (n-1) और फ़ाइबर (n-2) के पिछले परिकलित मान को जोड़ना होगा

पुनरावर्तन: मूल रूप से आप इसे आसानी से हल करने के लिए छोटे हिस्से में समस्या को विभाजित करते हैं, लेकिन इसे ध्यान में रखें कि अगर हम एक ही मूल्य को अन्य पुनरावर्तन कॉल में पहले से गणना नहीं करते हैं तो पुन: गणना से बचें।

संस्मरण: मूल रूप से तालिका में पुरानी गणना की गई पुनरावर्तन मूल्य को संग्रहीत करना संस्मरण के रूप में जाना जाता है जो पुन: गणना से बचता है यदि इसकी पहले से ही कुछ पिछली कॉल द्वारा गणना की जाती है तो किसी भी मूल्य की गणना एक बार की जाएगी। इसलिए गणना करने से पहले हम जांचते हैं कि क्या इस मूल्य की गणना पहले ही की जा चुकी है या नहीं अगर पहले से ही गणना की गई है तो हम पुनर्मिलन के बजाय तालिका से समान लौटाते हैं। यह टॉप अप एप्रोच भी है


-2

यहाँ का एक सरल अजगर कोड उदाहरण है Recursive, Top-down, Bottom-upफाइबोनैचि श्रृंखला के लिए दृष्टिकोण:

पुनरावर्ती: ओ (2 एन )

def fib_recursive(n):
    if n == 1 or n == 2:
        return 1
    else:
        return fib_recursive(n-1) + fib_recursive(n-2)


print(fib_recursive(40))

टॉप-डाउन: O (n) बड़े इनपुट के लिए कुशल

def fib_memoize_or_top_down(n, mem):
    if mem[n] is not 0:
        return mem[n]
    else:
        mem[n] = fib_memoize_or_top_down(n-1, mem) + fib_memoize_or_top_down(n-2, mem)
        return mem[n]


n = 40
mem = [0] * (n+1)
mem[1] = 1
mem[2] = 1
print(fib_memoize_or_top_down(n, mem))

नीचे-ऊपर: O (n) सरलता और छोटे इनपुट आकारों के लिए

def fib_bottom_up(n):
    mem = [0] * (n+1)
    mem[1] = 1
    mem[2] = 1
    if n == 1 or n == 2:
        return 1

    for i in range(3, n+1):
        mem[i] = mem[i-1] + mem[i-2]

    return mem[n]


print(fib_bottom_up(40))

पहला मामला n ^ 2 का नहीं चल रहा है, इसकी समय जटिलता O (2 ^ n) है: stackoverflow.com/questions/360748/…
सैम

अद्यतन धन्यवाद। @ ससम
0xAliHn
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.