[Beauty of Data Structure and Algorithm] 03-Complexity Analysis (Part 2): Analysis of the best, worst, average, and time-sharing complexity

  In the previous blog, we summarized the complexity of big O notation and analysis skills, and also listed some examples of common complexity analysis. In this blog, take a look at the following four knowledge points:

  • Best case time complexity
  • Worst case time complexity (worst case time complexity)
  • Average case time complexity
  • Amortized time complexity

1. Best and worst case time complexity

  Old rules, we first look at an example:

1  // n represents the length of the array array 
2  int find ( int [] array, int n, int x)
 3  {
 4      int i = 0 ;
 5      int pos = -1 ;
 6  
7      for (; i <n; ++ i)
 8      {
 9          if (array [i] == x) pos = i;
 10      }
 11      
12      return pos;
 13 }

  The function of this code is to find the index of the variable x in the unordered array. If it is not found, it returns -1. It is not difficult to see that the time complexity of the above code is O (n).

  However, to find a piece of data in an array, it is not necessary to traverse the entire array every time. If you find it halfway, you can end early. The processing of the above code is not efficient enough, let's add a processing to jump out of the loop:

1  // n represents the length of the array array 
2  int find ( int [] array, int n, int x)
 3  {
 4      int i = 0 ;
 5      int pos = -1 ;
 6  
7      for (; i <n; ++ i)
 8      {
 9          if (array [i] == x)
 10          {
 11              pos = i;
 12              break ;
 13          }
 14      }
 15      
16     return pos;
17 }

  At this point the question is coming, the optimized code may jump out in the middle. Is its time complexity still O (n)? If the first element in the array is x, the time complexity should be O (1). If x does not exist in the array, then the time complexity is O (n). The method mentioned earlier does not seem to solve this problem.

  Therefore, we have to introduce three concepts: best-case time complexity , worst-case time complexity, and average-case time complexity . The best case time complexity and the worst case time complexity are not difficult to understand. We focus on analyzing the average case time complexity.

 

Second, the average time complexity

  The best-case time complexity and the worst-case time complexity are too extreme, and the probability of occurrence is not large. It is more scientific to adopt the average case time complexity.

  We still use the above example to analyze. There are n + 1 cases for the position of x in the array, which are 0 ~ n-1 and not in the array. We add up the number of elements that need to be traversed in various situations, and then divide by n + 1, which is the average number of elements that need to be traversed:

  

  According to the simplified method described earlier, the coefficients, low-order, and constants are removed, and the average time complexity is O (n).

  Although this conclusion is correct, in fact, the calculation process is slightly problematic-the probability of these n + 1 cases is different. x is either in the array or not in the array, but the probabilities corresponding to these two cases are difficult to calculate. For ease of expression, we might as well set its probability to 1/2. If x is in the array, the probability of appearing at each position is the same, 1 / n. In summary, the probability that x appears anywhere in the array is 1 / (2n). If the above probabilities are considered, the mathematical expression of the average time complexity is as follows:

   

  This value is the weighted average (expected value) in probability theory. So the full name of the average time complexity should be the weighted average time complexity or the expected time complexity .

  After introducing probability, the weighted average time complexity of this code is still O (n).

  In most cases, we do not need to distinguish between the best, the worst, and the average case time complexity. Many times, we can meet the demand with one complexity. Only when the same block of code has different magnitudes in time complexity, we will use these three complexity notation to distinguish.

 

3. Time-sharing complexity

   So far, we have sorted out most of the complexity analysis of the algorithm. Let's look at a more advanced concept , the amortization time complexity , and its corresponding analysis method, amortization analysis (or amortization analysis ).

  Amortized time complexity sounds a bit like average time complexity, and these two concepts are indeed very easy to confuse. As mentioned earlier, in most cases, there is no need to distinguish between the best, worst and average complexity. The average complexity is only used in some special cases. The application scenarios of time-sharing complexity are more special and limited.

  The old rules, we still analyze through examples:

1  // array means an array of length n
 2  // array.length in the code is equal to n 
3  int [] array = new  int [n];
 4  int count = 0 ;
 5  
6  void insert ( int val)
 7  {
 8      if (count == array.length)
 9      {
 10          int sum = 0 ;
 11  
12          for ( int i = 0 ; i <array.length; ++ i)
 13          {
 14             sum = sum + array[i];
15         }
16 
17         array[0] = sum;
18         count = 1;
19     }
20 
21     array[count] = val;
22     ++count;
23 }

  The above code implements a function to insert data into an array. When the array is full, that is, count == array.length in the code, we use a for loop to traverse the array and sum, put the sum value after the sum in the first position of the array, and then put the new data insert. However, if there is free space at the beginning of the array, the data is directly inserted into the array.

  What is the time complexity of this code?

  In the ideal case, there is free space in the array, we only need to insert the data into the position of the array index as count, so the time complexity of the best case is O (1).

  In the worst case, there is no free space in the array, we need to do a traversal and sum of the array, and then insert the data, so the worst-case time complexity is O (n).

  What is the average time complexity? The answer is O (1). We can still analyze by the probabilistic method mentioned earlier.

  Assuming that the length of the array is n, we can divide it into n cases according to the location of data insertion, and the time complexity of each case is O (1). In addition to this, there is an "extra" situation where data is inserted when the array has no free space. The time complexity at this time is O (n). Moreover, the probability of occurrence of these n + 1 cases is the same, which is 1 / (n + 1). Therefore, according to the calculation method of weighted average, the average time complexity we obtained is:

  

  In fact, the average complexity of this example does not need to be so complicated, without introducing knowledge of probability theory.

  Let's compare the two examples of find () and insert () to illustrate:

  • In extreme cases, the find () function has O (1) complexity. But in most cases, insert () has a time complexity of O (1). Only in a few cases, the complexity is relatively high, O (n).
  • For the insert () function, the insertion frequency of O (1) time and the insertion of O (n) time complexity are very regular, and there is a certain timing relationship between before and after. (n) After the insertion, followed by n-1 O (1) insertion operations, cyclically.

  For this special scenario, we introduced a simpler analysis method: amortization analysis method . We have a name for the time complexity obtained through amortization analysis, which is called amortized time complexity .

  How to use amortization analysis method to analyze the amortized time complexity of the algorithm? We still use insert () as an example. Each O (n) insert operation will be followed by n-1 O (1) insert operations, so the time-consuming operation is spread over the next n-1 time-consuming operations. Amortized, the average time complexity of this group of continuous operations is O (1). This is the general idea of ​​amortized analysis.

  When we perform a set of continuous operations on a data structure, in most cases, the time complexity is very low, only in some cases, the time complexity is relatively high, and there is a sequential timing relationship between these operations. At this time, we This group of operations can be analyzed together to see if the time-consuming operation with higher time complexity can be evenly distributed to other operations with lower time complexity. In situations where amortized time complexity analysis can be applied, generally the amortized time complexity is equal to the best case time complexity .

  Amortized time complexity is a special average time complexity, we do not need to spend too much effort to distinguish between them. What we should master most is its analysis method, amortization analysis. As for whether the result of the analysis is called average or average, this is just a statement, not important. A brief summary of their application scenarios, if you encounter, you can understand the principle.

 

Four, content summary

  This blog post introduces four types of time complexity:

  • Best case time complexity
  • Worst-case time complexity
  • Average time complexity
  • Time-sharing complexity

  The reason for introducing these complexity concepts is that, for the same piece of code, the complexity measurement level may be different under different input conditions. After introducing these concepts, we can express the execution efficiency of a piece of code more comprehensively.

Thinking questions

  Analyze the time complexity of the following add () function:

1  // Global variable, array of size 10, length len, subscript i. 
2  int array [] = new  int [ 10 ]; 
 3  int len = 10 ;
 4  int i = 0 ;
 5  
6  // Add an element to the array 
7  void add ( int element)
 8  {
 9      if (i> = len )
 10      {
 11          // Insufficient array space, re-apply for a double-sized array space 
12          int new_array [] = new  int [len * 2];
 13          // Copy the data in the original array array to new_array 
14          for ( int j = 0 ; j <len; ++ j)
 15          {
 16              new_array [j] = array [j];
 17          }
 18          // new_array copied to array, array size is now twice the len 
. 19          Array = new_array;
 20 is          len = 2 * len;
 21 is      }
 22 is      // the element is placed in position i of the index, an index i is incremented by 
23 is      Array [i ] = element;
 24      ++ i;
 25 }

My point of view

  There are n + 1 cases. When the array space is sufficient, the time complexity is O (1). When the array is full, the time complexity is O (n).

  Average time complexity:

  

  At this time, it is O (1).

  Time-sharing complexity: O (1).

Guess you like

Origin www.cnblogs.com/murongmochen/p/12688624.html