文章目录
- 引入
- 算法复杂度
- 一.时间复杂度
- 定义
- 大O渐进表示法
- 经典例题
- 常量
- 字符串
- 二分查找
- 冒泡排序
- 递归
- 1.阶乘
- 2.斐波切纳数列
- 二.空间复杂度
- 定义
- 经典例题
- 冒泡排序
- 递归
- 1.阶乘
- 2.斐波切纳数列
引入
为什么要有算法复杂度?
当我们正在解决一个问题的时候,想出了多种思路,我们需要找出符合题目的解法(有的题会要求算法复杂度),那学习算法复杂度正是需要我们解题的关键。
算法复杂度
算法在编写成可执行程序后,运行时需要耗费时间资源和空间(内存)资源 。
因此衡量一个算法的好坏,一般是从时间和空间两个维度来衡量的,即时间复杂度和空间复杂度。
说明:
1.时间复杂度主要衡量一个算法的运行快慢。
2.空间复杂度主要衡量一个算法运行所需要的额外空间。
补充:
1.在计算机发展的早期,计算机的存储容量很小。所以对空间复杂度很是在乎。但是经过计算机行业的迅速发展,计算机的存储容量已经达到了很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复杂度。
2.摩尔定律
一.时间复杂度
定义
在计算机科学中,时间复杂性,又称时间复杂度,算法的时间复杂度是一个函数,它定性描述该算法的运行时间。这是一个代表算法输入值的字符串的长度的函数。
void Func1(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;
}
我们看一下循环的执行次数——与N和M有关
列出精确的函数的表示式 : F(N)=N*N+2N+10
这是我们平常见到的时间复杂度吗?
答案:NO
大O渐进表示法
承接上文
1.我们需要无比精确的表示函数里面循环的执行次数吗?不需要
2.这样做在实际中是不是比较麻烦呢?是
3.那有什么办更为简便吗?有
这就引出了此法。
我觉得再学过高等数学的极限思想会更容易理解,那就是看影响比较大的部分,当变量很大的时候,看影响更大的一部分,即为当变量趋于无穷时,函数表达式等价的式子。
再看上面的代码:
列出精确的函数的表示式:F(N)=NN+2N+10
当N趋于无穷(很大)的时候对函数表达式影响最大的是NN的式子。
那么上面代码的时间复杂度为:O(N^2)
注意:
这里变量通常为N的表达式,但是有的时候也会出现变量X,M,Y 等的表达式,甚至是M*N之类的。
常见的算法复杂度:
经典例题
常量
void Func4(int N)
{
int count = 0;
for (int k = 0; k < 100; ++ k)
{
++count;
}
printf("%d\n", count);
}
这里循环了100次。
对于常量:用O(1)表示
字符串
const char * strchr ( const char * str, int character )
{
while(*str)
{
if(*str==character)
{
return str;
}
else
{
str++;
}
}
return NULL;
}
一般会从三个角度进行思考
1.最好的情况
例如:假设要在字符串“abcdefg”查找 ‘a’只查找了1次
2.平均的情况
例如:假设要在字符串“abcdefg”查找 ‘d’查找了4次
3.最坏的情况
例如:假设要在字符串“abcdefg”查找 'h’查找了7次
那我们假设字符串的长度为N
最好的情况:1
平均的情况:(1+N)/2
最好的情况:N
我们要衡量的尺度一般是从最坏的情况进行考虑:N
那么这里的时间复杂度为:O(N)
二分查找
int BinarySearch(int* a, int n, int x)
{
assert(a);
int begin = 0;
int end = n-1;
// [begin, end]:begin和end是左闭右闭区间,因此有=号
while (begin <= end)
{
int mid = begin + ((end-begin)>>1);//这与(begin+end)/2的效果差不多
if (a[mid] < x)
begin = mid+1;
else if (a[mid] > x)
end = mid-1;
else
return mid;
}
return -1;
}
图解:
冒泡排序
void BubbleSort(int* a, int n)
{
assert(a);
for (size_t end = n; end > 0; --end)
{
int exchange = 0;
for (size_t i = 1; i < end; ++i)
{
if (a[i-1] > a[i])
{
Swap(&a[i-1], &a[i]);
exchange = 1;
}
}
if (exchange == 0)
break;
}
}
图解:
递归
1.阶乘
long long Fac(size_t N)
{
if(0 == N)
return 1;
return Fac(N-1)*N;
}
图解:
2.斐波切纳数列
long long Fib(size_t N)
{
if(N < 3)
return 1;
return Fib(N-1) + Fib(N-2);
}
图解:
注意:
这里函数的运行的顺序,为接下来的空间复杂度做铺垫(这里看不懂没关系)
图解:
二.空间复杂度
定义
空间复杂度(Space Complexity)是对一个算法在运行过程中临时占用存储空间大小的量度,记做S(n)=O(f(n))。
补充:这里也采用大O渐进法
注意:
函数运行时所需要的栈空间(存储参数、局部变量、一些寄存器信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数在运行时候显式申请的额外空间来确定。
经典例题
冒泡排序
void BubbleSort(int* a, int n)
{
assert(a);
for (size_t end = n; end > 0; --end)
{
int exchange = 0;
for (size_t i = 1; i < end; ++i)
{
if (a[i-1] > a[i])
{
Swap(&a[i-1], &a[i]);
exchange = 1;
}
}
if (exchange == 0)
break;
}
}
图解:
递归
1.阶乘
long long Fac(size_t N)
{
if(0 == N)
return 1;
return Fac(N-1)*N;
}
图解:
2.斐波切纳数列
long long Fib(size_t N)
{
if(N < 3)
return 1;
return Fib(N-1) + Fib(N-2);
}
图解:
注意:
这里函数的运行的顺序