【数据结构】之算法的时间与空间复杂度

博主声明:

转载请在开头附加本文链接及作者信息,并标记为转载。本文由博主 威威喵 原创,请多支持与指教。

本文首发于此   博主威威喵  |  博客主页https://blog.csdn.net/smile_running

    我们计算机专业的同学,在大学里几乎都有学过《数据结构与算法分析》这门课。我在刚刚接触这门课的时候,基本是搞不懂它是做什么的,可以这么说,我几乎没有把它运用到日常学习和编程中去。以至于给我的一种错觉是,这门课学了好像没什么大用,在实际编程中,我根本用不上。

    这是我个人的一个致命错误,在转向 Java 阵营的时候,可以说是调调 API 就能够轻松实现一个数组、链表或集合的排序操作,调 API 能轻松实现数组反转、拷贝等操作。忽然有一天,离开了这些 API,自己啥也写不出来,突然感到自己太肤浅了,只会调 API 啥也不会。

    虽然我们开发软件都是调 API,但是连基础的排序算法、链表、栈与队列及二叉树与图都不懂的话,这是说不过去的。《数据结构与算法分析》这本书说到,程序 = 数据结构 + 算法。所以,我决定要把之前没学好的课程补回来,让自己别太肤浅了。

    接下来呢,我们来看一个简简单单的例子。学习过这门课,或看过相关书籍的都会见过这样的一个案例,就是数学家高斯。他小学时,有一次老师觉得课堂很乱,就出了一个 1 + 2 + 3 + ... + 100 的题目,让同学们做,说做不完的不许回家吃饭。由于计算量比较大,大家都算了好一会儿,也没见人算出来。可是这个高斯就大声的喊道 5050,老师也比较惊讶!期间,高斯说了自己的一个计算方法,是这样的:

    高斯是伟大是数学家,小学时就如此聪明。他能发现这个数列的规律,这其实就是一个等差数列。在我们初中的时候,就已经接触过了,相比较从 1 + 2 + 3 ... 一直加到 100 的计算方式,期间出现一次失误,都导致满盘皆输。所以呢,这个数列即是等差数列,就可以对应的公式来计算,等差数列公式如下:

    接下来,我们来看看代码是如何书写的吧。同一个例子,求 1 + 2 + ... + 100 的结果。第一种,最普通的做法是循环相加,这个没什么难度,谁都会写。代码如下:

#include <stdio.h>

/** 博主:威威猫,https://blog.csdn.net/smile_running **/
void main()
{
	int i, sum = 0;

	for(i=1; i<=100; i++)
		sum += i;

	printf("sum = %d \n", sum);
}

    另一种,当然是运用等差数列的公式写法,代码如下:

#include <stdio.h>

/** 博主:威威猫,https://blog.csdn.net/smile_running **/
void main()
{
	int i, sum = 0;

	sum = (1 + 100) * 100 / 2;

	printf("sum = %d \n", sum);
}

   (注意第二种等差数列的做法,其不能先除于 2 再乘于 100,这样会得到结果是 5000,原因是 sum 是 int 类型, 101 / 2 得到是小数)

一、算法的时间复杂度

    对比这两种的算法,虽然它们得出的结果一致,但是其算法的时间复杂度却不一样。首先来看第一种,循环内执行了 100 次,对比第二种,其只做了 1 次,这个差距还是比较大的。所以呢,我们写程序是一定要考虑其性能的,也就是算法的时间复杂度,来看一下算法的时间复杂度的定义:

    算法的时间复杂度是一个函数,它定性描述了该算法的运行时间。这是一个关于代表算法输入值的字符串的长度的函数。时间复杂度常用大O符号表述,不包括这个函数的低阶项首项系数

    上面一个重点地方就是:不包括这个函数的低阶项首项系数。这句话的意思很简单,我们来看如下的一张图,这时是我们在编程中常见的算法的时间复杂度。

    看完这张图的时间复杂度表示法,相信你已经明白不包括这个函数的低阶项首项系数的意思了。对于上面的算法,我们一般只考虑到 平方阶 就差不多了,立方阶及后面的需要的时间太久了,在很多情况下并不合适。

    接下来,我们通过代码的方式,去分析一下在实际编程中几种常见的算法的时间复杂度。首先,我们看常数阶,这个很简单,就如上面的等差数列求和的例子,其时间复杂度为 O(1),代码如下:

1、常数阶

#include <stdio.h>

/** 博主:威威猫,https://blog.csdn.net/smile_running **/
void main()
{
	int i, sum = 0;

	sum = (1 + 100) * 100 / 2;

	printf("sum = %d \n", sum);
}

2、对数阶

    对数阶,这个也比较常见,可以联想到等比数列,先举一个例子,如下面的代码:

#include <stdio.h>

/** 博主:威威猫,https://blog.csdn.net/smile_running **/
void main()
{
	int sum=1, n=100;

	while(sum < n)
		sum *= 2;

	printf("sum = %d \n", sum);
}

    这个循环的结束是 sum >= n,那么 sum 的值就相当于 2^x >= n,可以得出我们的 x = log2n,所以它的时间复杂度是 O(logn),推到也很简单。

3、线性阶

    线性阶恐怕是最最常见的一种了,还是上面的例子,这样的代码就是线性阶,其时间复杂度为 O(n)

#include <stdio.h>

/** 博主:威威猫,https://blog.csdn.net/smile_running **/
void main()
{
	int i, sum = 0;

	for(i=1; i<=100; i++)
		sum += i;

	printf("sum = %d \n", sum);
}

4、n * logn 阶

    n*logn 阶是一个 n 阶与一个 log 阶混合的结果,比如下面的代码,其时间复杂度为 O(nlogn)

#include <stdio.h>

/** 博主:威威猫,https://blog.csdn.net/smile_running **/
void main()
{
	int i,j;

	for(i=1; i<=n; i++)
		for(j=1; j<=n; j+=i)
       
		//执行复杂度为O(1)的算法
     
	printf("sum = %d \n", sum);
}

    其它几个都是比较好推导的,这一个比较特别,我们来看看具体是如何求出它的时间复杂度的。上面的示例代码,第一个循环永远为 n 次,相同点不看。不同的是 j += i,我们着重关注这里。

  • 当 i = 1 时,相当于 j++,结束是 n / 1
  • 当 i = 2 时,相当于 j = j+2,结束是 n / 2
  • 当 i = 3 时,相当于 j = j+3,结束是 n / 3
  • ......
  • 当 i = n-1 时,相当于 j = j+n-1,结束是 n / n-1
  • 当 i = n 时,相当于 j = j+n,结束是 n / n

    这里的区别就是当 i++ 时,第二个循环的执行次数就是 ( 1 + 1/2 + 1/3 + ... + 1/n-1 + 1/n) 次,而这个数列相加,得不到一个具体的值,它被称作欧拉公式:

    其得到的最终结果是:ln(n) + C,所以第一个循环是 n,与之相乘所得的时间复杂度就是: O(nlogn)

5、平方阶

    平方阶,意味着两个 n 阶的混合,例如冒泡排序、选择排序这样的嵌套循环。其时间复杂度为 O(n*n),也就是 O(n2),代码如下:

#include <stdio.h>

/** 博主:威威猫,https://blog.csdn.net/smile_running **/
void main()
{
	int i,j;

	for(i=1; i<=n; i++)
		for(j=1; j<=n; j++)
       
		//执行复杂度为O(1)的算法
     
	printf("sum = %d \n", sum);
}

    立方阶就是不举例了,因为其 n 的值如果大一点的话,其算法耗时将非常多。接下来,来看看算法的空间复杂度。

二、算法的空间复杂度

    空间复杂度(Space Complexity)是对一个算法在运行过程中临时占用存储空间大小的量度,记做S(n)=O(f(n))。比如直接插入排序时间复杂度是O(n^2),空间复杂度是O(1) 。而一般的递归算法就要有O(n)的空间复杂度了,因为每次递归都要存储返回信息。一个算法的优劣主要从算法的执行时间和所需要占用的存储空间两个方面衡量

    算法的空间复杂度,稍作了解即可,有一个概念就行了。算法的时间复杂度与空间复杂度,就是该算法所执行需要的时间与空间了,我们的算法可以相对的在空间需求上做一些牺牲,来争取更高效的运行效率,重点还是要关注算法的时间复杂度。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值