数据结构C语言版 —— 时间复杂度&空间复杂度概念和计算

Posted 爱敲代码的三毛

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构C语言版 —— 时间复杂度&空间复杂度概念和计算相关的知识,希望对你有一定的参考价值。

文章目录


时间复杂度&空间复杂度

1. 算法效率

算法效率分析一般分为两种,一种是时间效率,另外一种是空间效率。时间效率被称为时间复杂度,空间效率则被称为空间复杂度。时间复杂度是用来衡量一个算法的运行速度,而空间复杂度主要是用来衡量一个算法的所需要的额外空间,早期的计算机存储容量很小,所以比对空间复杂度很是在乎。但是随着计算机的叙述发展,计算机的存储已经到了一个很高的程度,比如现在的一台笔记本至少都是16G内存+512G磁盘,服务甚至是几百个G的内存,几百T的磁盘。所以现在并不那么关心空间复杂度,也经常出现空间换时间的做法。

2. 时间复杂度

1) 时间复杂度的概念

时间复杂度的一个定义:在计算机科学中,算法的时间复杂度是一个函数(一个数学函数),它定量描述了一个算法执行所消耗的时间,从理论上来说,是不能计算出来的,只有当你把代码放到机器上跑才能知道运行时间,但是通过机器测试这种方式明显不现实,因为每台计算机的配置都不一定相同,所以才有了时间复杂度的分析方式。一个算法所花费的时间与其语句的执行次数成正比,算法中的基本操作的执行次数,为算法的时间复杂度

2) 大O的渐近表示法

来看一段代码

void func1(int N)

    int count = 0;
    int i = 0;
    for (i = 0; i < N ; i++) //执行N次
    
        for (int j = 0; j < N ; j++) //执行N次
        
            count++;
        
    
    int k = 0;
    for (k = 0; k < 2 * N ; k++) //执行2*N次
    
        count++;
    
    int M = 10;
    while (M--) //执行10次
    
        count++;
    
    printf("%d\\n",count);

那么我们来计算一下这个代码的运行次数就是

f ( N ) = N 2 + 2 ∗ N + 10 f(N) = N^2+2*N+10 f(N)=N2+2N+10

但实际我们计算时间复杂度时,并不一定要计算的那么精确,而是只计算大概的执行次数.

我看到func1的执行次数,如果当我们的N非常大时,假设N = 1000,那么这里的+10是可以忽略了,因为 100 0 2 = 1000000 1000^2=1000000 10002=1000000,在一百万面前+10可以说是微乎其微了,所以+1和+10没什么区别。同理 2 ∗ N 2*N 2N也是一样的,当N足够大趋近于无穷时, 2 ∗ N 2*N 2N也时微乎其微了。

那么就可以使用大O的渐近表示法

O O O符号:是用于描述函数渐进行为的数学符号

推到大 O O O阶方法

  1. 用常数1取代运行时间汇总的所有加法常数
  2. 在修改后的运行次数函数中,只保留最高阶项
  3. 如果最高阶存在且不是1,则去除与这个项数相乘的常数,得到的结果就是大 O O O

通过上面的方法来推导一下

用常数1取代运行时间汇总的所有加法常数

f ( N ) = N 2 + 2 ∗ N + 1 f(N) = N^2+2*N+1 f(N)=N2+2N+1

在修改后的运行次数函数中,只保留最高阶项

f ( N ) = N 2 f(N) = N^2 f(N)=N2

这里的最高阶项不是1,所以func1函数的时间复杂度就是** O ( N 2 ) O(N^2) O(N2)**

O O O渐进表示法去掉了那些对结果影响不大的项数,只保留了最影响结果的那一项

另外有些算法存在着,最好、平均和最坏情况

  • 最坏情况:任意输入规模的最大运行次数(上界)
  • 平均情况:任意输入规模的期望运行次数
  • 最好情况:任意输入规模的最小运行次数(下界)

举个例子:

假设在一个数组中查找一个数字。

  • 最好情况:1次找到,数组第一个数子就是我们要找的, O ( 1 ) O(1) O(1)
  • 最坏情况:最后一个是我们要找的, O ( n ) O(n) O(n)
  • 平均情况:注意:这里的平均情况并不是最好和最坏情况相加的平均值,而是我们期望运行的次数,有时候平均情况可能和最好或者是最坏情况一样。

我们平常嘴上所说的时间复杂度就是最坏情况的时间复杂度

3) 时间复杂度案例举例

实例1

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);

粗略计算就是 f ( N ) = 2 ∗ N + 10 f(N) = 2*N+10 f(N)=2N+10

在修改后的运行次数函数中,只保留最高阶项,如果最高阶存在且不是1,则去除与这个项数相乘的常数

那么这个代码的时间复杂度就是** O ( N ) O(N) O(N)**

实例2

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);

这种情况有一点特殊,因为不知到N和M谁大,所以对于这种不确定谁对结果的影响大,就都需要保留下来。所以这个代码的时间复杂度就是 O ( N + M ) O(N+M) O(N+M)

如果可以确定N远远大于M那么时间复杂度就是 O ( N ) O(N) O(N)

实例3

void Func4(int N)

    int count = 0;
    for (int k = 0; k < 100; ++ k)
    
        ++count;
    
    printf("%d\\n", count);

对于这种场数次数的时间复杂度就是 O ( 1 ) O(1) O(1)

实例4

int find(int* arr, int N int key)

	assert(arr);
	int i = 0;
	for (i = 0; i < N; i++)
	
		if (arr[i] == key)
		
			return i;
		
	
	return -1;

这个代码是在一个数组中查找一个数字,对于这种情况就是直接取它的最坏情况的时间复杂度,就是 O ( N ) O(N) O(N)

实例5

void BubbleSort(int* arr, int n)

    assert(arr);
	int i = 0;
	for (i = 0; i < n-1; i++)//排序趟数
	
		int flag = 0;
		int j = 0;
		for (j = 0; j < n - 1 - i; j++)//比较次数
		
			if (arr[j] > arr[j + 1])
			
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
				flag = 1;
			
		
		if (flag == 0)
		
			break;
		
	
    

这是冒泡排序的代码,冒泡排序的时间复杂度也是比较特殊的。

冒泡排序的外层循环是排序的趟数,每一趟冒泡排序都会确定一个数字的位置。所以每一趟排序后比较的次数都要减去1。

那么计算的次数就是 ( N − 1 ) + ( N − 2 ) + ( N − 3 ) . . . + 2 + 1 (N-1) + (N-2) + (N-3)... + 2 + 1 (N1)+(N2)+(N3)...+2+1

这是要给等比数列,通过等比数列的前N项和公式得出$\\fracN*(N-1)2 $

通过大 O O O渐进法得出冒泡排序的时间负责度就是 O ( N 2 ) O(N^2) O(N2)

实例6

void BinarySearch(int* arr, int size, int key)

    assert(arr);
	int left = 0;
	int right = size - 1;
	int mid = 0;
	while (left < right)
	
		mid = (right - left) / 2 + left;
		if (arr[mid] < key)
		
			left = mid + 1;
		
		else if (arr[mid] > key)
		
			right = mid - 1;
		
		else
		
			return mid;
		

	

	return -1;
    

这是一个二分查找的代码,二分查找一次砍掉数组的一半元素。那么查找的次数就是 N / 2 / 2 / 2 / 2... / 2 = 1 N/2/2/2/2.../2 = 1 N/2/2/2/2.../2=1,找了 x x x次,则 / 2 /2 /2 x x x次,那么长度为$N 的 数 组 最 坏 则 要 查 找 次 数 就 是 , 的数组最坏则要查找次数就是, N = 2^x , 转 为 对 数 的 形 式 就 是 ,转为对数的形式就是 ,x = \\log_2N $

所以二分查找的时间复杂度就是 O ( log ⁡ 2 N ) O(\\log_2N) O(log2N)

实例7

long long Factorial(size_t N)

	return N < 2 ? N : Factorial(N-1)*N;

这是一个递归计算N的阶层的代码,这个代码的时间负责度又是多少呢?

递归算法的时间复杂度 = = = 递归次数 * 每次递归函数中的执行的次数

这里的递归次数是 N − 2 N-2 N2,每次递归函数中的执行次数就是1,那么这个代码的时间复杂度就是 O ( N ) O(N) O(N)

实例8

long long Fibonacci(size_t N)

	return N < 2 ? N : Fibonacci(N-1)+Fibonacci(N-2);

这是递归计算斐波那契数列的函数

递归计算斐波那契数列,类似于一颗二叉树。假设这一棵二叉树是满二叉树。

那么第一层计算次数就是 2 0 2^0 20,第二层就是 2 1 2^1 21,第三层就是 2 2 2^2 22,以此类推…,那么函数每一次的执行次数就是 f ( N ) = 2 N − 1 f(N) = 2^N-1 f(N)=2N1,根据等比数列前N项和公式$S_n\\fraca_1(1-q^n ) 1-q $,它的准确的时间复杂度就是 2 N − 1 − 空 缺 2^N-1-空缺 2N1

通过大 O O O渐进法推导后,这个代码的时间复杂度就是 O ( 2 N ) O(2^N) O(2<

以上是关于数据结构C语言版 —— 时间复杂度&空间复杂度概念和计算的主要内容,如果未能解决你的问题,请参考以下文章

数据结构(C语言版) 线性表 算法设计Demo24

数据结构(C语言版) 线性表 算法设计Demo25

数据结构(C语言版) 排序 算法设计Demo4

《数据结构:c语言版》(严蔚敏)知识点整合

数据结构c语言版八大算法(上)图文详解带你快速掌握——希尔排序,堆排序,插入排序,选择排序,冒泡排序!

小白学六大排序算法(C语言版)