01数据结构中的时间复杂度和空间复杂度

我们之前学的冒泡排序、二分查找等操作,实际上都是一种算法,而衡量算法好坏的标准则是数据结构中的时间复杂度和空间复杂度。
本章将带大家了解C语言数据结构中算法的时间复杂度和空间复杂度的概念,以及它们是如何计算的。



时间复杂度

一个算法的运行是需要时间的,而这个运行时间在不同的计算机上是不同的,因此我们无法定量去衡量这个算法的运行时间,但是可以用该算法的执行次数来判断它运行所需的时间,执行次数越多,所需的时间就越长,因此它的时间复杂度就越高。
所以算法中的基本操作的执行次数,为算法的时间复杂度

常见的时间复杂度计算

对于一个简单的for循环函数:

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

这其中操作++count;执行的次数是N。
而如果其中再嵌套一个for循环:

void Func1(int N) {
 int count = 0;
 for (int i = 0; i < N ; ++ i)
 {
   for (int j = 0; j < N ; ++ j)
 {
 ++count;
 }
 }

 printf("%d\n", count);
}

执行的次数就变成了N2
再多加一点代码:

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

执行次数就变成了N 2+2*N+10。

但执行次数并不是时间复杂度的最终表现形式,因为当N特别大时,N2是远远大于2*N+10的,N2对结果的影响更大,而实际情况中我们并不需要这么准确次数描述,因此用N2来形容程序大概执行次数更为方便。因此时间复杂度就是N2
换句话说,时间复杂度就是程序执行次数中对结果影响最大的那一项。

时间复杂度使用的是使用大O的渐进表示法,因此Func1的时间复杂度为:O(N2)。

大O的渐进表示法有以下规则:

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

也就是说如果一个算法执行次数是常数次比如5、10,那么它的时间复杂度就是O(1);
如果一个算法执行次数是5N次,则时间复杂度就是O(N);
当一个算法执行次数是一个多项式时,比如N 2+2*N+10,那它的时间复杂度只保留最高阶,所以就是O(N2)。

比如下面的算法:

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

这一段算法的执行次数是2*N+10,根据上面的规则,它的时间复杂度是O(N)。

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,由于我们并不知道N和M谁更大,因此M和N对结果的影响同样大,所以这一段算法的时间复杂度为O(M+N);如果告诉我们M远大于N或者N远大于M,则结果只保留大的那一项,因此时间复杂度就是O(M)或O(N)。

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

这个算法的执行次数是100,是常数次,根据规则时间复杂度为O(1)。

  1. 冒泡排序算法:
void bubble_sort(int arr[], int N)//传入数组和数组元素的个数N
{
	
	int i;
	for (i = 0; i < N - 1; i++)//趟数
	{
		int j = 0;
		for (j = 0; j < N - i - 1; j++)//交换的个数
		{
			if (arr[j] > arr[j+1])//交换两个元素
			{
				int tmp = arr[j];
				arr[j] = arr[j+1];
				arr[j+1] = tmp;
			}
		}
	}
}

它的执行次数实际上是一个等差数列:
在这里插入图片描述

展开以后取影响最大的项是N2/2,因此时间复杂度是O(N2)。

但是有的算法并不是执行固定次数,比如二分查找算法可能查找一次就找到目标,也有可能会查找好多次,因此这些算法的时间复杂度存在最好、平均和最坏情况。在实际中一般情况关注的是算法的最坏运行情况

  1. 在字符串中查找指定的字符:
const char * strchr ( const char * str, char character )
{
  while(*str != '\0')
 {
     if(*str == character)
      {
             return str;
      }
      
     ++str;
 }
  
  return NULL; 
}

根据最坏的情况,它的算法时间复杂度就是O(N)。

  1. 折半查找(二分查找)
int bin_search(int arr[], int left, int right, int key) 
{
 int mid = 0;
 while(left<=right)
 {
 mid = (left+right)>>1;
 if(arr[mid]>key)
 {
 right = mid-1;
 }
 else if(arr[mid] < key)
 {
 left = mid+1;
 }
 else
 return mid;//找到了,返回下标
 }
 return -1;//找不到
}

对于这个算法的最坏情况,折半查找每次查找一半, 2 l o g 2 n 2^{log_2{n}} 2log2n=n,所以只需查找 l o g 2 n {log_2{n}} log2n次,但是由于这样写并不好写,底数不好敲,所以它的时间复杂度为O(logN)。

  1. 递归的算法
// 阶乘递归
int Factorial(size_t N)
{
 return N < 2 ? N : Factorial(N-1)*N; 
}

这个算法会从N一直递归下去直到N=1,因此该算法一共调用了N次,所以它的时间复杂度是O(N)。

常用时间复杂度所耗费的时间从小到大依次是O(1)<O(logN)<O(N)<O(NlogN)<O(N2)<O(N3)<O(2N)<O(N!)<O(NN)。


空间复杂度

空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度 。空间复杂度算的不是程序占用
了多少bytes的空间,算的是变量的个数。

空间复杂度的计算和时间复杂度类似,其规则相同,也使用大O渐进表示法。

常见的空间复杂度计算

  1. 冒泡排序算法:
void bubble_sort(int arr[], int N)//传入数组和数组元素的个数N
{
	
	int i;
	for (i = 0; i < N - 1; i++)//趟数
	{
		int j = 0;
		for (j = 0; j < N - i - 1; j++)//交换的个数
		{
			if (arr[j] > arr[j+1])//交换两个元素
			{
				int tmp = arr[j];
				arr[j] = arr[j+1];
				arr[j+1] = tmp;
			}
		}
	}
}

在整个算法中,只用到了五个变量:arr,N,i,j,tmp,每次交换都会创建tmp,但是交换结束以后tmp都会被销毁,因此这个算法中变量的个数始终是5,所以它的空间复杂度是O(1)。

  1. 递归实现菲波那切数
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 ;
}

在这个算法中,malloc动态开辟了n+1个大小为sizeof(long long)的空间,相当于创建了n+1个变量,再加上n,i,一共是n+3个变量,因此它的空间复杂度就是O(N)。

  1. 递归计算阶乘
int Factorial(size_t N)
{
 return N < 2 ? N : Factorial(N-1)*N; 
}

递归调用了N次,开辟了N个栈帧,每个栈帧使用了常数个空间,相当于每个栈帧的空间复杂度是O(1)。因此总的空间复杂度为O(N)。


  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

今天也要写bug、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值