Η ουσία των μεθόδων δυναμικού προγραμματισμού. Η έννοια του δυναμικού προγραμματισμού

Οι περισσότερες μέθοδοι έρευνας λειτουργιών συνδέονται κυρίως με εργασίες πολύ συγκεκριμένου περιεχομένου. Η κλασική συσκευή των μαθηματικών αποδείχθηκε ότι ήταν ελάχιστη χρήσιμη για την επίλυση πολλών προβλημάτων βελτιστοποίησης που περιλαμβάνουν μεγάλο αριθμό μεταβλητών και/ή περιορισμών με τη μορφή ανισοτήτων. Δεν υπάρχει αμφιβολία ότι η ιδέα της διαίρεσης ενός προβλήματος μεγάλων διαστάσεων σε υποπροβλήματα μικρότερων διαστάσεων, συμπεριλαμβανομένων λίγων μόνο μεταβλητών, και στη συνέχεια επίλυσης του συνολικού προβλήματος σε μέρη είναι ελκυστική. Σε αυτή την ιδέα βασίζεται η μέθοδος δυναμικού προγραμματισμού.

Ο δυναμικός προγραμματισμός (DP) είναι μια μαθηματική μέθοδος της οποίας η δημιουργία και ανάπτυξη ανήκει κατά κύριο λόγο στον Bellman. Η μέθοδος μπορεί να χρησιμοποιηθεί για την επίλυση ενός πολύ μεγάλου φάσματος προβλημάτων, συμπεριλαμβανομένων προβλημάτων κατανομής πόρων, αντικατάστασης και διαχείρισης αποθεμάτων και προβλημάτων φόρτωσης. Χαρακτηριστικό του δυναμικού προγραμματισμού είναι η προσέγγιση για την επίλυση ενός προβλήματος σε στάδια, καθένα από τα οποία συνδέεται με μία ελεγχόμενη μεταβλητή. Ένα σύνολο επαναλαμβανόμενων (αναστρέψιμων, περιοδικών) υπολογιστικών διαδικασιών που συνδέουν διάφορα στάδια διασφαλίζει ότι επιτυγχάνεται μια αποδεκτή βέλτιστη λύση στο πρόβλημα στο σύνολό του όταν φτάσει στο τελευταίο στάδιο.

Η προέλευση του ονόματος δυναμικός προγραμματισμός σχετίζεται πιθανώς με τη χρήση μεθόδων DP σε προβλήματα λήψης αποφάσεων σε σταθερά χρονικά διαστήματα (για παράδειγμα, σε προβλήματα διαχείρισης αποθεμάτων). Ωστόσο, οι μέθοδοι DP χρησιμοποιούνται επίσης με επιτυχία για την επίλυση προβλημάτων στα οποία δεν λαμβάνεται υπόψη ο παράγοντας χρόνος. Για το λόγο αυτό, ο όρος προγραμματισμός πολλαπλών σταδίων φαίνεται πιο κατάλληλος, αντανακλώντας τη σταδιακή φύση της διαδικασίας επίλυσης ενός προβλήματος.

Η θεμελιώδης αρχή που διέπει τη θεωρία DP είναι η αρχή της βέλτιστης. Ουσιαστικά, καθορίζει τη διαδικασία για μια βήμα προς βήμα λύση ενός προβλήματος που επιτρέπει την αποσύνθεση (αυτός είναι ένας πιο αποδεκτός τρόπος από την άμεση επίλυση του προβλήματος στην αρχική διατύπωση) χρησιμοποιώντας επαναλαμβανόμενες υπολογιστικές διαδικασίες.

Ο δυναμικός προγραμματισμός επιτρέπει τον βέλτιστο σχεδιασμό ελεγχόμενων διαδικασιών. Με τον όρο «ελεγχόμενες» εννοούμε διαδικασίες την πορεία των οποίων μπορούμε να επηρεάσουμε στον έναν ή τον άλλο βαθμό.

Προαπαιτούμενα δυναμικού προγραμματισμού:

  • · Τα χαρακτηριστικά του συστήματος εξαρτώνται μόνο από τη δεδομένη κατάσταση του συστήματος, και όχι από τον τρόπο που το σύστημα έφτασε σε αυτήν την κατάσταση.
  • · Η μετάβαση ενός συστήματος από τη μια κατάσταση στην άλλη διαρκεί έναν ορισμένο πεπερασμένο αριθμό βημάτων.
  • · Κάθε βήμα (Επιλογή συγκεκριμένης λύσης) συνδέεται με ένα συγκεκριμένο αποτέλεσμα (το οικονομικό αποτέλεσμα νοείται ως η αξία της αντικειμενικής συνάρτησης του προβλήματος). Το αποτέλεσμα της απόφασης που λαμβάνεται εξαρτάται από την τρέχουσα κατάσταση στην οποία βρίσκεται το αντικείμενο διαχείρισης και την απόφαση διαχείρισης (επίπτωση) που λαμβάνεται.
  • · Το συνολικό αποτέλεσμα σε πολλά βήματα είναι το άθροισμα των επιδράσεων σε κάθε βήμα.

Η βέλτιστη υποδομή στον δυναμικό προγραμματισμό σημαίνει ότι η βέλτιστη λύση σε μικρότερα υποπροβλήματα μπορεί να χρησιμοποιηθεί για την επίλυση του αρχικού προβλήματος. Για παράδειγμα, η συντομότερη διαδρομή σε ένα γράφημα από μια κορυφή (που συμβολίζεται με s) στην άλλη (που συμβολίζεται με t) μπορεί να βρεθεί ως εξής: πρώτα, υπολογίζουμε το συντομότερο μονοπάτι από όλες τις κορυφές που γειτνιάζουν με το s στο t και, στη συνέχεια, λαμβάνοντας λαμβάνοντας υπόψη τα βάρη των ακμών που συνδέουν το s με γειτονικές κορυφές, επιλέξτε την καλύτερη διαδρομή προς το t (από ποια κορυφή είναι καλύτερο να διασχίσετε). Γενικά, μπορούμε να λύσουμε ένα πρόβλημα που έχει βέλτιστη υποδομή ακολουθώντας τα παρακάτω τρία βήματα.

Ανάλυση μιας εργασίας σε μικρότερες δευτερεύουσες εργασίες.

Η εύρεση της βέλτιστης λύσης στα υποπροβλήματα είναι αναδρομική, χρησιμοποιώντας τον ίδιο αλγόριθμο τριών βημάτων.

Χρήση της ληφθείσας λύσης σε υποπροβλήματα για την κατασκευή μιας λύσης στο αρχικό πρόβλημα.

Οι δευτερεύουσες εργασίες λύνονται χωρίζοντάς τες σε υποεργασίες ακόμη μικρότερου μεγέθους κ.λπ., μέχρι να καταλήξει κανείς στην ασήμαντη περίπτωση ενός προβλήματος που μπορεί να λυθεί σε σταθερό χρόνο (η απάντηση μπορεί να ειπωθεί αμέσως). Για παράδειγμα, αν πρέπει να βρούμε n!, τότε η ασήμαντη εργασία θα ήταν 1! = 1 (ή 0! = 1).

Επικαλυπτόμενα υποπροβλήματα στον δυναμικό προγραμματισμό σημαίνει υποπροβλήματα που χρησιμοποιούνται για την επίλυση ενός αριθμού προβλημάτων (όχι μόνο ενός) μεγαλύτερου μεγέθους (δηλαδή κάνουμε το ίδιο πράγμα πολλές φορές). Ένα εντυπωσιακό παράδειγμα είναι ο υπολογισμός της ακολουθίας Fibonacci, F_3 = F_2 + F_1Και F_4 = F_3 + F_2-- ακόμα και σε μια τόσο ασήμαντη περίπτωση υπολογισμού μόνο δύο αριθμών Fibonacci, έχουμε ήδη υπολογίσει F_2εις διπλούν. Αν συνεχίσουμε παρακάτω και μετρήσουμε F_5, Οτι F_2θα μετρηθούν άλλες δύο φορές, αφού για να υπολογιστεί F_5θα χρειαστεί ξανά F_3Και F_4. Αυτό που συμβαίνει είναι ότι μια απλή αναδρομική προσέγγιση θα χάσει χρόνο στον υπολογισμό λύσεων σε προβλήματα που έχει ήδη λύσει.

Για να αποφύγουμε μια τέτοια εξέλιξη, θα αποθηκεύσουμε τις λύσεις στα υποπροβλήματα που έχουμε ήδη λύσει και όταν χρειαστούμε ξανά τη λύση του υποπροβλήματος, αντί να την υπολογίσουμε ξανά, απλώς θα την ανακτήσουμε από τη μνήμη. Αυτή η προσέγγιση ονομάζεται προσωρινή αποθήκευση. Μπορούμε επίσης να πραγματοποιήσουμε περαιτέρω βελτιστοποιήσεις - για παράδειγμα, εάν είμαστε απολύτως βέβαιοι ότι δεν θα χρειαζόμαστε πλέον τη λύση μιας δευτερεύουσας εργασίας, μπορούμε να την αποβάλουμε από τη μνήμη, ελευθερώνοντάς την για άλλες ανάγκες ή εάν ο επεξεργαστής είναι αδρανής και να ξέρετε ότι τη λύση σε ορισμένες δευτερεύουσες εργασίες που δεν έχουν ακόμη υπολογιστεί, θα χρειαστούμε στο μέλλον, μπορούμε να τις λύσουμε εκ των προτέρων.

Συνοψίζοντας τα παραπάνω, μπορούμε να πούμε ότι ο δυναμικός προγραμματισμός χρησιμοποιεί τις ακόλουθες ιδιότητες του προβλήματος:

  • · επικαλυπτόμενες δευτερεύουσες εργασίες.
  • · βέλτιστη υποδομή.
  • · την ικανότητα απομνημόνευσης λύσεων σε δευτερεύουσες εργασίες που συναντώνται συχνά.

Ο δυναμικός προγραμματισμός ακολουθεί γενικά δύο προσεγγίσεις για την επίλυση προβλημάτων:

  • · Δυναμικός προγραμματισμός από πάνω προς τα κάτω: ένα πρόβλημα αναλύεται σε μικρότερα υποπροβλήματα, αυτά επιλύονται και στη συνέχεια συνδυάζονται για την επίλυση του αρχικού προβλήματος. Η απομνημόνευση χρησιμοποιείται για την επίλυση υποπροβλημάτων που εμφανίζονται συχνά.
  • · Δυναμικός προγραμματισμός από κάτω προς τα πάνω: όλες οι δευτερεύουσες εργασίες που θα χρειαστούν στη συνέχεια για την επίλυση του αρχικού προβλήματος υπολογίζονται εκ των προτέρων και στη συνέχεια χρησιμοποιούνται για την κατασκευή μιας λύσης στο αρχικό πρόβλημα. Αυτή η μέθοδος είναι καλύτερη από τον προγραμματισμό από πάνω προς τα κάτω όσον αφορά το μέγεθος της απαιτούμενης στοίβας και τον αριθμό των κλήσεων συναρτήσεων, αλλά μερικές φορές δεν είναι εύκολο να γνωρίζουμε εκ των προτέρων ποια υποπροβλήματα θα χρειαστεί να επιλύσουμε αργότερα.

Οι γλώσσες προγραμματισμού μπορούν να θυμούνται το αποτέλεσμα μιας κλήσης συνάρτησης με ένα συγκεκριμένο σύνολο ορισμάτων (απομνημόνευση) για να επιταχύνουν την "αξιολόγηση κατά όνομα". Ορισμένες γλώσσες έχουν αυτό το χαρακτηριστικό ενσωματωμένο (για παράδειγμα, Scheme, Common Lisp, Perl) και ορισμένες απαιτούν πρόσθετες επεκτάσεις (C++).

Υπάρχει ο σειριακός δυναμικός προγραμματισμός, ο οποίος περιλαμβάνεται σε όλα τα εγχειρίδια για την έρευνα στη λειτουργία, και ο μη σειριακός δυναμικός προγραμματισμός (NSDP), ο οποίος είναι σήμερα ελάχιστα γνωστός, αν και ανακαλύφθηκε τη δεκαετία του 1960.

Ο συνηθισμένος δυναμικός προγραμματισμός είναι μια ειδική περίπτωση μη σειριακού δυναμικού προγραμματισμού, όταν το γράφημα των σχέσεων μεταβλητών είναι απλώς μια διαδρομή. Το NSDP, ως μια φυσική και γενική μέθοδος για τη συνεκτίμηση της δομής ενός προβλήματος βελτιστοποίησης, θεωρεί ένα σύνολο περιορισμών και/ή μια αντικειμενική συνάρτηση ως αναδρομικά υπολογίσιμη συνάρτηση. Αυτό σας επιτρέπει να βρείτε μια λύση σταδιακά, σε κάθε στάδιο χρησιμοποιώντας πληροφορίες που ελήφθησαν στα προηγούμενα στάδια, και η αποτελεσματικότητα αυτού του αλγορίθμου εξαρτάται άμεσα από τη δομή του γραφήματος των σχέσεων μεταξύ των μεταβλητών. Εάν αυτό το γράφημα είναι αρκετά αραιό, τότε το ποσό του υπολογισμού σε κάθε στάδιο μπορεί να διατηρηθεί εντός λογικών ορίων.

Μία από τις κύριες ιδιότητες των προβλημάτων που επιλύονται με χρήση δυναμικού προγραμματισμού είναι η προσθετικότητα. Τα μη προσθετικά προβλήματα επιλύονται με άλλες μεθόδους. Για παράδειγμα, πολλά προβλήματα βελτιστοποίησης των επενδύσεων μιας εταιρείας δεν είναι πρόσθετα και επιλύονται συγκρίνοντας την αξία της εταιρείας με και χωρίς επενδύσεις.

Ο δυναμικός προγραμματισμός είναι ένα θέμα στο οποίο είναι αφιερωμένα αρκετά άρθρα στο RuNet, οπότε αποφασίσαμε να το αντιμετωπίσουμε. Αυτό το άρθρο θα αναλύσει τα κλασικά προβλήματα σε ακολουθίες, τη μονοδιάστατη και τη δισδιάστατη δυναμική, θα παράσχει τη λογική για λύσεις και θα περιγράψει διαφορετικές προσεγγίσεις για την εφαρμογή τους. Όλος ο κώδικας που δίνεται στο άρθρο είναι γραμμένος σε Java.

Για τι πράγμα μιλάμε; Τι είναι ο δυναμικός προγραμματισμός;

Μια μέθοδος για την επίλυση ενός προβλήματος με τη διαίρεση του σε πολλές πανομοιότυπες δευτερεύουσες εργασίες που διασυνδέονται περιοδικά. Το απλούστερο παράδειγμα θα ήταν οι αριθμοί Fibonacci - για να υπολογίσουμε έναν συγκεκριμένο αριθμό σε αυτήν την ακολουθία, πρέπει πρώτα να υπολογίσουμε τον τρίτο αριθμό προσθέτοντας τους δύο πρώτους και μετά τον τέταρτο με τον ίδιο τρόπομε βάση το δεύτερο και το τρίτο, και ούτω καθεξής (ναι, ακούσαμε για την κλειστή φόρμουλα).

Εντάξει, πώς να το χρησιμοποιήσετε;

Η λύση ενός προβλήματος με δυναμικό προγραμματισμό θα πρέπει να περιέχει τα ακόλουθα:

Χρειάζεται λοιπόν να γράψω μια αναδρομική μέθοδο για να λύσω αυτό το πρόβλημα; Άκουσα ότι είναι αργοί.

Φυσικά, δεν είναι απαραίτητο, υπάρχουν άλλες προσεγγίσεις για την εφαρμογή της δυναμικής. Ας τα δούμε χρησιμοποιώντας το παρακάτω πρόβλημα ως παράδειγμα:

Υπολογίστε τον nο όρο της ακολουθίας που δίνεται από τους τύπους:
a 2n = a n + a n-1,
a 2n+1 = a n - a n-1,
a 0 = a 1 = 1.

Ιδέα λύσης

Εδώ μας δίνονται και οι αρχικές καταστάσεις (a 0 = a 1 = 1) και οι εξαρτήσεις. Η μόνη δυσκολία που μπορεί να προκύψει είναι η κατανόηση ότι το 2n είναι η προϋπόθεση για την άρτια κατάσταση ενός αριθμού και το 2n+1 είναι η προϋπόθεση για την περιττότητα. Με άλλα λόγια, πρέπει να ελέγξουμε αν ένας αριθμός είναι άρτιος και να τον μετρήσουμε σύμφωνα με αυτό χρησιμοποιώντας διάφορους τύπους.

Αναδρομική λύση

Η προφανής υλοποίηση είναι να γράψετε την ακόλουθη μέθοδο:

Ιδιωτικό static int f(int n)( if(n==0 || n==1) return 1; // Έλεγχος για αρχική τιμή if(n%2==0)( //Έλεγχος για ισοτιμία επιστροφή f(n /2)+f(n/2-1); -1) /2-1) // Υπολογίστε χρησιμοποιώντας τον τύπο για μονούς //δείκτες, με αναφορά σε προηγούμενες τιμές )

Και λειτουργεί εξαιρετικά, αλλά υπάρχουν κάποιες αποχρώσεις. Αν θέλουμε να υπολογίσουμε την f(12) , τότε η μέθοδος θα υπολογίσει το άθροισμα f(6)+f(5) . Ταυτόχρονα, f(6)=f(3)+f(2) και f(5)=f(2)-f(1), δηλ. θα υπολογίσουμε την τιμή της f(2) δύο φορές. Υπάρχει μια λύση σε αυτό - απομνημόνευση (αποθήκευση τιμών).

Αναδρομική λύση με προσωρινή αποθήκευση τιμών

Η ιδέα της απομνημόνευσης είναι πολύ απλή - μόλις υπολογίσουμε μια τιμή, την εισάγουμε σε κάποιο είδος δομής δεδομένων. Πριν από κάθε υπολογισμό, ελέγχουμε αν η υπολογιζόμενη τιμή βρίσκεται σε αυτή τη δομή και αν ναι, τη χρησιμοποιούμε. Ένας πίνακας γεμάτος με τιμές σημαίας μπορεί να χρησιμοποιηθεί ως δομή δεδομένων. Εάν η τιμή του στοιχείου στον δείκτη N είναι ίση με την τιμή της σημαίας, τότε δεν την έχουμε υπολογίσει ακόμα. Αυτό δημιουργεί ορισμένες δυσκολίες, γιατί η τιμή της σημαίας δεν πρέπει να ανήκει στο σύνολο των τιμών συνάρτησης, κάτι που δεν είναι πάντα προφανές. Προσωπικά, προτιμώ να χρησιμοποιώ έναν πίνακα κατακερματισμού - όλες οι λειτουργίες σε αυτόν εκτελούνται σε O(1), το οποίο είναι πολύ βολικό. Ωστόσο, με μεγάλο αριθμό τιμών, δύο αριθμοί μπορεί να έχουν τον ίδιο κατακερματισμό, κάτι που φυσικά προκαλεί προβλήματα. Σε αυτή την περίπτωση, θα πρέπει να χρησιμοποιήσετε, για παράδειγμα, κόκκινο-μαύρο ξύλο.

Για την ήδη γραμμένη συνάρτηση f(int), οι τιμές προσωρινής αποθήκευσης θα μοιάζουν με αυτό:

Ιδιωτικό στατικό HashMap cache = νέο HashMap () private static int fcashe(int n)( if(!cache.containsKey(n))(//Ελέγξτε αν βρήκαμε αυτήν την τιμή cache.put(n, f(n)); //Εάν όχι, τότε βρείτε και γράψτε στο πίνακας ) επιστρέφει cache.get(n)

Όχι πολύ δύσκολο, συμφωνείτε; Αλλά αυτό εξαλείφει έναν τεράστιο αριθμό λειτουργιών. Πληρώνετε για αυτό με επιπλέον κατανάλωση μνήμης.

Διαδοχικός υπολογισμός

Τώρα ας πάμε πίσω από εκεί που ξεκινήσαμε - η αναδρομή είναι αργή. Όχι τόσο αργό που θα προκαλούσε πραγματικό πρόβλημα στην πραγματική ζωή, αλλά σε έναν αγώνα αθλητικού προγραμματισμού, κάθε χιλιοστό του δευτερολέπτου μετράει.

Η μέθοδος διαδοχικού υπολογισμού είναι κατάλληλη μόνο εάν η συνάρτηση αναφέρεται αποκλειστικά στα στοιχεία πριν από αυτήν - αυτό είναι το κύριο, αλλά όχι το μόνο μειονέκτημά της. Το πρόβλημά μας ικανοποιεί αυτήν την προϋπόθεση.

Η ουσία της μεθόδου είναι η εξής: δημιουργούμε έναν πίνακα N στοιχείων και τον γεμίζουμε διαδοχικά με τιμές. Πιθανώς ήδη μαντέψατε ότι με αυτόν τον τρόπο μπορούμε επίσης να υπολογίσουμε εκείνες τις τιμές που δεν χρειάζονται για την απάντηση. Σε ένα σημαντικό μέρος των δυναμικών προβλημάτων, αυτό το γεγονός μπορεί να παραλειφθεί, αφού πολλές φορές χρειάζονται όλες οι τιμές για την απάντηση. Για παράδειγμα, όταν αναζητούμε τη συντομότερη διαδρομή, δεν μπορούμε παρά να υπολογίσουμε τη διαδρομή σε κάποιο σημείο που πρέπει να επανεξετάσουμε όλες τις επιλογές. Αλλά στο πρόβλημά μας πρέπει να υπολογίσουμε περίπου τιμές log 2 (N) (στην πράξη περισσότερες), για το 922337203685477580ο στοιχείο (MaxLong/10) θα χρειαστούμε 172 υπολογισμούς.

Ιδιωτικό στατικό int f(int n)( if(n<2) return 1; //Может, нам и вычислять ничего не нужно? int fs = int[n]; //Создаём массив для значений fs=fs=1; //Задаём начальные состояния for(int i=2; i

Ένα άλλο μειονέκτημα αυτής της προσέγγισης είναι η σχετικά μεγάλη κατανάλωση μνήμης.

Δημιουργία Στοίβας Ευρετηρίου

Τώρα πρέπει ουσιαστικά να γράψουμε τη δική μας αναδρομή. Η ιδέα είναι η εξής - πρώτα «κατεβαίνουμε» από το Ν στις αρχικές καταστάσεις, θυμόμαστε τα ορίσματα από τα οποία θα χρειαστεί να υπολογίσουμε τη συνάρτηση. Στη συνέχεια επιστρέφουμε "επάνω", υπολογίζοντας διαδοχικά τις τιμές από αυτά τα ορίσματα, με τη σειρά που καταγράψαμε.

Οι εξαρτήσεις υπολογίζονται ως εξής:

LinkedList στοίβα = νέα LinkedList () stack.add(n); (Συνδεδεμένη λίστα ουρά = νέα LinkedList () //Ευρετήρια αποθήκευσης για τα οποία οι εξαρτήσεις δεν έχουν ακόμη υπολογιστεί ουρά.add(n); int dum? while(queue.size()>0)( //Εφόσον υπάρχει κάτι για υπολογισμό dum = queue.removeFirst(); if(dum%2==0)( //Έλεγχος ισοτιμίας if(dum/2>1 )( // Εάν η υπολογισμένη εξάρτηση δεν ανήκει στις αρχικές καταστάσεις stack.addLast(dum/2); //Add to the stack queue.add(dum/2); //Save to //υπολογίστε περαιτέρω εξαρτήσεις ) εάν (dum/2-1>1 )( //Έλεγχος αν ανήκουν στις αρχικές καταστάσεις stack.addLast(dum/2-1); //Add to the stack queue.add(dum/2-1); / /Αποθήκευση στο //υπολογισμός περαιτέρω εξαρτήσεων ) )else( if((dum-1)/2>1)( //Έλεγχος που ανήκει στην αρχική κατάσταση stack.addLast((dum-1)/2); //Προσθήκη ουράς .add((dum-1)/2) στη στοίβα ; //Αποθήκευση στο //υπολογίστε περαιτέρω εξαρτήσεις ) if((dum-1)/2-1>1)( //Έλεγχος που ανήκει στην αρχική στοίβα καταστάσεων. addLast((dum-1)/2-1) / / Προσθήκη στην ουρά στοίβας εργασία, υπάρχει ένας πιο κομψός τρόπος για να βρείτε όλες τις εξαρτήσεις, αλλά ένας αρκετά καθολικός εμφανίζεται εδώ */ ) )

Το μέγεθος στοίβας που προκύπτει είναι πόσοι υπολογισμοί πρέπει να κάνουμε. Έτσι πήρα τον αριθμό 172 που αναφέραμε παραπάνω.

Τώρα εξάγουμε τους δείκτες έναν προς έναν και υπολογίζουμε τις τιμές για αυτούς χρησιμοποιώντας τύπους - είναι εγγυημένο ότι όλες οι απαραίτητες τιμές θα έχουν ήδη υπολογιστεί. Θα το αποθηκεύσουμε όπως πριν - σε έναν πίνακα κατακερματισμού.

HashMap τιμές = νέο HashMap () values.put(0,1); //Είναι σημαντικό να προσθέσετε τις αρχικές καταστάσεις //στον πίνακα τιμών values.put(1,1); while(stack.size()>0)( int num = stack.removeLast(); if(!values.containsKey(num))( //Θα πρέπει να θυμάστε αυτήν την κατασκευή //από την παράγραφο σχετικά με την προσωρινή αποθήκευση if(num%2 = =0)( //Ελέγξτε την τιμή int ισοτιμίας = values.get(num/2)+values.get(num/2-1); //Υπολογίστε τις τιμές values.add(num, value); //Place it στον πίνακα )else( int value = values.get((num-1)/2)-values.get((num-1)/2-1); //Υπολογίστε τις τιμές values.add(num, value //Τοποθετήστε το στον πίνακα) )

Έχουν υπολογιστεί όλες οι απαραίτητες τιμές, το μόνο που μένει είναι να γράψουμε

Επιστροφή values.get(n);

Φυσικά, αυτή η λύση είναι πολύ πιο χρονοβόρα, αλλά αξίζει τον κόπο.

Εντάξει, τα μαθηματικά είναι όμορφα. Τι γίνεται με τις εργασίες στις οποίες δεν δίνονται τα πάντα;

Για μεγαλύτερη σαφήνεια, ας αναλύσουμε το ακόλουθο πρόβλημα για τη μονοδιάστατη δυναμική:

Στην κορυφή μιας σκάλας που περιέχει N σκαλοπάτια υπάρχει μια μπάλα, η οποία αρχίζει να τα πηδά προς τα κάτω. Η μπάλα μπορεί να μεταπηδήσει στο επόμενο βήμα, ένα βήμα αργότερα ή δύο βήματα αργότερα (δηλαδή, εάν η μπάλα είναι στο 8ο βήμα, τότε μπορεί να μετακινηθεί στο 5ο, 6ο ή 7ο.) Προσδιορίστε τον αριθμό των πιθανών ". διαδρομές» της μπάλας από την κορυφή στο έδαφος.

Ιδέα λύσης

Μπορείτε να φτάσετε στο πρώτο βήμα μόνο με έναν τρόπο - κάνοντας ένα άλμα με μήκος ίσο με ένα. Μπορείτε να φτάσετε στο δεύτερο βήμα κάνοντας ένα άλμα μήκους 2 ή από το πρώτο βήμα - μόνο 2 επιλογές. Μπορείτε να φτάσετε στο τρίτο σκαλί κάνοντας ένα άλμα τριών μήκους, από τα πρώτα ή τρία βήματα. Εκείνοι. μόνο 4 επιλογές (0->3; 0->1->3; 0->2->3; 0->1->2->3). Τώρα ας δούμε το τέταρτο βήμα. Μπορείτε να φτάσετε σε αυτό από το πρώτο βήμα - μία διαδρομή για κάθε διαδρομή πριν από αυτό, από το δεύτερο ή από το τρίτο - παρόμοια. Με άλλα λόγια, ο αριθμός των μονοπατιών προς το 4ο βήμα είναι το άθροισμα των διαδρομών προς το 1ο, 2ο και 3ο βήμα. Από μαθηματική άποψη, F(N) = F(N-1)+F(N-2)+F(N-3) . Τα πρώτα τρία βήματα θα θεωρηθούν οι αρχικές καταστάσεις.

Υλοποίηση μέσω αναδρομής

private static int f(int n)( if(n==1) return 1; if(n==2) return 2; if(n==3) return 4; return f(n-1)+f(n -2)+f(n-3)

Δεν υπάρχει τίποτα δύσκολο εδώ.

Με βάση το γεγονός ότι, σε γενικές γραμμές, μια απλή λύση σε έναν πίνακα N στοιχείων είναι προφανής, θα δείξω εδώ μια λύση σε έναν πίνακα μόνο τριών.

Int vars = new int; vars=1;vars=2;vars=4; for(int i=3; i

Δεδομένου ότι κάθε επόμενη τιμή εξαρτάται μόνο από τις τρεις προηγούμενες, καμία τιμή κάτω από τον δείκτη μικρότερη από το i-3 δεν θα ήταν χρήσιμη για εμάς. Στον παραπάνω κώδικα, γράφουμε μια νέα τιμή στη θέση της παλαιότερης, η οποία δεν χρειάζεται πλέον. Η ανακύκλωση του υπολοίπου της διαίρεσης με το 3 μας βοηθά να αποφύγουμε ένα σωρό προτάσεις υπό όρους. Απλό, συμπαγές, κομψό.

Γράφτηκε στην κορυφή για κάποιο είδος δισδιάστατης δυναμικής;..

Δεν υπάρχουν ειδικά χαρακτηριστικά που να σχετίζονται με τη δισδιάστατη δυναμική, αλλά, για κάθε περίπτωση, θα εξετάσω εδώ ένα πρόβλημα για αυτό.

Στον ορθογώνιο πίνακα NxM, στην αρχή η συσκευή αναπαραγωγής βρίσκεται στο επάνω αριστερό κελί. Σε μία κίνηση, επιτρέπεται να μετακινηθεί σε διπλανό κελί είτε προς τα δεξιά είτε προς τα κάτω (απαγορεύεται η κίνηση προς τα αριστερά και προς τα πάνω). Μετρήστε πόσους τρόπους έχει ένας παίκτης για να μπει στο κάτω δεξιό κελί.

Ιδέα λύσης

Η λογική της λύσης είναι εντελώς πανομοιότυπη με αυτή στο πρόβλημα σχετικά με τη μπάλα και τη σκάλα - μόνο τώρα μπορείτε να φτάσετε στο κελί (x,y) από τα κελιά (x-1,y) ή (x, y-1). Σύνολο F(x,y) = F(x-1, y)+F(x,y-1) . Επιπλέον, μπορείτε να καταλάβετε ότι όλα τα κελιά της μορφής (1,y) και (x,1) έχουν μόνο μία διαδρομή - ευθεία προς τα κάτω ή ευθεία προς τα δεξιά.

Υλοποίηση μέσω αναδρομής

Για όνομα του παραδείσου, μην κάνετε 2D δυναμικές μέσω αναδρομής. Έχει ήδη αναφερθεί ότι η αναδρομή είναι λιγότερο αποτελεσματική από έναν βρόχο όσον αφορά την απόδοση, επομένως η δισδιάστατη αναδρομή είναι επίσης τρομερή στην ανάγνωση. Μόνο σε ένα τόσο απλό παράδειγμα φαίνεται εύκολο και ακίνδυνο.

Ιδιωτικό στατικό int f(int i, int j) ( if(i==1 || j==1) επιστροφή 1; επιστροφή f(i-1, j)+f(i, j-1); )

Υλοποίηση μέσω μιας σειράς τιμών

int dp = νέο int; for(int i=0; i Μια κλασική λύση που χρησιμοποιεί δυναμική, τίποτα ασυνήθιστο - ελέγχουμε αν ένα κελί είναι ακμή και ορίζουμε την τιμή του με βάση τα γειτονικά κελιά.

Μπράβο, τα καταλαβαίνω όλα. Πώς πρέπει να δοκιμάσω τις δεξιότητές μου;

Εν κατακλείδι, θα δώσω μια σειρά από τυπικά προβλήματα για μονοδιάστατες και δισδιάστατες δυναμικές αναλύσεις.

Κίνδυνος έκρηξης

Κατά την επεξεργασία ραδιενεργών υλικών, παράγονται δύο τύποι αποβλήτων - άκρως επικίνδυνα (τύπου Α) και μη επικίνδυνα (τύπου Β). Τα ίδια δοχεία χρησιμοποιούνται για την αποθήκευση τους. Μετά την τοποθέτηση των απορριμμάτων σε δοχεία, τα τελευταία στοιβάζονται κάθετα. Μια στοίβα θεωρείται εκρηκτική εάν περιέχει περισσότερα από ένα δοχεία τύπου Α στη σειρά Μια στοίβα θεωρείται ασφαλής εάν δεν είναι εκρηκτική. Για έναν δεδομένο αριθμό δοχείων N, προσδιορίστε τον αριθμό των πιθανών τύπων ασφαλών στοίβων.

Λύση

Η απάντηση είναι ο (N+1)ο αριθμός Fibonacci. Ήταν δυνατό να μαντέψει κανείς απλά υπολογίζοντας τις πρώτες 2-3 τιμές. Ήταν δυνατό να το αποδείξουμε αυστηρά κατασκευάζοντας ένα δέντρο πιθανών κατασκευών.


Κάθε κύριο στοιχείο χωρίζεται σε δύο - κύριο (που τελειώνει με Β) και δευτερεύον (λήγει με Α). Τα πλευρικά στοιχεία μετατρέπονται σε κύρια σε μία επανάληψη (μόνο το Β μπορεί να προστεθεί σε μια ακολουθία που τελειώνει με Α). Αυτό είναι χαρακτηριστικό για τους αριθμούς Fibonacci.

Εκτέλεση

Για παράδειγμα, όπως αυτό:

//Εισαγωγή του αριθμού N από το πληκτρολόγιο N+=2; BigInteger fib = νέος BigInteger; fib=fib=BigInteger.ONE; for(int i=2; i

Ανεβαίνω σκάλες

Το αγόρι πλησίασε τις σκάλες των διοδίων. Για να πατήσετε σε οποιοδήποτε σκαλοπάτι, πρέπει να πληρώσετε το ποσό που αναγράφεται σε αυτό. Το αγόρι ξέρει πώς να κάνει το επόμενο βήμα ή να πηδήξει πάνω από ένα σκαλοπάτι. Πρέπει να μάθετε ποιο είναι το μικρότερο ποσό που θα χρειαστεί το αγόρι για να φτάσει στο κορυφαίο σκαλί.

Λύση

Προφανώς, το ποσό που θα δώσει το αγόρι στο Νο βήμα είναι το ποσό που έδωσε πριν συν το κόστος του ίδιου του βήματος. "Το ποσό που έδωσε πριν" εξαρτάται από το από ποιο βήμα το αγόρι πηγαίνει στο Νο - από το (Ν-1) ή από το (Ν-2)ο. Πρέπει να επιλέξετε το μικρότερο.

Εκτέλεση

Για παράδειγμα, όπως αυτό:

Int Imax; //*Εισαγάγετε τον αριθμό των βημάτων από το πληκτρολόγιο* DP = new int; for(int i=0; i

Αριθμομηχανή

Υπάρχει μια αριθμομηχανή που εκτελεί τρεις λειτουργίες:

  • Προσθέστε ένα στον αριθμό Χ.
  • Πολλαπλασιάστε τον αριθμό X με 2.
  • Πολλαπλασιάστε τον αριθμό Χ με 3.

Προσδιορίστε τον ελάχιστο αριθμό πράξεων που απαιτούνται για να ληφθεί ο δεδομένος αριθμός N από τον αριθμό 1. Εκτυπώστε αυτόν τον αριθμό και, στην επόμενη γραμμή, ένα σύνολο εκτελεσμένων πράξεων της μορφής "111231".

Λύση

Η απλή λύση είναι να διαιρέσουμε τον αριθμό με το 3 όσο το δυνατόν περισσότερο, διαφορετικά με το 2 αν είναι δυνατόν, διαφορετικά να αφαιρέσουμε ένα και ούτω καθεξής μέχρι να γίνει ένα. Αυτή είναι η λάθος απόφαση, γιατί... εξαλείφει, για παράδειγμα, τη δυνατότητα αφαίρεσης ενός αριθμού με ένα και στη συνέχεια διαίρεσης με το τρία, κάτι που θα προκαλούσε σφάλματα σε μεγάλους αριθμούς (όπως το 32718).

Η σωστή λύση είναι να βρείτε για κάθε αριθμό από 2 έως N τον ελάχιστο αριθμό ενεργειών με βάση τα προηγούμενα στοιχεία, με άλλα λόγια: F(N) = min(F(N-1), F(N/2), F (N/3) ) + 1 . Θα πρέπει να θυμόμαστε ότι όλοι οι δείκτες πρέπει να είναι ακέραιοι.

Για να δημιουργήσετε ξανά τη λίστα των ενεργειών, πρέπει να πάτε προς την αντίθετη κατεύθυνση και να αναζητήσετε έναν δείκτη i έτσι ώστε F(i)=F(N), όπου N είναι ο αριθμός του εν λόγω στοιχείου. Αν i=N-1, γράψτε 1 στην αρχή της γραμμής, αν i=N/2 - δύο, διαφορετικά - τρία.

Εκτέλεση
int N; //Είσοδος πληκτρολογίου int a = new int; a= 0; ( int min; for(int i=2; i 1)( if(a[i]==a+1)( ret.insert(0, 1); i--; συνέχεια; ) if(i%2==0&&a[i]==a+1)( ret.insert(0, 2); System.out.println(ret);

Ο φθηνότερος τρόπος

Κάθε κελί του ορθογώνιου πίνακα N*M περιέχει έναν αριθμό. Αρχικά, ο παίκτης βρίσκεται στο επάνω αριστερό κελί. Σε μία κίνηση, επιτρέπεται να μετακινηθεί σε διπλανό κελί είτε προς τα δεξιά είτε προς τα κάτω (απαγορεύεται η κίνηση προς τα αριστερά και προς τα πάνω). Όταν περνάει από ένα κελί, ο παίκτης παίρνει τόσα κιλά φαγητού όσα και ο αριθμός που αναγράφεται σε αυτό το κελί (τροφή λαμβάνεται επίσης για το πρώτο και το τελευταίο κελί της διαδρομής του).

Πρέπει να βρείτε το ελάχιστο βάρος φαγητού σε κιλά, δίνοντας το οποίο ο παίκτης μπορεί να μπει στην κάτω δεξιά γωνία.

Το μάθημα θα εξετάσει την έννοια του δυναμικού προγραμματισμού και την ιστορική πτυχή της εμφάνισής του. Εξετάζονται προβλήματα δυναμικού προγραμματισμού και μερικά παραδείγματα των λύσεών τους.


Η έννοια του «δυναμικού προγραμματισμού» χρησιμοποιήθηκε για πρώτη φορά τη δεκαετία του 1940 από τον Richard Bellman για να περιγράψει τη διαδικασία εύρεσης λύσης σε ένα πρόβλημα όπου η απάντηση σε ένα πρόβλημα μπορεί να ληφθεί μόνο αφού λυθεί ένα άλλο πρόβλημα που «προηγείται».
Έτσι, ο Αμερικανός μαθηματικός και ένας από τους κορυφαίους ειδικούς στον τομέα των μαθηματικών και της τεχνολογίας υπολογιστών - Ρίτσαρντ Ερνστ Μπέλμαν- έγινε ο πατέρας του δυναμικού προγραμματισμού.

Αργότερα, η διατύπωση της έννοιας βελτιώθηκε και βελτιώθηκε στη σύγχρονη μορφή της από τον ίδιο τον Bellman.

Η λέξη "προγραμματισμός"στο πλαίσιο του "δυναμικού προγραμματισμού" στην πραγματικότητα αναφέρεται στην κλασική κατανόηση του προγραμματισμού (γραφή κώδικα σε μια γλώσσα προγραμματισμού) δεν έχει ουσιαστικά καμία σχέση με αυτό. Η λέξη «Προγραμματισμός» έχει την ίδια σημασία με τη φράση «μαθηματικός προγραμματισμός», η οποία είναι συνώνυμη με τη λέξη «βελτιστοποίηση».

Να γιατί προγράμματαθα χρησιμοποιηθεί ως η βέλτιστη ακολουθία ενεργειών για να βρεθεί λύση στο πρόβλημα.

Γενικά, για αρχή, άτυπος ορισμός του δυναμικού προγραμματισμούμπορεί να ακούγεται έτσι:

Δυναμικός προγραμματισμόςείναι μια τεχνική ή μέθοδος που σας επιτρέπει να λύσετε ορισμένα προβλήματα συνδυαστικής, βελτιστοποίησης και άλλων προβλημάτων που έχουν μια συγκεκριμένη ιδιότητα (την ιδιότητα της συν-βελτιστοποίησης για δευτερεύουσες εργασίες).

Προβλήματα βελτιστοποίησης, κατά κανόνα, σχετίζονται με το έργο της μεγιστοποίησης ή ελαχιστοποίησης μιας ή άλλης αντικειμενικής συνάρτησης (για παράδειγμα, μεγιστοποίηση της πιθανότητας να μην σπάσει το σύστημα, μεγιστοποίηση του αναμενόμενου κέρδους κ.λπ.).

Προβλήματα συνδυαστικής, κατά κανόνα, απαντήστε στην ερώτηση πόσα αντικείμενα υπάρχουν που έχουν συγκεκριμένες ιδιότητες ή πόσα συνδυαστικά αντικείμενα υπάρχουν που έχουν δώσει ιδιότητες.

Δηλαδή, το DP δεν λύνει όλα τα προβλήματα, αλλά μόνο ορισμένα, μια συγκεκριμένη κατηγορία δευτερευουσών εργασιών. Αλλά αυτή η κατηγορία δευτερεύουσας εργασίας χρησιμοποιείται σε πολλά γνωστικά πεδία: προγραμματισμός, μαθηματικά, γλωσσολογία, στατιστική, θεωρία παιγνίων, οικονομία, επιστήμη υπολογιστών κ.λπ.

Τα προβλήματα που επιλύονται με χρήση δυναμικού προγραμματισμού πρέπει να έχουν ιδιότητα συναξιότητας, το οποίο θα συζητηθεί σε επόμενα μαθήματα.

Μια άτυπη εξήγηση της ιδιότητας βελτιστοποίησης των υποπροβλημάτων μπορεί να αποδειχθεί χρησιμοποιώντας ένα διάγραμμα:
Υπάρχει ένα πρόβλημα που θέλουμε να λύσουμε χρησιμοποιώντας DP, π.χ. βρείτε κάποιο σχέδιο για να το λύσετε. Ας πούμε ότι αυτό το πρόβλημα είναι περίπλοκο και δεν μπορούμε να το λύσουμε αμέσως. Παίρνουμε ένα μικρό υποπρόβλημα και το λύνουμε πρώτα (για x1). Στη συνέχεια, χρησιμοποιώντας αυτή τη μικρή λύση x1, και χωρίς να αλλάξουμε τη δομή αυτής της λύσης, λύνουμε το επόμενο πρόβλημα με τα x1 και x2. Και τα λοιπά.

Ρύζι. 1.1. Μια άτυπη εξήγηση της ιδιότητας βελτιστοποίησης των υποπροβλημάτων

Η άτυπη εξήγηση συζητείται με περισσότερες λεπτομέρειες.

Παραδείγματα προβλημάτων που λύθηκαν με χρήση δυναμικού προγραμματισμού

Αρχικά, εξετάστε τα προβλήματα βελτιστοποίησης (εργασίες 1-5):

  1. Διαδρομή βέλτιστου μήκους
  2. Παράδειγμα:Υπάρχει κάποιος οδικός χάρτης που παρουσιάζεται με τη μορφή γραφήματος. Στόχος μας: να βγούμε από το σημείο ΕΝΑστο σημείο σι. Αυτό πρέπει να γίνει με τέτοιο τρόπο ώστε να ελαχιστοποιείται η απόσταση ή η σπατάλη καυσίμου.

    Αντικειμενική λειτουργίαεδώ είναι η απόσταση από ΕΝΑπριν σι. Εκείνοι. στόχος μας είναι να ελαχιστοποιήσουμε την απόσταση.

    Και τι είναι μεταβλητή επιλογής? Για να βρεις το συντομότερο μονοπάτι, πρέπει να παίρνεις αποφάσεις κάθε φορά. Εκείνοι. Σε κάθε σημείο ή σε κάθε διασταύρωση, πρέπει να λαμβάνονται αποφάσεις: πού να στρίψετε ή να πάτε ευθεία.

    Σπουδαίος:Από αυτό το πρόβλημα μπορείτε ήδη να δείτε τη γενική δομή των προβλημάτων που επιλύονται χρησιμοποιώντας δυναμικό προγραμματισμό: Κάθε πρόβλημα έχει μια αντικειμενική συνάρτηση και μια μεταβλητή επιλογής.

  3. Αντικατάσταση μηχανήματος (ελαχιστοποίηση του κόστους)
  4. Παράδειγμα:Κάθε χρόνο αποφασίζουμε αν θα οδηγήσουμε το παλιό αυτοκίνητο για έναν ακόμη χρόνο και θα επιβαρυνθούμε με τα έξοδα συντήρησης και συντήρησης του παλιού αυτοκινήτου ή αν θα πουλήσουμε το αυτοκίνητο και θα αγοράσουμε ένα καινούργιο (και θα επιβαρυνθούμε με τα έξοδα αγοράς).

    Αντικειμενική λειτουργία:ελαχιστοποίηση του κόστους (είτε το κόστος συντήρησης ενός παλιού αυτοκινήτου είτε η αγορά ενός νέου).

    Μεταβλητή επιλογής:πάρτε μια απόφαση κάθε χρόνο να πουλήσετε το αυτοκίνητο ή να το κρατήσετε.

  5. Ανταλλαγή χαρτοφυλακίου
  6. Παράδειγμα:Παίζοντας στο χρηματιστήριο, αγορά μετοχών οποιωνδήποτε εταιρειών


    Αντικειμενική λειτουργία:μεγιστοποίηση μέση τιμήεισόδημα, γιατί στο χρηματιστήριο το εισόδημα προκύπτει πιθανολογικά, δηλ. είναι μια στατιστική διαδικασία, πιθανολογική.

    Μεταβλητή επιλογής:τι είδους επενδυτικό χαρτοφυλάκιο θα υπάρχει: πόσες μετοχές και ποια εταιρεία πρέπει να αγοράσουμε.

  7. Κατάρτιση σχεδίου βέλτιστης παραγωγής (logistics)
  8. Παράδειγμα:Υπάρχει ένα εργοστάσιο που κατασκευάζει έπιπλα. Το εργοστάσιο απασχολεί συγκεκριμένο αριθμό εργαζομένων που μπορούν να παράγουν την αντίστοιχη ποσότητα ορισμένων επίπλων (καρέκλες, τραπέζια, ντουλάπια κ.λπ.)


    Αντικειμενική λειτουργία: μεγιστοποίηση κέρδους.

    Μεταβλητή επιλογής:επιλέγοντας πόσες καρέκλες ή τραπέζια πρέπει να φτιαχτούν για να έχουν αρκετή εργασία.

  9. Παιχνίδια (πιθανολογικά ή μη)
  10. Παράδειγμα:Συμμετοχή σε διάφορα παιχνίδια


    Αντικειμενική λειτουργία:μεγιστοποίηση της πιθανότητας νίκης ή μεγιστοποίηση της μέσης νίκης κ.λπ.

    Η μεταβλητή επιλογής εδώ εξαρτάται από το συγκεκριμένο παιχνίδι.

    Τα προβλήματα 1 - 5 είναι παραδείγματα προβλημάτων βελτιστοποίησης.

    Συνδυαστική:

  11. Γραφήματα και δέντρα
  12. Παράδειγμα:Το πρόβλημα είναι να αποφασίσουμε πόσα δέντρα υπάρχουν που έχουν συγκεκριμένο αριθμό φύλλων. ή πόσα γραφήματα υπάρχουν για να λύσουν μια τέτοια εργασία κ.λπ.

  13. Πρόβλημα ανταλλαγής νομισμάτων ή αριθμός τρόπων επιστροφής αλλαγών
  14. Παράδειγμα:Υπάρχουν νομίσματα διαφορετικής ονομαστικής αξίας, με ποιους τρόπους μπορείτε να επιστρέψετε τα ρέστα.

Αυτή είναι μια σύντομη περιγραφή των προβλημάτων δυναμικού προγραμματισμού που θα συζητηθούν λεπτομερώς στη συνέχεια.

Η έννοια του δυναμικού προγραμματισμού

Μια άτυπη εξήγηση της βελτιστοποίησης των υποπροβλημάτων DP.

Ας σκεφτούμε άτυποςιδέα DP.

Ας πάρουμε λοιπόν το παράδειγμα ενός εργοστασίου κατασκευής επίπλων.

Για Για να επιτευχθεί ο στόχος της μεγιστοποίησης του κέρδους, είναι απαραίτητο να επιλυθούν πολλές δευτερεύουσες εργασίες:

  • πόσες καρέκλες να παραχθούν - μεταβλητή X1,
  • πόσοι πίνακες να παραχθούν - μεταβλητή X2,
  • πόσοι εργαζόμενοι να προσληφθούν - μεταβλητή X3,
  • ... Xn.

Με μεγάλο αριθμό υποπροβλημάτων, είναι δύσκολο να κατανοήσουμε πώς να λύσουμε ένα τέτοιο πρόβλημα. Συνήθως, Η επίλυση ενός μικρού προβλήματος είναι ευκολότερη από την επίλυση ενός μεγάλου προβλήματοςπου αποτελείται από μικρά.

Ως εκ τούτου, η ΑΣ προτείνει τα εξής:

  • Παίρνουμε μια δευτερεύουσα εργασία με τη μεταβλητή X1 και ξεχνάμε τις υπόλοιπες δευτερεύουσες εργασίες προς το παρόν.
  • Για παράδειγμα, ένα εργοστάσιο παράγει μόνο καρέκλες. Ο διευθυντής έχει το καθήκον να αποκομίσει το μέγιστο κέρδος από την πώληση των καρεκλών.

  • Αφού βρούμε τη βέλτιστη λύση για την πρώτη υποεργασία, παίρνουμε την δευτερεύουσα εργασία για δύο μεταβλητές X1 και X2 και τη λύνουμε χρησιμοποιώντας την ήδη βρεθεί λύση για την πρώτη υποεργασία.
  • Λαμβάνουμε μια λύση για μια μεγαλύτερη υποεργασία, όπου εμφανίζονται οι μεταβλητές X1 και X2. Στη συνέχεια, χρησιμοποιώντας τη λύση που προέκυψε, αναλαμβάνουμε υποεργασίες που καλύπτουν τα Χ1, Χ2 και Χ3.
  • Και έτσι συνεχίζουμε μέχρι να βρούμε μια λύση για όλο το γενικό πρόβλημα.

Επίσημη ιδέα του DP

Συχνά, όταν θέτεις ένα πρόβλημα, η φαινομενικά βέλτιστη λύση είναι δοκιμάζοντας όλες τις πιθανές επιλογές. Ωστόσο, λόγω του πολύ μεγάλου αριθμού τέτοιων επιλογών και, κατά συνέπεια, της υπερφόρτωσης της μνήμης του υπολογιστή, αυτή η μέθοδος δεν είναι πάντα αποδεκτή.

Επιπλέον, μπορεί να προκύψει το εξής ερώτημα: για να βρούμε, για παράδειγμα, το ελάχιστο ή το μέγιστο, γιατί δεν βρίσκουμε την παράγωγο; ή να μην χρησιμοποιήσω σύνολα La Grange ή άλλες μεθόδους μαθηματικής ανάλυσης; Γιατί χρειάζεστε το DP εάν έχετε μεγάλο οπλοστάσιο κεφαλαίων;

Το γεγονός είναι ότι:

Ο δυναμικός προγραμματισμός βασίζεται στην ιδέα της επίλυσης ενός δεδομένου προβλήματος διαιρώντας το σε ξεχωριστά μέρη (υποεργασίες, στάδια), λύνοντας αυτές τις δευτερεύουσες εργασίες και στη συνέχεια συνδυάζοντας αυτές τις λύσεις σε μια κοινή λύση. Συχνά οι περισσότερες από τις δευτερεύουσες εργασίες είναι ακριβώς οι ίδιες.

Είναι σημαντικό όταν λύνουμε ένα πιο σύνθετο πρόβλημα, να μην λύνουμε ξανά ένα μικρό υποπρόβλημα, αλλά να χρησιμοποιούμε την ήδη λυμένη απάντηση σε αυτό το υποπρόβλημα.
Σε ένα γράφημα μπορεί να μοιάζει με αυτό:


Σπουδαίος:Για το λόγο αυτό, η διαίρεση μιας εργασίας σε δευτερεύουσες εργασίες και επίλυση αυτών των υποπροβλημάτων μόνο μία φορά (!), μειώνοντας έτσι τον αριθμό των συνολικών υπολογισμών - μια πιο βέλτιστη μέθοδος, η οποία είναι εγγενής στον δυναμικό προγραμματισμό

Όταν λύνουμε ένα πρόβλημα με παράγωγα, σύνολα La Grange κ.λπ., εργαζόμαστε με συνεχείς συναρτήσεις. Κατά την επίλυση προβλημάτων DP, θα εργαστούμε κυρίως με διακριτές συναρτήσεις, επομένως δεν είναι σωστό να μιλάμε εδώ για τη χρήση συνεχών συναρτήσεων.
Για το λόγο αυτό, σε πολλά προβλήματα, αλλά όχι σε όλα, η χρήση μαθηματικής ανάλυσης θα είναι απαράδεκτη.

Ένα απλό παράδειγμα επίλυσης προβλημάτων με χρήση DP

Ας εξετάσουμε την επιλογή επίλυσης του προβλήματος χρησιμοποιώντας δυναμικό προγραμματισμό.

Παράδειγμα:Είναι απαραίτητο να υπολογίσετε το άθροισμα των n αριθμών: 1 + 2 + 3 + ... + n


Ποια είναι η υποτιθέμενη «δυσκολία» αυτού του προβλήματος: ότι πρέπει να πάρετε έναν μεγάλο αριθμό αριθμών ταυτόχρονα και να πάρετε την απάντηση.

Ας προσπαθήσουμε να εφαρμόσουμε ιδέες DP στο πρόβλημα και να το λύσουμε αναλύοντάς το σε απλές δευτερεύουσες εργασίες.
(Στο DP είναι πάντα απαραίτητο να καθοριστούν πρώτα οι αρχικές συνθήκες ή συνθήκη)

  • Ας ξεκινήσουμε με το άθροισμα του πρώτου στοιχείου, δηλ. απλά πάρτε το πρώτο στοιχείο:
    F(1) = 1
  • Τώρα, χρησιμοποιώντας τη λύση για το πρώτο στοιχείο, λύνουμε
    F(2) = F(1) + 2 = 1 + 2 = 3, δηλ. πρέπει να πάρετε το άθροισμα του πρώτου στοιχείου και να προσθέσετε το δεύτερο στοιχείο σε αυτό
  • F(3) = F(2) + 3 = 6
  • Συνεχίζουμε κατ' αναλογία και λαμβάνουμε την αντικειμενική συνάρτηση:
    F(n) = F(n-1) + An


Λοιπόν, τι κάναμε: καθορίσαμε τη σειρά και προσδιορίσαμε τις δευτερεύουσες εργασίες, στη συνέχεια λύσαμε καθεμία από αυτές, με βάση τη λύση της προηγούμενης.

Ένα απλό παράδειγμα όπου το DP εξακολουθεί να χρησιμοποιείται αδικαιολόγητα (τεχνητά) καταδεικνύει την αρχή των ιδεών του DP.

Ας δούμε ένα άλλο παράδειγμα.

Παράδειγμα:υπάρχει μια σκάλα n σκαλοπατιών, μπροστά από την οποία υπάρχει ένα άτομο που είναι πίσω 1 Το βήμα μπορεί είτε να ανέβει στο επόμενο βήμα είτε να πηδήξει πάνω από ένα σκαλί. Ερώτηση: με πόσους τρόπους μπορεί να φτάσει στο τελευταίο σκαλοπάτι;


Λύση:

Ας εξετάσουμε τις απλούστερες επιλογές (υποεργασίες):

Ας δούμε ένα παράδειγμα βημάτων i

Πώς μπορούμε να φτάσουμε στο βήμα i:

  1. από το βήμα i-1, και θα μπορούσαμε να φτάσουμε στο βήμα i-1 με τρόπους i-1
  2. από το βήμα i-2, και θα μπορούσαμε να φτάσουμε στο βήμα i-2 με τρόπους i-2

Για παράδειγμα, πώς να φτάσετε 4ο βήμα:

Οτι., συνολικός αριθμός τρόπωνφτάστε στο βήμα i:
f(a i) = f(a i-1) + f(a i-2)

Ας προσδιορίσουμε τις αρχικές τιμές, από το οποίο θα ξεκινήσει η επίλυση του προβλήματος.
Αν ξεκινήσετε με 1, τότε ο τύπος αντιστοιχεί στην εύρεση της ακολουθίας των αριθμών Fibonacci.

Βλέπουμε ότι το πρόβλημα είναι ουσιαστικά συνδυαστικό (δηλαδή, ο αριθμός των τρόπων για να γίνει κάτι) έχει περιοριστεί στον υπολογισμό κάποιας επαναλαμβανόμενης ακολουθίας.

Ασκηση 1:εφαρμόστε ένα παράδειγμα για τα πρώτα δέκα βήματα (ουσιαστικά τους 10 πρώτους αριθμούς της σειράς Fibonacci) χρησιμοποιώντας αναδρομή.

Συμπληρώστε τον κωδικό:

1 2 3 4 5 6 7 8 9 10 11 12 13 var c: ακέραιος ; διαδικασία getKolSposob(i, n: ακέραιος ) ; αρχίζω να γράφω (i+ n, " " ); Inc(c); αν ... τότε getKolSposob(...,... ) τέλος ; αρχή c: = 1 ; getKolSposob(0, 1); τέλος.

var c:ακέραιος; διαδικασία getKolSposob(i,n: ακέραιος); ξεκινήστε να γράφετε (i+n," "); Inc(c); αν ... τότε getKolSposob(...,...) τέλος; έναρξη c:=1; getKolSposob(0,1); τέλος.


Εργασία 2:
Λύση 15ου τύπου εργασιών Ενιαίας Πολιτικής Εξέτασης (Γραφήματα. Εύρεση του αριθμού των μονοπατιών).

Γεια σου, Habrakhabr. Αυτήν τη στιγμή εργάζομαι πάνω σε ένα εγχειρίδιο για τον προγραμματισμό της Ολυμπιάδας, μία από τις παραγράφους του οποίου είναι αφιερωμένη στον δυναμικό προγραμματισμό. Ακολουθεί ένα απόσπασμα από αυτήν την παράγραφο. Προσπαθώντας να εξηγήσω αυτό το θέμα όσο πιο απλά γινόταν, προσπάθησα να συνοδεύσω σύνθετες στιγμές με εικονογραφήσεις. Με ενδιαφέρει η γνώμη σας για το πόσο σαφές είναι αυτό το υλικό. Θα χαιρόμουν επίσης να λάβω συμβουλές σχετικά με το ποιες άλλες εργασίες πρέπει να περιλαμβάνονται σε αυτήν την ενότητα.

Σε πολλά προβλήματα προγραμματισμού της Ολυμπιάδας, η επίλυση με χρήση αναδρομικής ή εξαντλητικής αναζήτησης απαιτεί την εκτέλεση ενός πολύ μεγάλου αριθμού λειτουργιών. Μια προσπάθεια επίλυσης τέτοιων προβλημάτων, για παράδειγμα, με εξαντλητική αναζήτηση, οδηγεί σε υπέρβαση του χρόνου εκτέλεσης.

Ωστόσο, μεταξύ της εξαντλητικής αναζήτησης και ορισμένων άλλων προβλημάτων, μπορούμε να διακρίνουμε μια κατηγορία προβλημάτων που έχουν μια καλή ιδιότητα: να έχουν λύσεις σε ορισμένα υποπροβλήματα (για παράδειγμα, για έναν μικρότερο αριθμό n), μπορείτε να βρείτε μια λύση στο αρχικό πρόβλημα σχεδόν χωρίς αναζήτηση.

Τέτοια προβλήματα επιλύονται χρησιμοποιώντας τη μέθοδο δυναμικού προγραμματισμού και με τον ίδιο τον δυναμικό προγραμματισμό εννοούμε τη μείωση του προβλήματος σε δευτερεύουσες εργασίες.

Ακολουθίες

Το κλασικό πρόβλημα ακολουθίας είναι το εξής.

Ακολουθία Fibonacci φά nδίνεται από τους τύπους: φά 1 = 1, φά 2 = 1,
Fn = φά n - 1 + φά n- 2 στις n> 1. Πρέπει να βρω φά nκατά αριθμό n.

Μια λύση που μπορεί να φαίνεται λογική και αποτελεσματική είναι η επίλυση με χρήση αναδρομής:

Int F(int n) (αν (n< 2) return 1; else return F(n - 1) + F(n - 2); }
Χρησιμοποιώντας μια τέτοια λειτουργία, θα λύσουμε το πρόβλημα "από το τέλος" - θα μειώσουμε βήμα προς βήμα nμέχρι να φτάσουμε σε γνωστές αξίες.

Αλλά όπως μπορείτε να δείτε, ένα τέτοιο φαινομενικά απλό πρόγραμμα είναι ήδη n= 40 λειτουργεί αισθητά για μεγάλο χρονικό διάστημα. Αυτό οφείλεται στο γεγονός ότι τα ίδια ενδιάμεσα δεδομένα υπολογίζονται πολλές φορές - ο αριθμός των πράξεων αυξάνεται με την ίδια ταχύτητα με την αύξηση των αριθμών Fibonacci - εκθετικά.

Μία διέξοδος από αυτήν την κατάσταση είναι να αποθηκεύσετε τα ενδιάμεσα αποτελέσματα που έχουν ήδη βρεθεί με σκοπό την επαναχρησιμοποίησή τους:

Int F(int n) ( if (A[n] != -1) return A[n]; if (n< 2) return 1; else { A[n] = F(n - 1) + F(n - 2); return A[n]; } }
Η λύση που δίνεται είναι σωστή και αποτελεσματική. Αλλά για αυτό το πρόβλημα ισχύει επίσης μια απλούστερη λύση:

F = 1; F = 1; για (i = 2; i< n; i++) F[i] = F + F;
Αυτή η λύση μπορεί να ονομαστεί λύση "από την αρχή" - πρώτα συμπληρώνουμε τις γνωστές τιμές και μετά βρίσκουμε την πρώτη άγνωστη τιμή ( φά 3), μετά το επόμενο κ.λπ., μέχρι να φτάσουμε στο επιθυμητό.

Αυτή είναι ακριβώς η κλασική λύση για τον δυναμικό προγραμματισμό: πρώτα λύσαμε όλα τα υποπροβλήματα (τα βρήκαμε όλα φά ΕγώΓια Εγώ < n), στη συνέχεια, γνωρίζοντας τις λύσεις στα υποπροβλήματα, βρήκαμε την απάντηση ( φά n = φά n - 1 + φά n - 2 , φά n- 1 και φά n- 2 έχουν ήδη βρεθεί).

Μονοδιάστατος Δυναμικός Προγραμματισμός

Για να κατανοήσουμε καλύτερα την ουσία του δυναμικού προγραμματισμού, ορίζουμε πρώτα πιο επίσημα τις έννοιες της εργασίας και της δευτερεύουσας εργασίας.

Αφήστε το αρχικό πρόβλημα να είναι η εύρεση ενός συγκεκριμένου αριθμού Τμε αρχικά στοιχεία n 1 , n 2 , ..., n κ. Δηλαδή, μπορούμε να μιλήσουμε για τη συνάρτηση Τ(n 1 , n 2 , ..., n κ), η αξία του οποίου είναι η απάντηση που χρειαζόμαστε. Στη συνέχεια θα θεωρήσουμε τις εργασίες ως δευτερεύουσες εργασίες
Τ(Εγώ 1 , Εγώ 2 , ..., Εγώ κ) στο Εγώ 1 < n 1 , Εγώ 2 < n 2 , ..., Εγώ κ < n κ .

Το ακόλουθο μονοδιάστατο πρόβλημα δυναμικού προγραμματισμού παρουσιάζεται σε διάφορες παραλλαγές.

Στο n < 32 полный перебор потребует нескольких секунд, а при n= 64 η εξαντλητική αναζήτηση δεν είναι κατ' αρχήν εφικτή. Για να λύσουμε το πρόβλημα χρησιμοποιώντας τη μέθοδο δυναμικού προγραμματισμού, μειώνουμε το αρχικό πρόβλημα σε δευτερεύουσες εργασίες.

Στο n = 1, n= 2 η απάντηση είναι προφανής. Ας υποθέσουμε ότι έχουμε ήδη βρει κ n - 1 , κ n- 2 — ο αριθμός τέτοιων ακολουθιών μήκους n- 1 και n - 2.

Ας δούμε πόσο μπορεί να είναι το μήκος της ακολουθίας n. Αν ο τελευταίος χαρακτήρας του είναι 0, τότε ο πρώτος n- 1 — οποιαδήποτε σωστή ακολουθία μήκους
n- 1 (δεν έχει σημασία αν τελειώνει με μηδέν ή ένα - ακολουθεί το 0). Υπάρχουν μόνο τέτοιες ακολουθίες κ n- 1 . Εάν ο τελευταίος χαρακτήρας είναι ίσος με 1, τότε ο προτελευταίος χαρακτήρας πρέπει να είναι ίσος με 0 (αλλιώς θα υπάρχουν δύο στη σειρά) και ο πρώτος
n- 2 χαρακτήρες - οποιαδήποτε έγκυρη ακολουθία μήκους n- 2, ο αριθμός τέτοιων ακολουθιών είναι ίσος κ n - 2 .

Ετσι, κ 1 = 2, κ 2 = 3, κ n = κ n - 1 + κ n- 2 στις n> 2. Δηλαδή, αυτή η εργασία καταλήγει στην εύρεση των αριθμών Fibonacci.

2D Δυναμικός Προγραμματισμός

Ένα κλασικό πρόβλημα στον δισδιάστατο δυναμικό προγραμματισμό είναι το πρόβλημα των διαδρομών σε ένα ορθογώνιο πεδίο.
Σε διαφορετικές συνθέσεις, πρέπει να μετρήσετε τον αριθμό των διαδρομών ή να βρείτε μια διαδρομή που είναι η καλύτερη από κάποια άποψη.

Ακολουθούν μερικές διατυπώσεις τέτοιων εργασιών:

Εργασία 2. n*Μκύτταρα. Μπορείτε να κάνετε βήματα ενός κελιού προς τα δεξιά ή προς τα κάτω. Μετρήστε με πόσους τρόπους μπορείτε να φτάσετε από το επάνω αριστερό κελί στο κάτω δεξιά κελί.

Εργασία 3.Δίνεται ένα ορθογώνιο πεδίο μεγέθους n*Μκύτταρα. Μπορείτε να κάνετε βήματα ενός κελιού προς τα δεξιά, προς τα κάτω ή διαγώνια δεξιά και κάτω. Κάθε κελί περιέχει έναν φυσικό αριθμό. Πρέπει να μεταβείτε από το επάνω αριστερό κελί στο κάτω δεξιά κελί. Το βάρος της διαδρομής υπολογίζεται ως το άθροισμα των αριθμών από όλα τα κελιά που επισκεφτήκατε. Είναι απαραίτητο να βρείτε μια διαδρομή με ελάχιστο βάρος.

Ένα χαρακτηριστικό γνώρισμα όλων αυτών των προβλημάτων είναι ότι κάθε μεμονωμένη διαδρομή δεν μπορεί να περάσει από το ίδιο κελί δύο ή περισσότερες φορές.

Ας εξετάσουμε την εργασία 2 με περισσότερες λεπτομέρειες Σε κάποιο κελί με συντεταγμένες (. Εγώ,ι) μπορεί να προέρχεται μόνο από πάνω ή από αριστερά, δηλαδή από κελιά με συντεταγμένες ( Εγώ - 1, ι) Και ( Εγώ, ι - 1):

Έτσι, για ένα κελί ( Εγώ, ι) ο αριθμός των διαδρομών A[i][j] θα είναι ίσος με
A[j] + A[i], δηλαδή το πρόβλημα μειώνεται σε δύο δευτερεύουσες εργασίες. Αυτή η υλοποίηση χρησιμοποιεί δύο παραμέτρους − ΕγώΚαι ι- επομένως, σε σχέση με αυτό το πρόβλημα μιλάμε για δισδιάστατο δυναμικό προγραμματισμό.

Τώρα μπορούμε να περάσουμε διαδοχικά μέσα από τις σειρές (ή τις στήλες) του πίνακα Α, βρίσκοντας τον αριθμό των διαδρομών για το τρέχον κελί χρησιμοποιώντας τον παραπάνω τύπο. Πρώτα πρέπει να βάλετε τον αριθμό 1 στο Α.

Στο πρόβλημα 3, σε ένα κελί με συντεταγμένες ( Εγώ, ι) μπορούμε να πάρουμε από κελιά με συντεταγμένες
(Εγώ- 1, j), ( Εγώ, ι- 1) και ( Εγώ - 1, ι- 1). Ας υποθέσουμε ότι για καθένα από αυτά τα τρία κελιά έχουμε ήδη βρει τη διαδρομή του ελάχιστου βάρους και τοποθετήσαμε τα ίδια τα βάρη σε W[j], W[i],
W. Για να βρείτε το ελάχιστο βάρος για ( Εγώ, ι), πρέπει να επιλέξετε το ελάχιστο από τα βάρη W[j], W[i], W και να προσθέσετε σε αυτό τον αριθμό που είναι γραμμένος στο τρέχον κελί:

W[i][j] = min(W[j], W[i], W) + A[i][j];

Αυτό το έργο περιπλέκεται από το γεγονός ότι είναι απαραίτητο να βρεθεί όχι μόνο το ελάχιστο βάρος, αλλά και η ίδια η διαδρομή. Επομένως, σε έναν άλλο πίνακα θα καταγράφουμε επιπλέον για κάθε κελί από ποια πλευρά πρέπει να το εισάγουμε.

Το παρακάτω σχήμα δείχνει ένα παράδειγμα των αρχικών δεδομένων και ένα από τα βήματα του αλγορίθμου.

Ακριβώς ένα βέλος οδηγεί σε κάθε ένα από τα ήδη περασμένα κελιά. Αυτό το βέλος δείχνει από ποια πλευρά πρέπει να έρθετε σε αυτό το κελί για να λάβετε το ελάχιστο βάρος που καταγράφεται στο κελί.

Αφού περάσετε ολόκληρο τον πίνακα, θα χρειαστεί να εντοπίσετε την ίδια τη διαδρομή από το τελευταίο κελί, ακολουθώντας τα βέλη προς την αντίθετη κατεύθυνση.

Επακόλουθα προβλήματα

Εξετάστε το πρόβλημα της αυξανόμενης δευτερεύουσας ακολουθίας.

Εργασία 4.Δίνεται μια ακολουθία ακεραίων. Είναι απαραίτητο να βρεθεί η μεγαλύτερη αυστηρά αυξανόμενη ακολουθία του.

Ας αρχίσουμε να λύνουμε το πρόβλημα από την αρχή - θα αναζητήσουμε την απάντηση, ξεκινώντας από τους πρώτους όρους αυτής της ακολουθίας. Για κάθε αριθμό Εγώθα αναζητήσουμε τη μεγαλύτερη αυξανόμενη υποακολουθία που τελειώνει με ένα στοιχείο στη θέση Εγώ. Αφήστε την αρχική ακολουθία να αποθηκευτεί στον πίνακα Α. Στον πίνακα L θα καταγράψουμε τα μήκη των μέγιστων υποακολουθιών που τελειώνουν με το τρέχον στοιχείο. Ας βρούμε όλα τα L[i] για 1<= Εγώ <= κ- 1. Τώρα μπορείτε να βρείτε το L[k] ως εξής. Εξετάζουμε όλα τα στοιχεία του A[i] για 1<= Εγώ < κ- 1. Αν
Ολα συμπεριλαμβάνονται]< A[k], то κΤο ου στοιχείο μπορεί να γίνει συνέχεια της υποακολουθίας που τελειώνει με το στοιχείο A[i]. Το μήκος της προκύπτουσας υποακολουθίας θα είναι 1 μεγαλύτερο από το L[i]. Για να βρείτε το L[k], πρέπει να περάσετε από όλα Εγώαπό 1 έως k - 1:
L[k] = max(L[i]) + 1, όπου το μέγιστο λαμβάνεται για όλα Εγώέτσι ώστε A[i]< A[k] и
1 <= Εγώ < κ.

Εδώ, το μέγιστο του κενού συνόλου θα θεωρείται ίσο με 0. Σε αυτήν την περίπτωση, το τρέχον στοιχείο θα γίνει το μοναδικό στην επιλεγμένη ακολουθία και δεν θα είναι συνέχεια ενός από τα προηγούμενα. Αφού συμπληρωθεί ο πίνακας L, το μήκος της μεγαλύτερης αυξανόμενης υποακολουθίας θα είναι ίσο με το μέγιστο στοιχείο του L.

Για να επαναφέρετε την ίδια την υποακολουθία, μπορείτε επίσης να αποθηκεύσετε τον αριθμό του προηγούμενου επιλεγμένου στοιχείου για κάθε στοιχείο, για παράδειγμα, σε έναν πίνακα N.

Ας εξετάσουμε τη λύση σε αυτό το πρόβλημα χρησιμοποιώντας το παράδειγμα της ακολουθίας 2, 8, 5, 9, 12, 6. Δεδομένου ότι δεν υπάρχει στοιχείο πριν από το 2, η μέγιστη υποακολουθία περιέχει μόνο ένα στοιχείο - L = 1, και δεν υπάρχει κανένα πριν it - N = 0. Επιπλέον,
2 < 8, поэтому 8 может стать продолжением последовательности с предыдущим элементом. Тогда L = 2, N = 1.

Το μόνο στοιχείο μικρότερο από το A = 5 είναι το A = 2, επομένως το 5 μπορεί να γίνει συνέχεια μιας μόνο υποακολουθίας - αυτής που περιέχει 2. Τότε
L = L + 1 = 2, N = 1, αφού το 2 βρίσκεται στη θέση 1. Ομοίως, εκτελούμε άλλα τρία βήματα του αλγορίθμου και παίρνουμε το τελικό αποτέλεσμα.

Τώρα επιλέγουμε το μέγιστο στοιχείο στον πίνακα L και από τον πίνακα N ανακατασκευάζουμε την ίδια την υποακολουθία 2, 5, 9, 12.

Ένα άλλο κλασικό πρόβλημα δυναμικού προγραμματισμού είναι το πρόβλημα παλίνδρομου.

Εργασία 5.Δίνεται μια σειρά από κεφαλαία γράμματα του λατινικού αλφαβήτου. Πρέπει να βρείτε το μήκος του μεγαλύτερου παλίνδρομου που μπορεί να ληφθεί διαγράφοντας μερικά γράμματα από μια δεδομένη συμβολοσειρά.

Ας συμβολίσουμε αυτή τη συμβολοσειρά με S και τα σύμβολά της με S[i], 1<= Εγώ <= n. Θα εξετάσουμε πιθανές υποσυμβολοσειρές αυτής της συμβολοσειράς με Εγώου ιου σύμβολο, τα συμβολίζουμε με μικρό(Εγώ, ι). Θα γράψουμε τα μήκη των μέγιστων παλίνδρομων για υποσυμβολοσειρές σε έναν τετράγωνο πίνακα L: L[i][j] είναι το μήκος του μέγιστου παλινδρόμου που μπορεί να ληφθεί από την υποσυμβολοσειρά μικρό(Εγώ, ι).

Ας αρχίσουμε να λύνουμε το πρόβλημα με τις απλούστερες υποσυμβολοσειρές. Για μια συμβολοσειρά χαρακτήρων (δηλαδή, μια υποσυμβολοσειρά της φόρμας μικρό(Εγώ, Εγώ)) η απάντηση είναι προφανής - δεν χρειάζεται να διαγράψετε τίποτα, μια τέτοια συμβολοσειρά θα είναι ένα παλίνδρομο. Για μια συμβολοσειρά δύο χαρακτήρων μικρό(Εγώ, Εγώ+ 1) είναι δυνατές δύο επιλογές: εάν οι χαρακτήρες είναι ίσοι, τότε έχουμε ένα παλίνδρομο, δεν χρειάζεται να διαγράψουμε τίποτα. Εάν οι χαρακτήρες δεν είναι ίσοι, τότε διαγράψτε οποιονδήποτε.

Ας μας δοθεί τώρα μια υποσυμβολοσειρά μικρό(Εγώ, ι). Εάν οι πρώτοι (S[i]) και οι τελευταίοι (S[j]) χαρακτήρες της υποσυμβολοσειράς δεν ταιριάζουν, τότε ένας από αυτούς πρέπει να διαγραφεί. Μετά μας μένει η υποσυμβολοσειρά μικρό(Εγώ, ι- 1) ή μικρό(Εγώ + 1, ι) - δηλαδή, ανάγουμε το πρόβλημα σε μια δευτερεύουσα εργασία: L[i][j] = max(L[i], L[j]). Εάν ο πρώτος και ο τελευταίος χαρακτήρες είναι ίσοι, τότε μπορούμε να αφήσουμε και τους δύο, αλλά πρέπει να γνωρίζουμε τη λύση στο πρόβλημα μικρό(Εγώ + 1, ι - 1):
L[i][j] = L + 2.

Ας δούμε τη λύση χρησιμοποιώντας τη συμβολοσειρά ABACCBA ως παράδειγμα. Πρώτα απ 'όλα, γεμίζουμε τη διαγώνιο του πίνακα με μονάδες, θα αντιστοιχούν στις υποσυμβολοσειρές μικρό(Εγώ, Εγώ) από έναν χαρακτήρα. Στη συνέχεια αρχίζουμε να εξετάζουμε υποχορδές μήκους δύο. Σε όλες τις υποσυμβολοσειρές εκτός από μικρό(4, 5), τα σύμβολα είναι διαφορετικά, οπότε γράφουμε 1 στα αντίστοιχα κελιά και 2 στο L.

Αποδεικνύεται ότι θα γεμίσουμε τον πίνακα διαγώνια, ξεκινώντας από την κύρια διαγώνιο που οδηγεί από την επάνω αριστερή γωνία προς την κάτω δεξιά. Για υποσυμβολοσειρές μήκους 3, λαμβάνονται οι ακόλουθες τιμές: σε μια υποσυμβολοσειρά ABA, το πρώτο και το τελευταίο γράμμα είναι ίσα, άρα
L = L + 2. Στις υπόλοιπες υποσυμβολοσειρές, το πρώτο και το τελευταίο γράμμα είναι διαφορετικά.

BAC: L = max(L, L) = 1.
ACC: L = max(L, L) = 2.
CCB: L = max(L, L) = 2.
CBA: L = max(L, L) = 1.

Εάν στην εργασία είναι απαραίτητο να εξάγουμε όχι το μήκος, αλλά το ίδιο το παλίνδρομο, τότε εκτός από τη συστοιχία μηκών, πρέπει να κατασκευάσουμε μια σειρά από μεταβάσεις - για κάθε κελί, θυμηθείτε ποια από τις περιπτώσεις εφαρμόστηκε (για σαφήνεια, στο σχήμα, αντί για τις αριθμητικές τιμές που κωδικοποιούν μεταβάσεις, σχεδιάζονται τα αντίστοιχα βέλη) .

  • Η ουσία της μεθόδου δυναμικού προγραμματισμού………………………..4

  • Παράδειγμα επίλυσης προβλήματος χρησιμοποιώντας τη μέθοδο δυναμικού προγραμματισμού…………………………………………………………………………………

    Κατάλογος των πηγών που χρησιμοποιήθηκαν……………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………

    1. Δυναμικός προγραμματισμός. ΒΑΣΙΚΕΣ ΕΝΝΟΙΕΣ.

    Δυναμικός προγραμματισμός (DP)στη θεωρία συστημάτων υπολογιστών, ένας τρόπος επίλυσης πολύπλοκων προβλημάτων με τη διάσπασή τους σε απλούστερες υποεργασίες. Ισχύει για προβλήματα με βέλτιστη υποδομή, τα οποία μοιάζουν με ένα σύνολο επικαλυπτόμενων υποπροβλημάτων των οποίων η πολυπλοκότητα είναι ελαφρώς μικρότερη από την αρχική. Σε αυτή την περίπτωση, ο χρόνος υπολογισμού, σε σύγκριση με τις «αφελείς» μεθόδους, μπορεί να μειωθεί σημαντικά.

    Η βασική ιδέα στον δυναμικό προγραμματισμό είναι αρκετά απλή. Κατά κανόνα, για να λυθεί ένα δεδομένο πρόβλημα, είναι απαραίτητο να λυθούν μεμονωμένα μέρη του προβλήματος (υποεργασίες) και στη συνέχεια να συνδυαστούν οι λύσεις των δευτερευουσών εργασιών σε μία γενική λύση. Συχνά πολλές από αυτές τις δευτερεύουσες εργασίες είναι ίδιες. Η προσέγγιση δυναμικού προγραμματισμού είναι η επίλυση κάθε υποπροβλήματος μόνο μία φορά, μειώνοντας έτσι τον αριθμό των υπολογισμών. Αυτό είναι ιδιαίτερα χρήσιμο σε περιπτώσεις όπου ο αριθμός των επαναλαμβανόμενων δευτερευουσών εργασιών είναι εκθετικά μεγάλος.

    Ο δυναμικός προγραμματισμός είναι μια μαθηματική τεχνική που προσεγγίζει τη λύση μιας συγκεκριμένης κατηγορίας προβλημάτων με τη διάσπασή τους σε μέρη, μικρότερα και λιγότερο σύνθετα προβλήματα. Ταυτόχρονα, χαρακτηριστικό γνώρισμα είναι η επίλυση προβλημάτων σταδιακά, σε σταθερά διαστήματα, χρονικές περιόδους, που καθόρισαν την εμφάνιση του όρου δυναμικός προγραμματισμός. Πρέπει να σημειωθεί ότι οι μέθοδοι δυναμικού προγραμματισμού χρησιμοποιούνται επίσης με επιτυχία κατά την επίλυση προβλημάτων στα οποία δεν λαμβάνεται υπόψη ο παράγοντας χρόνος. Γενικά, η μαθηματική συσκευή μπορεί να αναπαρασταθεί ως προγραμματισμός βήμα προς βήμα ή βήμα προς βήμα. Η επίλυση προβλημάτων με μεθόδους δυναμικού προγραμματισμού πραγματοποιείται με βάση την αρχή της βελτιστοποίησης που διατυπώθηκε από τον R. E. Bellman: η βέλτιστη συμπεριφορά έχει την ιδιότητα ότι ανεξάρτητα από την αρχική κατάσταση του συστήματος και την αρχική λύση, η επόμενη λύση πρέπει να καθορίσει τη βέλτιστη συμπεριφορά σε σχέση με την κατάσταση που προκύπτει ως αποτέλεσμα της αρχικής λύσης. Από αυτό προκύπτει ότι ο σχεδιασμός κάθε βήματος θα πρέπει να πραγματοποιείται λαμβάνοντας υπόψη το συνολικό όφελος που προκύπτει μετά την ολοκλήρωση ολόκληρης της διαδικασίας, το οποίο επιτρέπει τη βελτιστοποίηση του τελικού αποτελέσματος σύμφωνα με το επιλεγμένο κριτήριο.

    Έτσι, ο δυναμικός προγραμματισμός με την ευρεία έννοια είναι ο βέλτιστος έλεγχος μιας διαδικασίας αλλάζοντας τις ελεγχόμενες παραμέτρους σε κάθε βήμα και, επομένως, επηρεάζοντας την πρόοδο της διαδικασίας, αλλάζοντας την κατάσταση του συστήματος σε κάθε βήμα. Γενικά, ο δυναμικός προγραμματισμός είναι μια αρμονική θεωρία για κατανόηση και αρκετά απλή ώστε να χρησιμοποιείται σε εμπορικές δραστηριότητες κατά την επίλυση τόσο γραμμικών όσο και μη γραμμικών προβλημάτων.

    Ο δυναμικός προγραμματισμός είναι ένας από τους κλάδους του βέλτιστου προγραμματισμού. Χαρακτηρίζεται από συγκεκριμένες μεθόδους και τεχνικές που εφαρμόζονται σε πράξεις στις οποίες η διαδικασία λήψης αποφάσεων χωρίζεται σε στάδια (βήματα). Οι μέθοδοι δυναμικού προγραμματισμού χρησιμοποιούνται για την επίλυση προβλημάτων βελτιστοποίησης παραλλαγών με δεδομένα κριτήρια βελτιστοποίησης, με ορισμένες συνδέσεις μεταξύ των μεταβλητών και της αντικειμενικής συνάρτησης, που εκφράζονται από ένα σύστημα εξισώσεων ή ανισοτήτων. Σε αυτή την περίπτωση, όπως και σε προβλήματα που επιλύονται με μεθόδους γραμμικού προγραμματισμού, οι περιορισμοί μπορούν να δοθούν με τη μορφή ισοτήτων ή ανισοτήτων. Ωστόσο, εάν σε προβλήματα γραμμικού προγραμματισμού οι εξαρτήσεις μεταξύ της συνάρτησης κριτηρίου και των μεταβλητών είναι αναγκαστικά γραμμικές, τότε στα προβλήματα δυναμικού προγραμματισμού αυτές οι εξαρτήσεις μπορεί επίσης να είναι μη γραμμικές. Ο δυναμικός προγραμματισμός μπορεί να χρησιμοποιηθεί τόσο για την επίλυση προβλημάτων που σχετίζονται με τη δυναμική μιας διαδικασίας ή συστήματος, όσο και για στατικά προβλήματα που σχετίζονται, για παράδειγμα, με την κατανομή πόρων. Αυτό διευρύνει σημαντικά το πεδίο του δυναμικού προγραμματισμού για την επίλυση προβλημάτων ελέγχου. Και η δυνατότητα απλοποίησης της διαδικασίας λύσης, η οποία επιτυγχάνεται με τον περιορισμό της περιοχής και του αριθμού που εξετάζονται κατά τη μετάβαση στο επόμενο στάδιο επιλογών, αυξάνει τα πλεονεκτήματα αυτού του συνόλου μεθόδων.

    Ωστόσο, ο δυναμικός προγραμματισμός έχει και μειονεκτήματα. Πρώτα απ 'όλα, δεν υπάρχει ενιαία καθολική μέθοδος λύσης. Σχεδόν κάθε πρόβλημα που επιλύεται με αυτή τη μέθοδο χαρακτηρίζεται από τα δικά του χαρακτηριστικά και απαιτεί αναζήτηση του καταλληλότερου συνόλου μεθόδων για την επίλυσή του. Επιπλέον, οι μεγάλοι όγκοι και η πολυπλοκότητα της επίλυσης προβλημάτων πολλαπλών βημάτων με πολλές καταστάσεις οδηγούν στην ανάγκη επιλογής προβλημάτων χαμηλής διάστασης ή χρήσης συμπιεσμένων πληροφοριών. Το τελευταίο επιτυγχάνεται με τη χρήση μεθόδων για την ανάλυση των επιλογών και την επεξεργασία της λίστας των καταστάσεων.

    Για διαδικασίες συνεχούς χρόνου, ο δυναμικός προγραμματισμός θεωρείται ως μια περιοριστική έκδοση ενός σχήματος διακριτής λύσης. Τα αποτελέσματα που λαμβάνονται σε αυτή την περίπτωση πρακτικά συμπίπτουν με εκείνα που λαμβάνονται με τις μέγιστες μεθόδους των L. S. Pontryagin ή Hamilton-Jacobi-Bellman.

    Ο δυναμικός προγραμματισμός χρησιμοποιείται για την επίλυση προβλημάτων στα οποία η αναζήτηση του βέλτιστου είναι δυνατή με μια προσέγγιση βήμα προς βήμα, για παράδειγμα, την κατανομή των σπάνιων επενδύσεων κεφαλαίου μεταξύ νέων τομέων χρήσης τους. ανάπτυξη κανόνων διαχείρισης ζήτησης ή αποθέματος που καθορίζουν τη στιγμή της αναπλήρωσης και το μέγεθος της εντολής αναπλήρωσης· ανάπτυξη αρχών για τον προγραμματισμό της παραγωγής και την εξίσωση της απασχόλησης σε συνθήκες κυμαινόμενης ζήτησης προϊόντων. κατάρτιση ημερολογιακών σχεδίων για τρέχουσες και μεγάλες επισκευές του εξοπλισμού και την αντικατάστασή του· αναζήτηση για τις μικρότερες αποστάσεις στο δίκτυο μεταφορών. διαμόρφωση της ακολουθίας ανάπτυξης μιας εμπορικής λειτουργίας κ.λπ.