[Data structure] Time and space complexity of the algorithm

Table of contents

1. What is an algorithm?

1.1 Algorithm complexity

2. The time complexity of the algorithm

2.1 The concept of time complexity

Calculate how many times the ++count statement in Func1 has been executed in total

2.2 Asymptotic representation of big O

2.3 Examples of common time complexity calculations 

Example 1: Execute 2N+10 times

Example 2: Execute M+N times

Example 3: Executed 100000000 times

Example 4: Calculate the time complexity of strchr

Example 5: Calculate the time complexity of BubbleSort

Example 6: Calculate the time complexity of BinarySearch

Example 7: Time Complexity of Computing Factorial Recursive Fac

Example 8: Calculate the time complexity of Fibonacci recursion Fib

3. Space complexity of the algorithm

Example 1: Calculate the space complexity of BubbleSort

Example 2: Calculate the space complexity of Fibonacci

Example 3: Calculate the space complexity of factorial recursive Fac

4. Common complexity comparison


1. What is an algorithm?

algorithm:

Algorithm : It is a well-defined calculation process that takes one or a set of values ​​as input and produces one or a set of values ​​as output. Simply put, an algorithm is a series of computational steps used to transform input data into output results.
Common applications for sorting / binary search
Algorithm features:

1. Infinity. An algorithm should contain finite steps of operations, not infinite. In fact, "finity" often means "within reasonable limits". If a computer is allowed to execute an algorithm that takes 1000 years to complete, although it is finite, it exceeds a reasonable limit, and people do not regard it as an effective algorithm.

2. Certainty. Every step in the algorithm should be definite , not vague and ambiguous. Each step in the algorithm should not be interpreted as a different meaning, but should be very clear. That is to say, the meaning of the algorithm should be unique and should not produce "ambiguity".

3. There are zero or more inputs . The so-called input refers to the necessary information obtained from the outside world when the algorithm is executed.

4. Has one or more outputs. The purpose of an algorithm is to solve it, and an algorithm without an output is meaningless .

5. Effectiveness. Each step in the algorithm should be able to perform efficiently. and get definite results.

1.1 Algorithm complexity

After the algorithm is compiled into an executable program, it needs to consume time resources and space ( memory ) resources when running. Therefore, to measure the quality of an algorithm, it is generally measured from two dimensions of time and space , that is, time complexity and space complexity.
Time complexity mainly measures how fast an algorithm runs , while space complexity mainly measures the extra space (how much memory) an algorithm needs to run . In the early days of computer development, computers had very little storage capacity. So I am very concerned about the space complexity. However, with the rapid development of the computer industry, the storage capacity of computers has reached a very high level. So we no longer need to pay special attention to the space complexity of an algorithm.

2. The time complexity of the algorithm

2.1 The concept of time complexity

Definition of time complexity: In computer science, the time complexity of an algorithm is a function ( mathematical functional formula, not those nested functions in c language ) that quantitatively describes the running time of the algorithm. Theoretically speaking, the time it takes for an algorithm to execute cannot be calculated. Only when you put your program on the machine and run it can you know it. But do we need to test each algorithm on the computer? It is possible to test them all on the computer, but it is very troublesome, so there is an analysis method of time complexity. The time spent by an algorithm is proportional to the number of executions of the statements in it, and the number of executions of the basic operations in the algorithm is the time complexity of the algorithm .

Calculate how many times the ++count statement in Func1 has been executed in total

Let's try it out:
// 请计算一下Func1中++count语句总共执行了多少次?
void Func1(int N)
{
	int count = 0;
	for (int i = 0; i < N; ++i)
	{
		for (int j = 0; j < N; ++j)
		{
			++count;
		}
	}

	for (int k = 0; k < 2 * N; ++k)
	{
		++count;
	}
	int M = 10;
	while (M--)
	{
		++count;
	}
	printf("%d\n", count);
}

The functional formula of time complexity (that is, the number of basic operations performed by Func1 ):

                                               F(N)=N^2+2N+10

But this expression is too accurate, too detailed, and too cumbersome. The time complexity is not to accurately calculate the number of executions of this mathematical function, but to assign it a level , which magnitude it is .

For example: Just like Ma Yun and Ma Huateng, you don’t need to care about the specific amount of their accounts, you just need to know that they are rich.
Accurate value F(N)=N^2+2N+10 Estimated value O(N^2)
N = 10
F(N) = 130 100
N = 100
F(N) = 10210
10000
N = 1000
F(N) = 1002010
1000000
Conclusion 1:
The larger N is, the smaller the effect of the latter item on the result is, that is to say, the item with the highest order (N^2) is the most influential item, and the highest order item is retained.
Conclusion 2:
Through the above, we will find that the progressive representation of big O removes those items that have little influence on the result , and expresses the number of executions concisely and clearly.
In practice, when we calculate the time complexity, we do not necessarily need to calculate the exact number of executions, but only the approximate number of executions, so here we use the asymptotic notation of big O.
As long as it is represented by the big O, it means that it is an estimated value.

2.2 Asymptotic representation of big O

Big O notation ( Big O notation ): is a mathematical notation used to describe the asymptotic behavior of a function.
Derivation of the Big O order method:
1. Replace all additive constants in runtime with the constant 1 .
2. In the modified running times function, only the highest order term is kept .
3. If the highest-order item exists and is not 1 , remove the constant multiplied by this item . The result is big O order.
There are best, average and worst case time complexities for some algorithms:
Worst case: maximum number of runs for any input size ( upper bound )
Average case: expected number of runs for any input size
Best case: Minimum number of runs ( lower bound ) for any input size
For example: to search for a data x in an array of length N :
Best case: 1 found
Worst case: N times found
Average case: N/2 found
In practice, the general situation is concerned with the worst operation of the algorithm , so the time complexity of searching data in the array is O(N)

2.3 Examples of common time complexity calculations 

Example 1: Execute 2N+10 times

// 计算Func2的时间复杂度?
void Func2(int N)
{
 int count = 0;
 for (int k = 0; k < 2 * N ; ++ k)
 {
 ++count;
 }
 int M = 10;
 while (M--)
 {
 ++count;
 }
 printf("%d\n", count);
}
The basic operation is performed 2N+10 times, and the time complexity is O(N) by deriving the big O-order method.

Example 2: Execute M+N times

// 计算Func3的时间复杂度?
void Func3(int N, int M)
{
 int count = 0;
 for (int k = 0; k < M; ++ k)
 {
 ++count;
 }
 for (int k = 0; k < N ; ++ k)
 {
 ++count;
 }
 printf("%d\n", count);
}
In Example 2 , the basic operation is performed M+N times, there are two unknowns M and N , and the time complexity is O(N+M)
It cannot be said that N is infinitely large, and M is not important. Unless say would give a relation:
N is much larger than M, the time complexity is O(N)
M is much larger than N, then the time complexity is O(M)
When M is equal to N or the difference between the two is not large, the time complexity is O(M+N)

Example 3: Executed 100000000 times

void Func4(int N)
{
	int count = 0;
	for (int k = 0; k < 100000000; ++k)
	{
		++count;
	}
	printf("%d\n", count + N);
}

int main()
{
	Func4(100000);
	Func4(1);

	return 0;
}

Execution: In fact, the speed of the cpu is very fast, and the difference in the number of executions can be ignored, so the time complexity is still O(1)

O(1) does not represent 1 time, but a constant time, even if k<1 billion, it is also O(1)

The largest constant we can usually write is about 4 billion (the range that can be represented by an integer), and the CPU can bear it.

In Example 3, the basic operation is performed 100000000 times, and the time complexity is O(1) by deriving the big O order method

Example 4: Calculate the time complexity of strchr

// 计算strchr的时间复杂度?
const char * strchr ( const char * str, int character );

This is about the mock implementation of strchr

#include<stdio.h>
#include<assert.h>
char* my_strchr(const char* str, const char ch)
{
	assert(str);
	const char* dest = str;
	while (dest != '\0' && *dest != ch)
	{
		dest++;
	}
	if (*dest == ch)
		return (char*)dest;
	return NULL;
}
int main()
{
	char* ret = my_strchr("hello", 'l');
	if (ret == NULL)
		printf("不存在");
	else
		printf("%s\n", ret);
	return 0;
}

My understanding is the difference between strchr and strstr: strstr is to enter a character string and search in the main string, while strchr is to enter a character and then search in the main string. This link has knowledge points about strstr: http://t.csdn.cn/NEaip

If the search fails, return NULL. If the search is successful, the address of the first character will be returned , and then it will be printed until the end of '\0'

so:

The time complexity of specifying the length of this array and then finding it is O(1). If the length is not clear, the length is N, then recursion is required N times, and the time complexity is O(N)
In Example 4 , the basic operation is performed once at best , and at worst N times. The time complexity is generally the worst, and the time complexity is O(N)

Example 5: Calculate the time complexity of BubbleSort

// 计算BubbleSort的时间复杂度?
void BubbleSort(int* a, int n)
{
 assert(a);
 for (size_t end = n; end > 0; --end)
 {
 int exchange = 0;
 for (size_t i = 1; i < end; ++i)
 {
 if (a[i-1] > a[i])
 {
 Swap(&a[i-1], &a[i]);
 exchange = 1;
 }
 }
 if (exchange == 0)
 break;
 }
}
Illustration:

 Then the number of comparisons constitutes an arithmetic sequence: use the arithmetic sequence summation formula to obtain the final number of executions is F(N)=(N-1)*N/2;

This question about loops does not mean that if there are two layers of loop nesting, it is directly judged that its time complexity is O(N^2), because if the number of comparisons is known (outer loop n<10, inner loop n< 1000000) that is O(1), and there will be an optimized version of bubble sorting. In the case of order, its time complexity is O(N), and only the outer loop is used.
Example 5 The basic operation is best executed N times, and the worst is executed (N*(N+1)/2 times. By deriving the big O -order method + time complexity, the worst is generally seen, and the time complexity is O(N^2 )

Example 6: Calculate the time complexity of BinarySearch

// 计算BinarySearch的时间复杂度?
int BinarySearch(int* a, int n, int x)
{
 assert(a);
 int begin = 0;
 int end = n-1;
 // [begin, end]:begin和end是左闭右闭区间,因此有=号
 while (begin <= end)
 {
 int mid = begin + ((end-begin)>>1);
 if (a[mid] < x)
 begin = mid+1;
 else if (a[mid] > x)
 end = mid-1;
 else
 return mid;
 }
 return -1;

}

Illustration:

Assuming that x times are found, then except for x 2

2^x =N  --> x = log2N   

So it can be multiplied by 2 from the last lookup to the length of the original array

Example 6 The basic operation is performed once at best, O(log2N) times at worst, and the time complexity is O(log2N) ps: logN means that the base is 2 and the logarithm is N in algorithm analysis. In some places it will be written as lgN.

Example 7: Time Complexity of Computing Factorial Recursive Fac

Calculate the time complexity of the following two pieces of code
//实例7:
// 计算阶乘递归Fac的时间复杂度?
long long Fac(size_t N)
{
	if (0 == N)
		return 1;

	return Fac(N - 1) * N;
}

long long Fac(size_t N)
{
	if (0 == N)
		return 1;

	for (size_t i = 0; i < N; i++)
	{

	}

	return Fac(N - 1) * N;
}

Illustration:

The number of executions of the for loop statement (if there are other loop statements) in each function call on the left, the 1 on the left means that it is a constant number of times, not 1 time (that is, if there is no loop statement in the function, there are Several if statements, the time complexity is also O(1)).

The one on the right simply means that there are N+1 function calls, and the loop statement in each function call is executed N+1 times, so the loop statement in each recursively called function should be added up.

Additional points:

The time is added up, not multiplied . For example, the return Fac(N-1)*N in the above figure: It means that the last result is multiplied by N, but the number of executions is also one time, because the * in this place is for the computer It's just a command. Time complexity is calculated as the number of executions of this instruction during the process of the program.

Example 8: Calculate the time complexity of Fibonacci recursion Fib

// 计算斐波那契递归Fib的时间复杂度?
long long Fib(size_t N)
{
	if (N < 3)
		return 1;

	return Fib(N - 1) + Fib(N - 2);

}

Illustration:

 The sum of the number of executions conforms to the geometric sequence: use the misplaced subtraction method

This question can be understood as follows:

Regarding the triangle, the white area will be much larger than the black area when N is larger, and the time complexity is used to calculate the magnitude of a certain mathematical function, and assign it a level, so It can be regarded as calculation in the state of full items, and then the sum of execution times constitutes a geometric sequence, and the time complexity of calculation is O(2^N) by using the big O asymptotic notation.

3. Space complexity of the algorithm

Space complexity is also a mathematical expression, which is a measure of the amount of storage space temporarily occupied by an algorithm during operation .
Space complexity is not how many bytes the program occupies , because this is not very meaningful, so space complexity is calculated by the number of variables .
The space complexity calculation rules are basically similar to the practical complexity, and the big O asymptotic notation is also used .
Note: The stack space ( storage parameters, local variables, some register information, etc. ) required by the function at runtime has been determined during compilation, so the space complexity is mainly determined by the extra space explicitly requested by the function at runtime .

Example 1: Calculate the space complexity of BubbleSort

// 计算BubbleSort的空间复杂度?
void BubbleSort(int* a, int n)
{
		assert(a);
	for (size_t end = n; end > 0; --end)
	{
		int exchange = 0;
		for (size_t i = 1; i < end; ++i)
		{
			if (a[i - 1] > a[i])
			{
				Swap(&a[i - 1], &a[i]);
				exchange = 1;
			}
		}
		if (exchange == 0)
			break;
	}
}

Because only one end, exchange, and i variables are created here, only the number of variables is calculated, regardless of the variable type, it is not counted as the specific number of bytes in the space, and they are all created in a loop, so the space complexity is O ( 1) .

As for the formal parameters int *a, and int n, they will not be counted in the space complexity.

Example 2: Calculate the space complexity of Fibonacci

// 计算Fibonacci的空间复杂度?
// 返回斐波那契数列的前n项
long long* Fibonacci(size_t n)
{
 if(n==0)
 return NULL;
 
 long long * fibArray = (long long *)malloc((n+1) * sizeof(long long));
 fibArray[0] = 0;
 fibArray[1] = 1;
 for (int i = 2; i <= n ; ++i)
 {
 fibArray[i] = fibArray[i - 1] + fibArray [i - 2];
 }
 return fibArray;
}

Space complexity, it calculates how much extra space you have opened up inside this function. If it is a constant number , it is O1. If the size of the opening is uncertain , it is generally O(N).

So the space complexity is O(N).

Example 3: Calculate the space complexity of factorial recursive Fac

// 计算阶乘递归Fac的空间复杂度?
long long Fac(size_t N)
{
 if(N == 0)
 return 1;
 
 return Fac(N-1)*N;
}

Illustration:

N layers are recursively called. Each call creates a stack frame. Each stack frame uses a constant space O(1).
Since N functions are called here and there is no return, the total is O(N)

Example 4: Calculate the space complexity of Fibonacci recursion Fib (two recursions)

// 计算斐波那契递归Fib的空间复杂度?
long long Fib(size_t N)
{
 if(N < 3)
 return 1;
 
 return Fib(N-1) + Fib(N-2);
}

Foreword:

The destruction of space does not mean that the space is completely destroyed, but the right to use it is returned to the operating system .

Because the memory space belongs to the operating system process, for example, let you malloc a piece of space, you will get the right to use this space, and free will return the right to use the space to the operating system

Time is gone forever Time is cumulative calculation, space can be reused without cumulative calculation
Simply put, the function on the right and the function on the left share a stack frame.

Code running: the stack grows downward, and calling Func1 and Func2 is equivalent to sharing a space, because after Func1 is destroyed, when Func2 is created, the location is still the same, and the address is also the same address.

Because a of the main function and a of Func1 are in different stack frames, they can have the same name.

Example 4 recursively calls N times, opens up N stack frames, and each stack frame uses a constant space. The space complexity is O(N)

When calling, create a stack frame;

When returning, destroy. (return to OS)

4. Common complexity comparison

The common complexity of the general algorithm is as follows:

Illustration:

 At the end of this chapter, if there are any deficiencies, please correct me.

Guess you like

Origin blog.csdn.net/weixin_65186652/article/details/131036702