小白视角下解读TencentOS tiny 预备篇(算法时间、空间复杂程度)

小白视角解读TencentOS tiny的前期工作

既然作为小白来解读这个操作系统,C语言简单的基础是要有的,然后就是先了解一些必备的知识,这样解读起来就比较容易一点。

什么是算法的复杂程度?

	首先要知道,算法的复杂程度包含两个,1 空间复杂程度  2 时间复杂程度
	1 时间复杂度:说白了也就是算法的执行时间,对于同一个问题的多种不同解决算法,执行时间越短的算法效率越高,越长的效率越低;
	2 空间复杂度: 一个算法的空间复杂度是指该算法所耗费的存储空间

时间复杂度分析:
1.一个算法执行所耗费的时间,从理论上是不能算出来的,必须上机运行测试才能知道。但我们不可能也没有必要对每个算法都上机测试,只需知道哪个算法花费的时间多,哪个算法花费的时间少就可以了。并且一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。一个算法中的语句执行次数称为语句频度或时间频度。记为T(n)。
这里面的n可以简单的理解成有多少行代码要执行,一般情况下,算法中基本操作重复执行的次数是问题规模n的某个函数,用T(n)表示,若有某个辅助函数f(n),使得当n趋近于无穷大时,T(n)/f(n)的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数,记作T(n)=O(f(n)),f(n) 可以是1、n、logn、n²等,称为算法的渐进时间复杂度,简称时间复杂度。
2.原理性的东西就不说那么多了,作为程序员,与其跟他说原理性的,还不如直接上代码来得实在。
常数函数

u8 swap_num(u8 a, u8 b)
{
	u8 sum;     	
	sum = (a * b) + b;	
	return sum;
}

上面的的算法时间复杂程度是O(1); 最通俗的讲就是 O(1)中就只有简单的常数赋值,即使你把
sum = (a * b) + b; 执行10遍 100遍,1000遍,sum的值还是 (a * b) + b,你大爷还是你大爷。

线性函数

u8 Get_sum(u8 n)
{
	u8 i,sum;
	for(i = 0; i < n; i++)
	{
			sum += i; //O(1)
	}
	return sum;
}

时间复杂程度为O(n),很简单,上面的代码是理解起来就是要执行多少次这个常数赋值的语句,他的执行次数跟你的输入n成正比,对应数学函数里面的 Y = NX;(N是常数)是一条线性的函数。

对数函数

void Log_fun(u8 n)
{
		u8 i = 1;
		while(i < n)
		{
			i = i * 2;   //O(1)
		}			
}

时间复杂程度为O(logn), 啥?logn不懂是什么?
如果 ,N = x^a即a的x次方等于N(a>0,且a≠1),那么数x叫做以a为底N的对数(logarithm),记作
x = loga^N ,对应这里的话就是 i乘以多少个 2 以后才能大于等于 n,我们假设个数是 x,也就是求 2^x = n,即 x = log2n,所以这个循环的时间复杂度就是 O(logn)。
平方函数

u8 Get_sum(u8 n)
{
	u8 i,j,sum;
	for(i = 0; i < n; i++)  //O(n)
	{		
		for(j = 0; j < n; j++)  //O(n)
		{
			sum += i;     //O(1)
		}
	}
	return sum;
}

时间复杂度为O(n^2 ), 显而易见,如果不看i那层循环,那这个时间复杂度就是O(n),这时加上i这个循环后,也就是 在n次里边循环n次 ,那不就是 n^2 咯。一般的双层嵌套for循环都是O(n^2);

如何计算算法的复杂程度?

像上边用O(x){ x = 1, n, logn,n^2 },称为大O表示法,你就记得O(x)就是代表时间复杂程度就行了。
算法的复杂度可以从最理想的情况、平均情况和最坏情况来评估,但是我们写算法的时候,或者写其他代码的时候,基本都只考虑最好的情况和最坏的情况,所以一般评估算法都是直接用最坏情况来评估。

那如何推导出x的值呢?以下有几点推导的方法:
1. 直接用1来取代运行的代码块中所有的加法常数;
(因为常数对复杂程度的影响是不大的,就如上面的常数函数,他本来是O(3),但是,我下面说了,把那句sum = (a * b) + b;再执行 10遍 100遍 1000遍,你大爷还是你大爷,他还是和O(3)是一样的,所以直接用O(1)来表示)
2.修改后的运行次数,只保留最高阶项
3.如果最高阶存在且不为1,去掉与这个项相乘的常数

u8 Get_sum(u8 n)
{
	u8 i,j,sum;
	for(i = 0; i < n; i++)  //O(n)
	{		
		for(j = i; j < n; i++)  //O(n)
		{
			sum += i;     //O(1)
		}
	}
	return sum;
}

当i = 0 时,内循环执行n次,i = 1时,内循环执行n-1次,i = n - 1时 内循环执行1次 故:
n + (n-1) + (n-2) + (n-3) +…+1
=(n+1)+[(n-1)+2]+[(n-2)+3]+[(n-3)+4]+……
=(n+1)+(n+1)+(n+1)+(n+1)+……
=(n+1)n/2
=n(n+1)/2
=n^2/2+n/2

根据法则 2, 3 ,保留最高项, 那就是 n^2/2 , 去掉常数项 就是 n ^2 了。

)
还有其他的常见的复杂度:
f(n)=nlogn时,时间复杂度为O(nlogn),可以称为nlogn阶。
f(n)=n³时,时间复杂度为O(n³),可以称为立方阶。
f(n)=2ⁿ时,时间复杂度为O(2ⁿ),可以称为指数阶。
f(n)=n!时,时间复杂度为O(n!),可以称为阶乘阶。
f(n)=(√n时,时间复杂度为O(√n),可以称为平方根阶。

说到底,这个时间复杂度到底用来干嘛的

别急,看个表个就明白了。各个时间复杂程度数值大小对比
可能还不够直观,那再看一个
相应的线性图
上表 X 代表 n, Y代表复杂程度。
通过上面两张表,可以看出O(1) O(n) O(logn) O(nlogn) 随着n的增大,复杂程度变化并不明显,反之O(n^2) O(2^n ) O(n!) ,的变化都是非常巨大的,这就说明 O(1) O(n) O(logn) O(nlogn) 对比O(n^2) O(2^n ) O(n!) 的复杂程度更低,在代码里边运行的效率会更高,举一个简单的栗子,对应第一张表格,如果我的一句语句
sum += i;执行完毕耗时1us,那么通过上面的复杂程度算一下,你就明白了,为什么算法要评估一下最坏的复杂程度了。所以写代码的时候要,特别是写循环相关的代码的时候,要考虑一下时间的复杂度,取复杂度低的算法会让你的程序执行效率会更高。

空间复杂度

空间复杂度就比较好理解了,空间复杂度就是你的算法需要占用内存的大小,一个程序的空间复杂度是指运行完一个程序所需内存的大小。利用程序的空间复杂度,可以对程序的运行所需要的内存多少有个预先估计。一个程序执行时除了需要存储空间和存储本身所使用的指令、常数、变量和输入数据外,还需要一些对数据进行操作的工作单元和存储一些为现实计算所需信息的辅助空间。程序执行时所需存储空间包括以下两部分:
(1) 固定部分,这部分空间的大小与输入/输出的数据的个数多少、数值无关。主要包括指令空间(即代码空间)、数据空间(常量、简单变量)等所占的空间。这部分属于静态空间。

(2) 可变空间,这部分空间的主要包括动态分配的空间,以及递归栈所需的空间等。这部分的空间大小与算法有关。

一个算法所需的存储空间用f(n)表示。S(n)=O(f(n)),其中n为问题的规模,S(n)表示空间复杂度。
对于一个算法,其时间复杂度和空间复杂度往往是相互影响的。

一个栗子:

要判断某某年是不是闰年:
可以编写一个算法来计算,这也就意味着,每次给一个年份,都是要通过计算得到是否是闰年的结果。

还有另一个办法就是,事先建立一个有 6666个元素的数组(年数比现实多就行),然后把所有的年份按下标的数字对应,如果是闰年,此数组项的值就是1,如果不是值为0。这样,所谓的判断某一年是否是闰年,就变成了查找这个数组的某一项的值是多少的问题。此时,我们的运算是最小化了,但是硬盘上或者内存中需要存储这 6666 个 0 和 1 。

这就是典型的使用空间换时间的概念。

总而言之,时间复杂度和空间复杂度是相互影响的,一味的追求好的时间复杂度,反而会让空间复杂度变差,反之亦然。

所以写代码的时候要综合评估两个复杂度。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值