Detailed explanation of divide and conquer Algorithms
I. Basic Concepts
In computer science, divide and conquer is an important algorithm. The literal explanation is "divide and conquer". It is to divide a complicated problem into two or more identical or similar subproblems, and then divide the subproblems into smaller subproblems ...... Until the final sub-problem can be solved simply and directly, the solution of the original problem is the merge of the sub-problem solutions. This technique is the basis of many efficient algorithms, such as sorting algorithms (fast sorting, Merge Sorting), Fourier Transformation (Fast Fourier transformation )......
The computing time required for any problem that can be solved by a computer is related to its scale. The smaller the problem, the easier it is to solve it directly, and the less computing time it takes to solve the problem. For example, for sorting of n elements, when n = 1, no calculation is required. When n = 2, you only need to make a comparison to sort the order. When n = 3, you only need to make three comparisons ,.... When N is large, the problem is not easy to handle. It is sometimes quite difficult to solve a large-scale problem directly.
Ii. Basic ideas and strategies
The principle of divide and conquer law is to divide a big problem that cannot be directly solved into the same problems of small scale, so that they can be cracked and managed separately.
The sub-governance policy is: For a problem with a scale of N, if the problem can be easily solved (for example, the scale of N is small), it will be solved directly, otherwise, it will be divided into k subproblems of small scale. These subproblems are independent of each other and are in the same form as the original problem, recursively solving these subproblems, then, combine the solutions of each subproblem to obtain the solution of the original problem. This algorithm design strategy is called divide and conquer.
If the original problem can be divided into k sub-problems, 1 <k ≤ n, and all of these sub-problems can be solved, you can use the solutions of these sub-problems to find the solution of the original problem, this method is feasible. Sub-problems produced by the Division and control method are often small models of the original problems, which provides convenience for the use of recursive technology. In this case, the sub-problem type can be consistent with the original problem type, but its scale is constantly reduced. In the end, the sub-problem can be easily solved directly. This naturally leads to the generation of recursive processes. Grouping and Recursion are like twins. They are often applied to algorithm design at the same time, and many efficient algorithms are generated.
Iii. Application of the division and Control Law
The problems solved by the Division and control law generally have the following characteristics:
1) The problem can be easily solved by narrowing down to a certain extent.
2) The problem can be divided into several smaller issues, that is, the problem has the optimal substructure.
3) The solutions of subproblems decomposed by this problem can be combined into the solutions of this problem;
4) Each subproblem identified by the problem is independent of each other, that is, the subproblem does not include a public subproblem.
The first feature is that most problems can be met, because the computing complexity of the problem generally increases with the increase of the problem scale;
Article 2 features are the premise for applying the divide and conquer methodIt is also feasible for most problems. This feature reflects the application of recursive thinking ;,
The third feature is the key. Whether or not the divide and conquer method can be used depends on whether the problem has the third feature., IfIf the first and second features are met and the third features are not met, the greedy method or dynamic programming method can be considered..
Article 4 efficiency of features involving divide and conquerIf the sub-problems are not independent, the division and Control Law should do a lot of unnecessary work to solve the public sub-problems repeatedly. At this time, although the Division and Control Law can be usedGenerally, it is better to use dynamic programming..
Iv. basic steps of divide and conquer Law
There are three steps in recursion of each layer:
Step 1 decomposition: Resolve the original problem into subproblems that are small in size and independent from each other, in the same form as the original problem;
Step 2 solve: If the subproblem is small and easy to solve, the subproblem is solved directly. Otherwise, the subproblem is solved recursively.
Step 3 merge: Merge the solutions of each subproblem into the solutions of the original problem.
Its general algorithm design pattern is as follows:
Divide-and-conquer (P)
1. If | p | ≤n0
2. Then return (Adhoc (p ))
3. Split P into smaller subproblems P1, P2,..., PK
4. For I limit 1 to K
5. Do Yi nvidivide-and-conquer (PI) △recursively solves pi
6. t merge Merge (Y1, Y2,..., yk) △merge subproblem
7. Return (t)
| P | indicates the scale of the problem P; N0 is a threshold value, indicating that when the scale of the problem P does not exceed N0, the problem is easily solved and does not need to be further decomposed. Adhoc (P) is the basic sub-algorithm in the Division and control method, and is used to directly solve small-scale problem P. Therefore, when the size of P does not exceed N0, the algorithm adhoc (P) is used for solving the problem. Algorithm Merge (Y1, Y2 ,..., yk) is the merge sub-algorithm in the Division and control method. It is used to split p sub-problems P1, P2 ,..., corresponding solution of PK Y1, Y2 ,..., merge yks into P.
V. Complexity Analysis of divide and conquer Law
A division and control method divides the problem of N into k subproblems of N/m. The decomposition threshold value N0 = 1, and the ADHOC solution scale is 1, which consumes 1 unit of time. Then, it takes F (n) to resolve the original problem into k sub-problems and merge the solutions of K sub-problems into the original problem using merge. T (n) indicates the calculation time required to solve the problem of the division and Control Method | p | = n:
T (n) = k t (N/m) + f (N)
Obtain the solution of the equation through iteration:
Recursive equations and their solutions only give the T (n) value when n is the power of M, but if T (n) is considered smooth enough, then, the growth rate of T (n) can be estimated by the value of T (n) WHEN n is equal to the power of M. It is generally assumed that T (n) is monotonic increasing, so when MI ≤ n <MI + 1, t (MI) ≤ T (n) <t (MI + 1 ).
Vi. Typical problems that can be solved using the divide and conquer method
(1) binary search (2) Big integer multiplication (3) strassen matrix multiplication (4) Board coverage (5) Merge Sorting (6) Fast sorting (7) Linear Time Selection
(8) closest point to question (9) Round Robin schedule
(10) Linglong
7. Thinking process when designing procedures based on divide and Control Law
In fact, it is similar to the mathematical induction method to find the formula for solving the problem, and then design a recursive program based on the formula.
1. The solution must first find the minimum problem Scale
2. Then, consider the solution when the problem grows.
3. After finding the recursive function formula (various scales or factors), design the recursive program.
8. Below are some examples of the procedures for the Division and Control Law!
1: thinking about the big number problem (1) the logic of the division and control method is used to check whether it is feasible. Here we give an example of the 2000 power of 3, because the maximum integer is 2 billion, beyond the limit! To help explain whether sub-governance can be performed and how to perform sub-governance, see the formula 123 345 678 370*3 = 037 034. Here we can write it like this: 123*3 = 369 345*3 = 1035 678*3 = 2034 the combination is 369 1035 2034. What did you find in comparison with the original results? In fact, our ancestor invented the calculation tool of Abacus for a long time in ancient times. He recalled his calculation method, which is the 10-in-1 notation we usually call! Here, we can think of the 1000 hexadecimal notation, that is, when 1000 goes up to 1, then the maximum value on each bit can only be 999, but not 1000. Using this idea, let's look at the formula above! 369 1035 2034 369 + 1 035 + 2 034 (plus 1, get 370) (in 1, retain 037) (in 2, retain 034) the final result is 370 037 034. The result is exactly the same. This is what we call the 1000-forward notation. It is worth noting: why is the number represented by the 1000 hexadecimal notation the same as the number represented by the 10 hexadecimal notation? In fact, this is not the case. The 100 hexadecimal notation is the 1000 hexadecimal notation or even the 10000 hexadecimal notation, the final result is the same. I don't know if you find it? For example, in decimal 199, 100 is also 1 99th. in decimal 1099, 1000 is also 1 099 (note that this 0 cannot be omitted) and so on! Now, let's go back to the question. The formula above is split into three numbers, which reflects the idea of divide and control. First, he satisfies the first one: the second condition can be solved when it is decomposed to a certain small scale: each small scale has the optimal sub-structure (which can be expressed within the range of variables). The third condition: every small problem can be combined to form a solution to a big problem. The fourth condition is that each small problem is independent of each other. Of course, we can use the idea of splitting! Next, let's take a look at Question 3 to the power of 2000. Here we use the 10000 hexadecimal array notation !! The function source code is as follows:
Void randombignumfun (INT low, int high) // pass the parameter to the base number and index {unsigned int temp [1024] = {0}; // Initialize an array, used to store the 10000 hexadecimal data temp [0] = low; // initialize the first element as the required value! Int flag =-1; // The flag variable used to indicate whether to carry to the previous digit, and the number of unsigned int m_count = 1 saved by a colleague; // The technical variable, calculate the number of elements occupied by the array: int _ index and index; // two cyclic variables for (_ Index = 0; _ index In fact, the above functions need to pass in parameters. Here we will pass 3 and 2000. The running result is as follows (unimaginable): If you do not understand anything, please leave a message on my blog !! Thinking about the big number Problem (2): The above mentioned is the multiplication form of the big number. Next we will demonstrate the calculation method of the big number multiplication, for example: 27392361983108271361039746313 multiplied by 37261038163103818366341087632113, of course, I won't write such a large array here. I will just give a simple example, such as 234*456 = ???? Someone may ask: is it still necessary to divide and conquer this directly calculated result? Here I mainly talk about a general algorithm IDEA, so no matter how many digits you encounter, you can write it like this! Analysis before the algorithm: here, if we split 456 into 4, 5, 6, and multiply them by 234, what is the result? The answer is as follows: 234x456 ________________ 1404 1170 936 -------------------------------------- the structure is clear to everyone, but we need to use a program to keep this result. Let's set up a two-dimensional array to save the result, the first row of the array is 1404, the second row is 1170, and the third row is 936. Because it cannot be directly stored, We need to calculate the storage location: How many rows should the array have, how many columns are there? Here we need to know that the product of two three-digit numbers, the result is a maximum of 6 digits, a two-digit, a six-digit multiplication, the result is a maximum of 2 + 6 = 8 digits, so here the array should have 6 columns, and for the number of rows, it is determined by the multiplier, so here is 3 rows !! Temp [3] [6] = {0 1 4 0 4 0 1 1 1 7 0 0 0 9 3 6 0} each column is sequentially added with 1 0 6 7 0 4; the answer is exactly the answer we need. Well, I don't need to talk about it. Let's look at the code here !!
# Include <iostream> using namespace STD; inline int translate (char Str) {return (STR-48);} int _ tmain (INT argc, _ tchar * argv []) {char numstr1 [3] = {'2', '3', '4'}; char numstr2 [3] = {'4', '5 ', '6'}; int temp [3] [6] = {0}; signed int flag =-1; int temp_x = 0; int temp_y; int _ index, index; for (_ Index = 2; _ index> = 0; -- _ index) // The two repeated loops here are assigned to the two-dimensional array {temp_y = 5-temp_x; for (Index = 2; index> = 0; -- index, -- temp_y ){ Temp [temp_x] [temp_y] = translate (numstr2 [_ Index]) * translate (numstr1 [Index]); If (flag! =-1) {temp [temp_x] [temp_y] + = flag; flag =-1;} If (temp [temp_x] [temp_y]> = 10) {flag = temp [temp_x] [temp_y]/10; temp [temp_x] [temp_y] % = 10 ;}} if (flag! =-1) {temp [temp_x] [temp_y] + = flag; flag =-1;} + + temp_x;} int temp_sum [6] = {0 }; flag =-1; for (Int J = 5; j> = 0; -- J) // next, this loop adds the array of each column to the final result Array {for (INT I = 2; I> = 0; -- I) temp_sum [J] + = temp [I] [J]; If (flag! =-1) {temp_sum [J] + = flag; flag =-1;} If (temp_sum [J]> = 10) {flag = temp_sum [J]/10; temp_sum [J] % = 10 ;}} flag =-1; for (INT I = 0; I! = 6; ++ I) // here is the output result cout <temp_sum [I]; cout <Endl; System ("pause"); Return 0 ;}
The running result is as follows:
The result is the same as we expected, but this program has a small bug, because two three digits are multiplied, not necessarily six digits, maybe five digits, so remember to judge whether the first one is 0 when outputting it! Here, the reader can adapt the above program as a function to calculate the product of any two numbers, but then the array needs to be dynamically created! 2: The combination of recursion and sub-governance (tower of Hanoi) regarding recursion, he can also be seen as a manifestation of the sub-governance idea. The recursion problem is everywhere, but he is always a difficult one, here I will mainly talk about the classic issue of tower pig in recursion! As for what is qingta, if you don't know anything about Baidu, here I will explain it in the program:
# Include <stdio. h> # include <stdlib. h> static int COUNT =-1; void move (char X, char y); // void Hanoi (int n, char one, char two, char three); // Declaration of the Hanoi function \ int main () {int m; printf ("Enter the total number of boards to move :"); scanf ("% d", & M); printf ("The following is the moving scheme of % d boards: \ n", m); Hanoi (M, 'A ', 'B', 'C'); System ("pause"); Return 0;} void Hanoi (INT N, char one, char two, char Three) // define the Hanoi function // move n disks from one to three by using two. {If (n = 1) Move (o Ne, three); else {Hanoi (n-1, one, three, two); // first move n-1 from one to twomove (one, three ); // then move the last n from one to threehanoi (n-1, two, one, three); // finally move n-1 from two to three} void move (char X, char y) // defines the move function {count ++; If (! (Count % 5) printf ("\ n"); printf ("% C moved to % C", x, y );}
If you enter five boards, the moving scheme is as follows: The result shows the moving process. As for how internal implementation is achieved, you can draw them by yourself to see it at a glance !! 2: The Binary Search Method (Binary Search) is used to narrow down the scope! The idea of the Bipartite method is relatively simple, that is, the upper and lower limits are used to narrow down the search boundary. When it falls within a certain range, it can be solved. This algorithm is also very common, I will not describe it here
#include <iostream>using namespace std;int binary_sreach(int *array,int len,int elem);int main(int argc, char* argv[]){int a[7] = {1,2,3,6,8,9,99};cout<<binary_sreach(a,7,6);system("pause");return 0;}int binary_sreach(int *array,int len,int elem){int low = 0;int high = len - 1;int middle;while (low <= high){middle = (low + high)/2;if(array[middle] == elem)return middle;else if(array[middle] > elem)high = middle;elselow = middle;}return -1;}
The running result can also be imagined as 3. the binary method is actually so simple, and the idea of sub-governance is obviously used here !! Divide and conquer is a very important algorithm concept. In various program design competitions, divide and conquer are often used to answer questions. Here we only list the solutions to some classic problems. The specific problems still need to be analyzed, I will keep updating it in the future and record it every time I meet it and share it with you!