算法的时间复杂度和空间复杂度

Posted 正义的伙伴啊

tags:

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

算法的时间复杂度和空间复杂度

算法的效率

如何衡量一个算法的好坏呢?
一个算法如果写的十分的短,是不是就非常的好呢?
例如斐波那契数列:

long long Fib(int N)
{
	if (N < 3)
		return 1;
	return Fib(N - 1) + Fib(N - 2);
}

实际上斐波那契数列的函数是最铸币的算法,随着N不断增大(大概到30),计算机就有可能计算不出来了。那么我们要用什么方式去衡量一个算法的好坏,所以我们引进了时间复杂度空间复杂度。 时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所占用空间

时间复杂度

时间复杂度的概念

衡量一个算法的运行时间快慢也就是时间复杂度,那么计量的单位是什么?如果用我们常用的时间单位例如:秒、毫秒。这就存在一个问题,不同机器由于配置不同时间会不相同,而且要想知道时间就必须上机跑代码很麻烦。所以我们把算法花费的时间与其中语句执行次数成正比,算法中的基本操作执行个数,为算法的时间复杂度。

void fun(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", count);
}

count在这个函数中我们很容易知道执行了F(N)=N^2+2*N+10
其实我们在计算精确的执行次数,而只需要大概执行次数,这里我们使用大O的渐进表示法

大O的渐进表示法

大O符号(Big O notation):用于描述函数渐进行为的数学符号。
推导大O阶方法:

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

所以函数FUN的复杂度为:O(N^2)

常见的时间复杂度举例

例一:

void fun(int N, int M)
{
	int count = 0;
	for (int k = 0; k < M; ++k)
	{
		++count;
	}
	for (int k = 0; k < N; ++k)
	{
		++count;
	}
}

很明显这里的时间复杂度是O(M+N)
其实如果M远远大于N则时间复杂度为O(M)
如果N远远大于M则时间复杂度为O(N)

例二:

void fun(int N, int M)
{
	int count = 0;
	for (int k = 0; k < 1000; ++k)
	{
		++count;
	}
}

时间复杂度为O(1000)但是如果为常数的话,时间复杂度可以直接写为O(1)
例三:冒泡排序


void Bubblesort(int *a, int n)
{
	for (int i = 0; i < n-1 ; i++)
	{
		for (int j = 0; j < n - 1 - i; j++)
		{
			if (a[j] < a[j + 1])
			{
				int temp = a[j];
				a[j] = a[j + 1];
				a[j + 1] = temp;
			}
		}
	}
}

由这个程序我们可以知道F(N)=N-1+N-2+.....+1=(N^2-N)/2

例四:二分查找

int BinarySearch(int* a, int n, int x)
{
	assert(a);
	int left = 0;
	int right = n - 1;
	while (left != right)
	{
		int mid = (left + right) / 2;
		if (x > a[mid])
		{
			left = mid + 1;
		}
		else if (x < a[mid])
		{
			right = mid - 1;
		}
		else
		{
			return mid+1;
		}
		return -1;
	}
}

这个算法对于每次查找,找不到就会将范围缩小二倍,数组缩小到0,所以时间复杂度自然也就是O(lgN)
注:这里lgN实际上是指log以2为底N的对数

实例Ⅴ:递归阶乘

long long fun(size_t N)
{
	if (0 == N)
		return 1;
	return fun(N - 1) * N;
}

这个也非常简单return的 值为fun(N-1)*fun(N-2)........fun(0)一共是N+1个,所以中间的代码也就执行了N+1次,所以时间复杂度是O(N)

实例Ⅶ:斐波那契数列


long long Fib(size_t N)
{
	if (N < 3)
	{
		return 1;
	}
	return Fib(N - 1) + Fib(N - 2);
}

这里时间复杂度像一颗树一样展开,每一个节点会有两个分支,所以是一个等比数列,从1一直到2^(n-2)次方的和2^(n-1)-1,但实际上要比这个小一点但是量级都是2^N,故时间复杂度为O(2^N)

空间复杂度

空间复杂度也是一个数学表达式,是对一个算法在运行过程中临时占用储存空间大小的度量

空间复杂度不是程序占用了多少bytes空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数。也用O()来表示。

注意:函数运行所需要的栈空间在编译的时候就已经确认好了,因此空间复杂度主要通过函数在运行时显示申请的空间来确定。

实例一:冒泡排序

void Bubblesort(int *a, int n)
{
	for (int i = 0; i < n-1 ; i++)
	{
		for (int j = 0; j < n - 1 - i; j++)
		{
			if (a[j] < a[j + 1])
			{
				int temp = a[j];
				a[j] = a[j + 1];
				a[j + 1] = temp;
			}
		}
	}
}

实际上这里开辟的空间只有i,j,temp三个所以空间复杂度为O(1)
但是我在初学的时候一直十分不解,i,j分别被定义了n次,那么空间复杂度不应该是O(N)吗?
实际上这里i,j在出了作用域之后就会被销毁,空间是可以重复利用的,所以实际上就开辟了常数个空间。
例二:

void fun(int k)
{
   int *p=(int *)malloc(n*sizeof(int));
}

这个函数就是纯纯的开辟了n个空间
例三:函数递归

long long F(size_t N)
{
	if (N == 0)
		return 1;
	return Fac(N - 1) * N;
}

首先答案是O(N)
这里如果对函数栈帧不是很了解的话,这个原理也就不会知道。函数栈帧可以简单的理解为:函数每被调用一次,都会在栈区上开辟一块空间,所以F(N)会调用F(N-1),F(N-1)会调用F(N-2)…顾同时会调用N个F,每个F都会开辟一块空间,所以空间复杂度为:O(N)

示例四:斐波那契数列


long long Fib(size_t N)
{
	if (N < 3)
	{
		return 1;
	}
	return Fib(N - 1) + Fib(N - 2);
}

通过上面的实例我们就可以知道斐波那契数列实际上开辟了N个Fib函数空间,只是这些函数空间会被重复利用,总共被运行2^n-1次,所以空间复杂度为O(N)

以上是关于算法的时间复杂度和空间复杂度的主要内容,如果未能解决你的问题,请参考以下文章

前端 算法的时间复杂度和空间复杂度

数据结构算法——算法复杂度分析

时间复杂度和空间复杂度

一文讲透算法中的时间复杂度和空间复杂度计算方式

时间复杂度和空间复杂度分析

时间复杂度和空间复杂度