One question per day (17) -- adjust a pile of cakes (recursion)

Source: Internet
Author: User

I. Problem:

On Friday evening, a group of colleagues drank a few more drinks at the hard drive bar near the higemma building. What do programmers talk about after a few more drinks? Naturally, it is an algorithm problem. A colleague said, "I used to work in a restaurant and customers often ordered a lot of pancakes. The size of the cakes in the store is different. I used to set a pile of cakes in order of size before arriving at the customer's dinner table-small on top and big on the bottom. Because I held the plate with one hand, I had to use the other hand. I grabbed the top cakes and turned them upside down. After several times, the order is arranged. I thought later, this is actually an interesting Sorting Problem: if there are n pancakes of different sizes, it should be repeated several times at least to achieve the final size and order of results ?"

Can you write a program that outputs the optimal pie turning process for N pancakes of different sizes? (ReferFlyingherts column)

I really want to have a big head and a little confused about this problem ~ + _ + ~

 

Ii. Analysis:


All the statuses of N pancakes after being flipped can form a tree. Searches for a node with the lowest level in the tree.

Because the number of nodes on each layer increases exponentially, when n is large, the tree is traversed with breadth first, and there may not be enough memory to save the intermediate results (considering the two nodes on each layer, operations such as rotation and shift can be used to convert each other. A function can be used to generate the state of each layer. In this case, the breadth-first method can be used. Therefore, depth-first is used. However, this tree is infinitely deep, and the search depth must be limited (that is, the upper limit of the minimum number of folds). When the depth reaches this value, the search will not continue. The minimum number of flipped ings must be smaller than the number of flipped ings required by any flip scheme. Therefore, you only need to construct a scheme and take the number of flipped ings as its initial value. The simplest flip solution is to flip the largest unseated pancake, locate its position in the final result, and flip it to the position. Therefore, for the number in n-1 and 2 between the pancake, up to 2 x (n-2) Flip times, the remaining 0 and 1 at most one flip, therefore, the upper limit of the minimum number of Flip is: 2 * (n-2) + 1 = 2 * n-3 (from the Internet can be found to the latest research results of this upper limit: the upper limit is 18/11 * n ), of course, it is best to directly calculate the number of flipped ings using this scheme as the initial value.

 

 

3. Reduce traversal times:

 

1) reduce the initial value of the "Maximum number of flipped ings" and use the previously mentioned flip scheme. Set the number of flipped ings to the initial value. For example {,} in the book, the initial value can be 10.

 

2) Will the traversal be reduced if the processed status appears? The answer is no. In depth traversal, A subtree must be traversed before the next subtree can be traversed. if a solution is located at the back of a layer, if you do not allow processing of a status that has already occurred, you may need to search several times to find the solution. However, when you allow processing of a status that has already occurred, you may soon find the solution, and reduce the "upper limit of the minimum number of flipped ings", so that more branches can be cut off, but the number of traversal nodes can be reduced. For example, if two subtree A and subtree B are searched for subtree A, after 100 times, a corresponding solution with a flip count of 20 can be obtained. Then, search for subtree B, after 20 times, you can get a solution with 10 flipped ings. If you cannot process existing statuses, it will take 100 times to traverse subtree A before traversing B, however, if you are allowed to flip back to the last status, the search will be performed across a and B, and the solution of subtree B may be found as long as 70 times (10 + 2 = 12 times ), in this case, the upper limit of the number of flipped ings is relatively small, so you can ignore more unnecessary searches. Taking {1.3, 195,} in the book as an example, by Program (_ pancake_1.cpp), you cannot search for times when turning back to the last status, you can only search 116 times for the last status.

 

3) if the last few pancakes are in place, consider the first few pancakes. The status (0, 1, 3, 4, 2, 5, 6), number 5, and 6 are in place. You only need to consider the first five pancakes, that is, the status (0, 1, 3, 4, 2 ). If an optimal solution moves an existing pancake from a certain flip and all the pancakes after the pancake are in place, A series of statuses starting from this flip. You can remove this pancake from it to get a series of new statuses. It is inevitable that a new solution can be designed to correspond to the new state of this series, and the solution will not use more flipped times than the original one.

 

4) it is estimated that the minimum number of times each status needs to be flipped (that is, the lower limit), plus the current depth, if the size is equal to the upper limit, you do not need to continue traversing. The lower limit can be determined as follows: from the last position, locate the first pancake number that is different from the final result position (that is, exclude the last few pancakes that are already in place ), from this position to the first position, calculate the number of consecutive neighboring pancakes, plus 1. Each Flip can only reduce the number of disconsecutive times by 1, but many people will ignore this situation: when the largest pancake is not in place, it must be put in place by one flip, however, this flip does not change the number of disconnections. (You can add a larger pancake at the end, so that this flip can change the discontinuous number .) For example, a pair of States (, 6) is equivalent to a State (, 2), because 1, 3, 4, 2 are not continuous, therefore, the lower limit is 2 + 1 = 3. The lower limit can also be determined as follows: add an existing pancake that is larger than all the pancakes at the end, and then calculate the discontinuous number. For example: (, 2), can be considered as (,), 1 and 3
, 4, 2, 2, and 5 are not consecutive. The lower limit is 3.

 

5) In most cases, the larger the upper limit of the number of flipped ings, the more search times. You can use the greedy algorithm to locate a solution as soon as possible by adjusting the priority of all possible flipped requests, thus reducing the number of searches. For example, you can first search for the flip that reduces the "lower limit", and then the flip that keeps the "lower limit" unchanged. Finally, you can search for the flip that increases the "lower limit. If you do not change the "lower limit", you can also sort it again based on the impact of the next flip on the "lower limit. As a result of priority sorting, the possibility of turning back to the last status can be further reduced to reduce the number of searches.

6) Other pruning methods:

Assume that the "upper limit" is min_swap during the M-th flip.

If you flip a pancake at a certain position to hold all the pancakes in place (that is, the number of flags is exactly m), the minimum number of flags that you can flip at other locations must be larger, therefore, none of these locations can be searched.

If, after a certain position is flipped, the "lower limit" is K and K + M> = min_swap, the new "lower limit" KK is greater than K, there are both KK + M> = min_swap, so you can not search. This pruning method is further supplemented with the previous "Adjusting the flip priority.

 

In addition, when you flip a pancake, only the change of the positions of the two pancakes affects the "lower limit". Therefore, you can record the "lower limit" of each status and perform the next flip, the "lower limit" of the new status can be determined only after several comparisons ". (When judging the number of disconnections, it is best to write-1 <= x & x <= 1, rather than X = 1 | x =-1. For int X; A <= x & x <= B, the compiler can optimize it to unsigned (X-a) <= B-.)

 

Result:

For example {,} in the book }:

 

Flipped back to last status

Number of times a function is called

Number of times the flipped function is called

1.3 _ pancake_2

Not Allowed

29

66

1.3 _ pancake_2

Allow

33

74

1.3 _ pancake_1

Not Allowed

195

398

1.3 _ pancake_1

Allow

116

240

(This example is quite special. Code 1.3 _ pancake_2.cpp (the major difference with 1.3 _ pancake_1.cpp is that it adds a judgment on the flip priority, Code download), When the last status cannot be flipped back and the initial value of min_swap is 2*10-2 = 18, the search function is called for 29 times, and the flip function is 56 times ).

 

The search order has a great impact on the results. If you set rows 1.3 _ pancake_2.cpp to 152nd:

For (INT Pos = 1, last_swap = cake_swap [step ++]; POS <size; ++ POS ){

This line is changed:

For (INT Pos = size-1, last_swap = cake_swap [step ++]; POS> = 1; -- POS ){

Only the search order is adjusted, and the number of calls to the search function is reduced from 29 to 11 (corresponding flip method: 1st, 6, 9, 6, 9, 6). The number of 10th pancakes is calculated, the total time is also reduced from 38 seconds to 21 seconds .)


4. source code and Analysis

# Include <iostream> # include <cassert> # include <cstdio> class laobing {PRIVATE: int * m_cakearray; // an array of pancake information int m_ncakecnt; // Number of pancakes int m_nmaxswap; // The maximum number of exchanges. According to the previous inference, the maximum value here is // m_ncakecnt * 2 int * m_swaparray; // The int * m_reversecakearray of the exchange result array; // The int * m_reversecakearrayswap; // int m_nsearch as the result array of the current flipped pancake exchange; // public: laobing () {m_ncakecnt = 0; m_nmaxswap = 0 ;}~ Laobing () {If (m_cakearray! = NULL) delete m_cakearray; If (m_swaparray! = NULL) delete m_swaparray; If (m_reversecakearray! = NULL) delete m_reversecakearray; If (m_reversecakearrayswap! = NULL) delete m_reversecakearrayswap;} // calculate the pie flip information // @ Param // pcakearray storage pancake Index Array // ncakecnt pancake count // void run (int * pcakearray, int ncakecnt) {Init (pcakearray, ncakecnt); m_nsearch = 0; search (0);} void moutput (int * cakearray, int ncakecnt, int * m_swaparray, int m_nmaxswap) {int t; for (INT I = 0; I <m_nmaxswap; I ++) // swap times {for (INT J1 = 0, J2 = m_swaparray [I]; j1 <J2; J1 ++, J2 --) // reverse Array {T = cakearray [J1]; cakearray [J1] = cakearray [J2]; cakearray [J2] = T ;}for (int K = 0; k <ncakecnt; ++ K) printf ("% d", cakearray [k]); printf ("\ n") ;}} void output () // The number of times the pancake is flipped {for (INT I = 0; I <m_nmaxswap; I ++) {printf ("% d", m_swaparray [I]);} printf ("\ n | search times |: % d \ n", m_nsearch); printf ("Total swap times = % d \ n", m_nmaxswap); moutput (m_cakearray, m_ncakecnt, m_swaparray, m_nmaxs WAP); // output switching process} PRIVATE: //// initialize the array information // @ Param // pcakearray storage pancake Index Array // ncakecnt pancake count // void Init (int * pcakearray, int ncakecnt) {assert (pcakearray! = NULL); Assert (ncakecnt> 0); m_ncakecnt = ncakecnt; // initialize the pancake array m_cakearray = new int [m_ncakecnt]; Assert (m_cakearray! = NULL); For (INT I = 0; I <m_ncakecnt; I ++) {m_cakearray [I] = pcakearray [I];} // set the maximum number of exchanges m_nmaxswap = upbound (m_ncakecnt); // initialize the exchange result array m_swaparray = new int [m_nmaxswap + 1]; Assert (m_swaparray! = NULL); // initialize the intermediate exchange result information m_reversecakearray = new int [m_ncakecnt]; for (INT I = 0; I <m_ncakecnt; I ++) {m_reversecakearray [I] = m_cakearray [I];} m_reversecakearrayswap = new int [m_nmaxswap];} int upbound (INT ncakecnt) // find the upper bound of the current flip {return ncakecnt * 2;} int lowerbound (int * pcakearray, int ncakecnt) // find the lower bound of the current flip {int T, ret = 0; // determine the minimum number of for (INT I = 1; I <ncakecnt; I ++) exchanges based on the sorting information of the current array) {// determine whether two adjacent pancakes are sorted by size on adjacent T = pcakearray [I]-pcakearray [I-1]; If (t = 1) | (t =-1) {} else {RET ++;} return ret;} // The main function of sorting void search (INT step) {int I, nestimate; m_nsearch ++; // estimate the minimum number of exchanges required for this search. nestimate = lowerbound (m_reversecakearray, m_ncakecnt); If (Step + nestimate> m_nmaxswap) return; // If the sorting is completed, the output result is if (issorted (m_reversecakearray, m_ncakecnt) {If (Step <m_nmaxswap) {m_nmaxswap = step; for (I = 0; I <m_nmaxswap; I ++) m_swaparray [I] = m_reversecakearrayswap [I];} return ;}// recursively flip for (I = 1; I <m_ncakecnt; I ++) {revert (0, I); // reverse m_reversecakearrayswap [STEP] = I; // which search (Step + 1) is reversed in the first step ); revert (0, I) ;}/// true: sorted // false: unordered // bool issorted (int * pcakearray, int ncakecnt) {for (INT I = 1; I <ncakecnt; I ++) {If (pcakearray [I-1]> pcakearray [I]) {return false ;}} return true ;} /// flip the pancake information // void revert (INT nbegin, int nend) {assert (nend> nbegin); int I, j, T; // flip the pancake information for (I = nbegin, j = nend; I <j; I ++, j --) {T = m_reversecakearray [I]; m_reversecakearray [I] = m_reversecakearray [J]; m_reversecakearray [J] = T ;}}; int main () {laobing ll; // here ll cannot be added with brackets laobing * l = new laobing (); int AA [10] = }; l-> Run (AA, 10); L-> output (); LL. run (AA, 10); Return 0 ;}

Output result:

4 8 6 8 4 9
| Search times |: 172126
Total swap times = 6
5 6 1 2 3 4 9 8 7 0
7 8 9 4 3 2 1 6 5 0
1 2 3 4 9 8 7 6 5 0
5 6 7 8 9 4 3 2 1 0
9 8 7 6 5 4 3 2 1 0
0 1 2 3 4 5 6 7 8 9

5. Optimization:

On the Internet, the source code of "the beauty of programming" "6th clicks" is posted. The following problems occur during compilation:

1) assert should be assert

2) m_arrswap is not defined and should be changed to m_swaparray

3) The init function has two for loops. If the last variable I is not defined, change I to int I.

In addition, every time the run function is run, the init function is called, and a new memory is applied, but the original memory is not released, resulting in Memory leakage. If (Step + nestimate> m_nmaxswap) also causes cross-border access to the m_reversecakearrayswap array, making the program unable to run normally.

 

The inefficiency of the program in the book is mainly due to the fact that the boundary conditions are not properly considered during pruning and judgment, the following modifications can be made:

1) if (Step + nestimate> m_nmaxswap)> changed to> =.

2) When determining the lower bound, if the largest pancake is not in the last position, it should be flipped once more. Therefore, insert a row before the lowerbound function return ret:

If (pcakearray [nCakeCnt-1]! = NCakeCnt-1)

RET ++;

3) N pancakes, flip the largest N-2 pancakes need up to 2 * (n-2) times, the remaining 2 up to 1 times, so the upper limit is 2 * N-3, therefore, m_nmaxswap initial value can take 2 * n-3 + 1 = 2 * N-2, so that each step and m_nmaxswap judgment can take the big equal sign.

4) use the method mentioned in the book to determine the "upper limit" to directly construct an initial solution, and set the number of flipped ings to the initial value of m_nmaxswap.

 

One or two changes can reduce the number of searches from 172126 to more than 20 thousand. Both changes can reduce the number of searches to 3475. If you change the value of 3rd, the number of searches is reduced to 2989. If you use the 4 method (the initial value is 10), the number of searches can be reduced to 1045.


Vi. Thinking

The P22 page in the book mentions dynamic planning, but at last it provides a recursive method that is generally applicable to the optimization problem but may be the worst in efficiency. This is confusing: it is not beautiful !? If we can prove that the problem meets the conditions of dynamic planning or greedy algorithm, the time complexity of solving the problem will be reduced to polynomial time or even n ^ 2. However, the dynamic planning mentioned in the book is not used in the end, and there is no clear reason. I think it is a loss (it should not be a mistake ). Let's look at why there is no dynamic planning or greedy algorithm.

We know that the dynamic planning method is a bottom-up method to obtain the optimal solution of the problem. It uses the optimal solution of the subproblem to construct the global optimal solution. The problem solved by using dynamic planning must meet two conditions: (1) optimal substructure (2) substructure is overlapping. Condition (1) enables us to construct the global optimal solution using the optimal solution of the subproblem, and condition (2) is that we can use the overlapping substructure to reduce the number of operations during the computation process. In addition, in the introduction to algorithms, we also use Directed Graphs with no-Permission Shortest Path and no-Permission longest path as examples to propose that condition (3) subproblems must be independent.

First, we assume that there is an optimized sub-structure for the pancake problem. Suppose we have n pancakes and number them from small to large in their radius. The tuning sub-structure tells us that for I pancakes, we only need to sort the first (I-1) and then normalize the I; or first arrange the 2nd to I, finally, we can return the first bitwise, or find a position K [I <= k <j], like the matrix multiplication and parentheses, so that we can first sort the first K, sort the J-K and then merge them to find an optimal flip policy...

According to the calculation process of the dynamic planning algorithm, we need an N * n matrix m, whereM [I] [J] indicates the number of folds required to sort the pancakes numbered I to J.. But we can really go from M [0] [0 .. j-1] and M [1] [J + 1], or is m [I] [J] calculated using the same column value as M [I] [J? If possible, we can obtain the polynomial time algorithm.

Let's take a look at the example in the book: (top), (bottom). Our final goal is to calculate M [0] [9].
Here we take M [0] [4] as an example. I have provided the matrix for calculation below:
0 1 2 3 4 5 6 7 8 9
------------------------
0 | 0 1 (1) {1} [?]
1 | 0 1 (1) {1}
2 | 0 1 (1)
3 | 0 0
4 | 0
------------------

In fact, if we want to forward the 0-4 pancake (Note: The Pancake number is also equivalent to its radius) to a positive sequence (it doesn't matter if there are other pancakes in the middle), according to the results given by the program, we need to flip three times, namely [2, 5, 9] (the second (starting from scratch), five, and nine pancakes in the flip queue respectively, the number here is not the Pancake number ):
[1] [2] [3] 6 5 [4] 9 8 7 [0]
[4] 5 6 [3] [2] [1] 9 8 7 [0]
[0] 7 8 9 [1] [2] [3] 6 5 [4]

We know that there is an arrangement of pancakes hidden behind each number in the Matrix. For example, M [0] [4] corresponds to, 5, and 4.
Therefore, the selection of each M [I] [J] contains a change in the order of its sub-arrangement.
When calculating M [I] [J], we need to calculate the flip structure consisting of all the partitions of the I-j pie (not all of which are 1, obtain the final value of the most M [I] [J] with the lowest number of flipped requests. For example, when calculating M [0] [4], you need to view:

/** Sort numbers 0 and 1-4 respectively, and combine the numbers into the number of flipped ings required for Order */
M [0] [0], M [1] [4]

/** Same as above */
M [0] [1], M [2] [4]

/** Same as above */
M [0] [2], M [3] [4]

/** Same as above */
M [0] [3], M [4] [4]

/* Sort numbers 0, 1, 2, and 3-4 respectively, and combine the four into the number of flipped ings required for the order.
* Note that this includes the issue of dividing four groups again!
*/
M [0] [0], M [1] [1], M [2] [2], M [3] [4]
... // Omitted in the middle
M [0] [3], M [4] [4]

In addition, we can eliminate the scheme (pruning?) that exceeds the maximum number of reverse operations ?), The time complexity of all the operations we have performed is no longer a polynomial time, but is no different from the previously mentioned recursive method.

The cause of this phenomenon is:The optimal solution of a subproblem is not necessarily the overall optimal solution.So when we are dealing with the entire problem,We need to traverse all possible sub-problems and calculate the cost of the problem so that we can finally make a choice that is conducive to the overall problem.

Therefore, the assumption at the beginning is that the pancake problem has the assumption of optimizing the sub-structure. Therefore, we cannot use dynamic planning or greedy algorithms.


But when talking about the "choice" of each step, I remember an algorithm called "A *" in the introduction to algorithms, the idea is to "Calculate" the final cost when selecting each step, and traverse the branch with the lowest current cost. The "estimation" result may not be the final price, but only serves as the basis for Branch selection. If anyone is interested, just do it.

 

 

 

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.