您的位置:首页 > 其它

有关时间复杂度,空间复杂度通俗的理解

2018-01-26 22:58 288 查看

有关时间复杂度,空间复杂度通俗的理解

今天,在复习C语言知识时,碰到了一个非常有趣的题目,题目如下:

一个数组中除了两个数字之外,其余数字均出现了两次,如{1, 2, 3, 4, 5, 3, 2, 1}。查找出这两个只出现一次的数字,要求如下:时间复杂度为O(n),控件复杂度位O(n)。

当笔者看到这道题目的时候,就被“时间复杂度为O(n),控件复杂度位O(n)”所困惑,究竟题目中的这两个要求是什么意思?

首先,笔者百度了一下,百度上显示如下:

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

what‘s that?一点都看不懂;

简单说O(n²)表示当n很大的时候,复杂度约等于Cn²,C是某个常数,简单说就是当n足够大的时候,n的线性增长,复杂度将沿平方增长。(如果还不懂继续往下看哦)

要想真正搞懂时间复杂度,空间复杂度其实不难,在笔者学习C中有关排序内容的时候就曾遇到过,只是当时没怎么注意,于是笔者又打开了尘封千年的笔记本,以下便是八大排序所对应的复杂度:



相信接触过排序的人都知道,各个排序是怎么实现的,举个最简单的例子——冒泡排序,在冒泡排序的代码中有这么一段:

// 这里是对长度为10的数组进行排序
for(i = 0; i < len-1; i ++)
{
for(j = 0; j < len - i - 1; j ++)
{
if(a[j] > a[j + 1])
{
tmp = a[j];
a[j] = a[j + 1];
a[j + 1] = tmp;
}
}
}


很明显,冒泡排序是通过连层嵌套的for语句实现的,对应上面表格中的时间复杂度(平均时间)是O(n^2);所以,根据上面的定义,我们把时间(len/数组长度)扩大至无限大,第一个for循环是一个简单的线性增长所以是O(n),同理,第二个for循环也是一个简单的线性增长,也是O(n),由于两个是嵌套的所以总的时间复杂度是O(n^2);

是不是,有了那么一点懂了,下面一些时间复杂度的算法:

时间复杂度是一个函数,它定量描述了该算法的运行时间。常见的时间复杂度有以下几种。
1,log(2)n,n,n log(2)n ,n的平方,n的三次方,2的n次方,n!

1指的是常数。即,无论算法的输入n是多大,都不会影响到算法的运行时间。这种是最优的算法。
而n!(阶乘)是非常差的算法。当n变大时,算法所需的时间是不可接受的。

用通俗的话来描述,我们假设n=1所需的时间为1秒。当时间复杂度是1,那么当n = 10时,需要10秒;
当时间复杂度是n的平方时,n = 10,需要100s;

当 n = 10000时
O(1)的算法需要1秒执行完毕。
O(n)的算法需要10,000秒 ≈ 2.7小时 执行完毕。
O(n2)的算法需要100,000,000秒 ≈ 3.17年 执行完毕。
O(n!)的算法需要XXXXXXXX(系统的计算器已经算不出来了)。

对于程序A,for(int i=0;i<1000;i++),当输入任意的n时循环次数均为1000,复杂度为O(1);
对于程序B,for(int i=0;i<n;i++),当输入任意的n时循环次数为n,复杂度为O(n)。


那么,相类似的,对于控件复杂度,我们可以理解为是空间复杂度,是对一个算法在运行过程中临时占用存储空间大小的量度。举个例子:

直接插入排序的时间复杂度是O(n^2),空间复杂度是O(1) 。
而一般的递归算法就要有O(n)的空间复杂度了,因为每次递归都要存储返回信息


以上是从百度百科截下来的原话,像堆排序,冒泡排序等空间复杂度都为1,而归并,基数则是n;简单来说一下原因,归并和基数排序中需要malloc动态分配内存空间,而冒泡,堆排序都是通过和自身元素换位置来排序的,最多交换变量时借助一个tmp变量;那么假如这四种排序同时对n(假设n非常大)个数据排序,归并和基数排序所maolloc的空间是大于n的,而堆和冒泡排序则是1或者是2;和n相比几乎可以忽略不计。

所以我们说堆,冒泡排序空间复杂度是O(1),而归并,基数则是O(n)。

类似,空间复杂度算法和时间复杂度也是一样的,主要看程序运行时开辟多少空间辅助程序运行;

嗯,到了这里的话,我们大致有了解题思路了,条件限制是不能有多层嵌套,和递归函数的出现,而辅助内存则是越少越好,不能有大量数据复制。

#include <stdio.h>
int main() {
int a[] = {1, 2, 0, 2, 1, 9, 3, 9, 0, -1};
int number1 = 0, number2 = 0;
int i = 1, tmp = a[0], index = 0;
for (; i < sizeof(a)/sizeof(*a); ++i) {  //得出 3 ^ -1
tmp ^= a[i];
}
number1 = tmp;
number2 = tmp;
// number1 = 0;
// number2 = 0;
// number1 = tmp;  number2 = tmp; 时,number累积异或的结果为出现在另一组的那个数
// number1 = 0;  number2 = 0; 时,number累积异或的结果为出现在本组的那个数

while (tmp) {                    //找出tmp从右数第一个为1的位置
if (1== (tmp & 1)) {
break;
}
++index;
tmp >>= 1;
}
for (i = 0; i < sizeof(a)/sizeof(a[0]); ++i) {  //以index位为1将数组分成两部分,每部分只含有一个只出现一次的数字
if (1 == ((a[i] >> index) & 1)) {           //得到一个
number1 ^= a[i];
}
else {                                      //得到另外一个
number2 ^= a[i];
}
}
printf("%d  %dn", number1, number2);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息