Dynamic planning, in fact, is similar to the division of the law, the basic idea is to divide the complex problem into a number of simple sub-problems, and then to solve. The difference is that the sub-problems concerned by the division and treatment do not overlap each other, while the sub-problems of dynamic planning are overlapping. For example, in the fast sort, we divide the data into two parts, the two parts of the recursive idea of the sorting of the whole problem into sub-problem sub-array, but the order of the two sub-arrays is not related to each other, the sort of a sub-array does not get any "benefit" because of the ordering of B sub-arrays. or "bad". But sometimes, there is a connection between sub-problems, such as the following "pipe cutting" problem:
Steel Tube Cutting original problem:
A company produces long steel tubes , which, in general, will cut the rods into different lengths and then sell them. One problem is that different lengths of pipe prices are not the same, but they are not exactly proportional, such as 2 meters of steel pipe price is less than 3 meters of steel , but not 2:3 of the proportion. The length of the steel pipe Price table is as follows:
Length I |
1 2 3 4 5 6 7 8 9 10 |
Price PI |
1 5 8 9 Ten - - - - - |
So the problem came, such as 30 meters long steel pipe, how to cut, cut into how many, in order to let the highest price, the highest yield?
solving the best benefits and corresponding allocation methods:
Naive algorithm:
The simplest direct idea, is to use brute force crack, n long pipe, can be decomposed into I long and n-i long two segment, because I can take value from 0~n, so we can not continue to cut I, so for the length of this section of I can directly call price array p[i] to get the price, Then add the return value of the function that evaluates the n-i recursive call for optimal returns. In the process of recording the optimal benefits of these combinations, such as the end of the cycle, you can get the best return price.
Suppose R[n] represents the best yield value of the N-long steel tube, the array p represents the price in the table above, where p[0]=0, from p[1]~p[10] corresponds to the data in the table above, then according to the above idea, there is the formula:
R[n]=max (P[i],r[n-i]), I from 1 to N, when N=0, R[n]=0, because 0 long steel pipe price is of course 0.
So give the following implementation code:
int Cut_rod (int* p, int n) {if (n = = 0) {return 0;} int q = -1;for (int i = 1; I <= n; i++) {/* * will be n-long steel bars, divided into two segments of I and n-i, I-long segments do not cut, and the n-i of the section for maximum * cutting proceeds, and then add, and Q is the sum of all combinations, the most profitable one */ Q = max (q, P[i] + Cut_rod (p, n-i));} return q;}
This method is easier to understand, but performance is not good?
You can simply take a look at the n=4 situation:
The division of the N=4 (the first segment of which is directly using P[i], the latter part calls the function to seek the best return):
The division of Cut_rod (p,4) May:
①1 long and 3 long: P[1]+cut_rod (p,3)
②2 Long and 2 long: P[2]+cut_rod (p,2)
③3 long and 1 long: P[3]+cut_rod (p,1)
④4 long and 0 long: P[4]+cut_rod (p,0)
and Cut_rod (p,3) can be divided into an array of P elements and Cut_rod (p,0), Cut_rod (p,1) and Cut_rod (p,2), and so on, you can give a recursive call tree to show how many times the Cut_rod recursive call:
It is not difficult to see, do a lot of repetitive work, to n=2 node as an example, respectively, in n=4 and n=3 are called. According to this, you can give a formula for the number of recursive calls, assuming that T (N) is the number of calls that Cut_rod the second argument is N, and T (0) is 1, because the first invocation of the root node is counted as well. So there are:
T (n) =1+t (0) +t (1) +...+t (n-1)
Using inductive method, it is easy to draw:T (n) =2^n.
The number of calls to power is obviously too large, and we'll make the whole process a little bit bigger.
Dynamic Programming algorithm:
In fact, we don't need to recalculate the results of Cut_rod at n=2 every time, just save the results when the first calculation is done, and then use them directly when needed. This is actually the so-called dynamic programming algorithm.
There are two ways of thinking here, a top-down method called with a memo, is to follow the previous code, when necessary to check whether it has been calculated, if it is, then directly use, if not, then calculate, and save the results. The second way of thinking is the bottom-up approach, regardless of need, first of all solve the sub-problem, and then to solve the problem of a higher level, but note that we need to start with the smallest sub-problem, in order to increase the scale, so every time the problem is solved, its sub-problems have been calculated, direct use can.
top-down method with memo:
int Memoized_cut_rod_aux (int* p, int n, int* r) {if (R[n] >= 0) {return r[n];} int q = -1;if (n = = 0) {q = 0;} else {for (int i = 1; I <= n; i++) {q = max (q, P[i] + memoized_cut_rod_aux (p, n-i, R) );}} R[n] = Q;return q;} /* * Top-up Cut-rod process */int Memoized_cut_rod (int* p, int n) {int* r = new Int[n + 1];//initialize R array, r array to hold, maximum return value for a solution, for n long steel bars Say, there are n+1 kinds of cutting schemes, so the array n+1 long for (int i = 0; I <= N; i++) {R[i] =-1;} Return Memoized_cut_rod_aux (P, N, r);}
Bottom-up method:
/* Bottom-up approach, first calculate the smaller sub-problem, and then calculate the larger sub-problem, because the larger sub-problem depends on the answer of smaller sub-problems, so in the calculation of larger sub-problems, there is no need to calculate the smaller sub-problem, because the answer has been calculated, and stored up */int Bottom_up_cut_rod (int p[], int n) {int* r = new Int[n + 1];r[0] = 0;//r[0] is initialized to 0 because 0 long steel bars have no gain for (int j = 1; J <= N; j + +) {int q = -1;/* * Here do not start with i=0, because i=0 is not appropriate, because the total length here is J, and the Division is the division between I and J-i, if I equals 0, then * means to know the value of R[j-0]=r[j] is the best division of the J-length of the proceeds, But we don't know here. And for p[0] there is no meaning in itself * The index of meaningful data in the P array is from 1 to n */for (int i = 1; I <= J; i++) {q = max (q, p[i] + r[j-i]);//}r[j] = q;} return r[n];}
The time complexity of both of the above algorithms is O (n^2).
Refactoring Solutions
The above code only gives the optimal yield value, but does not give the optimal income is in the kind of cut distribution mode, such as n=9, the best yield is 25, to be divided into 3 and 62 segments. Here you can use another array s to store the segmentation situation, such as s[9] storage 3, and then we let n=9-3, we can get s[6] the best segmentation situation, found that is 6, so there is no need to continue.
You only need to modify the code slightly to achieve the goal:
#include < Iostream>using namespace std;/* * The structure of the stored result, which contains r and s two arrays, respectively preserving the best profit and the best yield when the segment value */struct result {int* r;int* s;int len; Result (int L): R (), S (), Len (l) {r = new Int[len];s = new Int[len];r[0] = 0;} ~result () {delete[] r;delete[] s;}; result* extended_bottom_up_cut_rod (int p[], int n) {result* res = new result (n + 1); int q = -1;//The outer loop represents a reserved non-cutting segment for (in t i = 1; I <= N; i++) {//The inner loop represents the segment to be segmented and requires the best split for (int j = 1; J <= I; j + +) {if (Q < p[j] + res->r[i-j]) {q = p[j] + res-> R[i-j];res->s[i] = j;}} Res->r[i] = q;} return res;} int main () {int p[] = {0, 1, 5, 8, 9, Ten, p, p, C, p, n = 9;result* res = Extended_bottom_up_cut_rod (n); Out << "Best benefits:" << res->r[9] << endl;//loop output The actual best segment length cout << "segment condition:"; while (n > 0) {cout <& Lt Res->s[n] << "; n = n-res->s[n];} Delete Res;return 0;}
Running the above program, we can get the best yield of 9 steel pipe and the corresponding cutting situation:
Best Yield: 25
Segment situation: 3 6
"The introduction of Algorithms" The Problem of "steel tube cutting" in dynamic programming