时间复杂度与空间复杂度

时间复杂度与空间复杂度

时间复杂度与空间复杂度是我们在学习计算机时很重要的概念,可是很多同学对于时间复杂度与空间复杂度的重要性和概念不是很清晰,这篇博文将剖析时间复杂度与空间复杂度的应用。
首先我们为什么要在敲代码的时候注重时间复杂度与空间复杂度呢,举个例子,我们要处理上亿数量的文件,同样可以达到效果的两种算法一种我们只需要遍历一遍所有文件,有一种我们需要遍历文件数量的平方次,同样的一个功能人家的代码跑了几分钟你跑了一小时当然会有很大的差距,这是时间复杂度的重要性,空间复杂度的重要性在于当我们操纵海量数据时可能人家的算法下跑就可以跑过并且不占什么空间,但是到了你的代码可能空间都开不够。所以我们要对于代码做出优化,尽量找出时间复杂度与空间复杂度小的算法。
同时算法由于每一次获得的数据不同存在最好的情况与最坏的情况:
最好情况:任意输入规模的最小运行次数(一般不可能…)
平均情况:任意输入规模的期望运行次数
最坏情况:任意输入规模的最大运行次数
我们一般关注最坏情况,因为可能存在越界等情况的存在。

时间复杂度

首先我们来了解时间的复杂度,时间复杂度简单来说就是跑这段代码的时间。有的代码需要将某些逻辑运行一遍,有的就需要很多遍。
一个算法语句总的执行次数是关于问题规模N的某个函数,记为f(N),N 称为问题的规模。语句总的执行次数记为T(N),当N不断变化时,T(N)也在变化,算法执行次数的增长速率和f(N)的增长速率相同。则有T(N) = O(f(N)),称O(f(n))为时间复杂度的O渐进表示法。
上面这句话是什么意思呢,比如我们的函数运行次数为2*(n+1)的时候我们可以将他的时间复杂度记为o(n)。因为时间复杂度关心的是数量级而不是具体的数值,用数量级来表示可以统一规范而且便于比较。

时间复杂度的计算方法

  1. 找出算法中的基本语句,算法中执行次数最多的那条语句就是基本语句,通常是最内层循环的循环体。
  2. 计算基本语句的执行次数的数量级需计算基本语句执行次数的数量级,这就意味着只要保证基本语句执行次数的函数中的最高次幂正确即可,可以忽略所有低次幂和最高次幂的系数。这样能够简化算法分析,并且使注意力集中在最重要的一点上:增长率。
  3. 用大Ο记号表示算法的时间性能将基本语句执行次数的数量级放入大Ο记号中。
    ###常见的时间复杂度
    o(1):最大次数只遍历常数遍,例如我们计算加法等程序。
    o(n):例如在数组查找某个数据,遍历数组差不多需要走数据的大小次。
    o(lgn):这里是指以2为底N的对数,例如在二叉树中查找数据,二叉树如果有百万数据在搜索二叉树中找一个数据只用走20次即可。
    o(nlgn):例如堆排序,单趟排序找出最大值需要lgn,排完需要遍历数组所以是nlgn。
    o(nn):例如选择排序,单趟排序找出一个最大值,一共找出数组数量个最大值。
    ###递归的时间复杂度
    由于递归是不断建立栈帧压栈完成,所以递归的时间复杂度是**递归的总次数
    每次递归的次数。**

空间复杂度

空间复杂度简单来说就是这段代码占内存的大小,它与时间复杂度都是用o表示法,当这个函数需要开辟空间建立变量的大小是常数级的时候空间复杂度就是o(1),以此类推。
需要注意的是空间是可以用完销毁的,然而时间是不断累积的,我们在优化代码的时候需要注意优化空间复杂度控制它最大需要定义的变量就可以,可是我们在优化时间复杂度的时候有的时候缩短了时间的一半可是时间复杂度并没有变。
这里二分查找说明递归的复杂度提升

{
	while (left != right)
	{
		int mid = left + (left + right) / 2;
		if (a[mid] == key)
		{
			return 0;
		}
		if (a[mid] > key)
		{
			right = mid - 1;continue;
		}
		if (a[mid] < key)
		{
			left = mid + 1;continue;
		}
	}
	if (a[left] == key)
	{
		return 0;
	}
	return -1;
}

int BinarySearch(int* a, int left, int right,int key)
{
	int i;
	if (left == right)
	{
		if (a[left] == key)
		{
			return 0;
		}
		return -1;
	}
	int mid = left + (right - left) / 2;
	if (a[mid] == key)
	{
		return 0;
	}
	if (a[mid] < key)
	{
		i=BinarySearch(a, mid + 1, right, key);
	}
	if (a[mid] > key)
	{
		i=BinarySearch(a, left, mid - 1,key);
	}
	return i;
}

上文第一段为非递归的二分查找,我们时间复杂度是o(lgn)。第二段是递归的二分查找,时间复杂度是o(n*lgn)

尾递归

这里是扯到递归顺便一提关于递归的小知识
众所周知递归是把函数中的数据例如变量等压入系统的栈中,可是有一个问题,随着我们递归次数的加深,栈本身就内存就不多,很有可能发生栈不够用的情况,这时可能有同学就会说那我们可以在堆上开辟一个模拟栈,可是他还是很占空间而且很麻烦,我们这里提出尾递归的概念,当我们在返回的时候返回函数本身的话递归的效果不变但是这时递归的话由于编译器的优化这一层函数在栈帧中的内容就已经销毁,所以我们栈帧中只存在下一层在return中的内容。
所以综上所述,我们在return的时候再递归,由于编译器的优化,我们可以避免栈帧存不下的问题。尾递归在海量数据中很有用处。

你可能感兴趣的:(数据结构)