算法
算法(Algorithm)是指用来操作数据、解决程序问题的一组方法。对于同一个问题,使用不同的算法,也许最终得到的结果是一样的,但在过程中消耗的资源和时间却会有很大的区别。
那么我们应该如何去衡量不同算法之间的优劣呢?
主要还是从算法所占用的「时间」和「空间」两个维度去考量。
- 时间维度:是指执行当前算法所消耗的时间,我们通常用「时间复杂度」来描述。
- 空间维度:是指执行当前算法需要占用多少内存空间,我们通常用「空间复杂度」来描述。
因此,评价一个算法的效率主要是看它的时间复杂度和空间复杂度情况。然而,有的时候时间和空间却又是「鱼和熊掌」,不可兼得的,那么我们就需要从中去取一个平衡点。
下面我来分别介绍一下「时间复杂度」和「空间复杂度」的计算方式。
时间复杂度
首先要说的是,时间复杂度的计算并不是计算程序具体运行的时间,而是算法执行语句的次数。
当我们面前有多个算法时,我们可以通过计算时间复杂度,判断出哪一个算法在具体执行时花费时间最多和最少。
常见的时间复杂度有:
- 常数阶O(1),
- 对数阶O(log2 n),
- 线性阶O(n),
- 线性对数阶O(n log2 n),
- 平方阶O(n^2),
- 立方阶O(n^3)
- k次方阶O(n^K),
- 指数阶O(2^n)。
随着n的不断增大,时间复杂度不断增大,算法花费时间越多。
计算方法
①选取相对增长最高的项
②最高项系数是都化为1
③若是常数的话用O(1)表示
如f(n)=2*n^3+2n+100
则O(n)=n^3。
通常我们计算时间复杂度都是计算最坏情况
时间复杂度例题
1. 常数例题的时间复杂度
int x = 90;
int y = 100;
while (y > 0)
{
if (x > 100)
{
x = x - 10;
y--;
}
else
{
x++;
}
}
这段代码里只有常量,所以时间复杂度只有O(1)
2. for循环的时间复杂度
for(i=1; i<=n; ++i)
{
j = i;
j++;
}
这段代码,for循环里面的代码会执行n遍,因此它消耗的时间是随着n的变化而变化的,因此这类代码都可以用O(n)来表示它的时间复杂度。
- 嵌套循环的时间复杂度
for (i = 0; i < n; i++)
{
for (j = 0; j < i; j++)
{
for (k = 0; k < j; k++)
{
x++;
}
}
}
当有若干个循环时,时间复杂度是和嵌套层数最多的循环语句中最内层的频度决定的,时间复杂度:O(n^3)
- 求二分法的时间复杂度
int select(int a[], int k, int len)
{
int left = 0;
int right = len - 1;
while (left <= right)
{
int mid = left + ((right - left) >> 2);
if (a[mid] == k)
{
return 1;
}
else if (a[mid] > k)
{
right = mid - 1;
}
else
{
left = mid + 1;
}
}
return NULL;
}
在最坏的情况下循环x次后找到,n/(2^x)=1;x=log2n;
所以二分查找的时间复杂度为:O(log2n)
空间复杂度
空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度。
计算方法:
①忽略常数,用O(1)表示
②递归算法的空间复杂度=递归深度N*每次递归所要的辅助空间
③对于单线程来说,递归有运行时堆栈,求的是递归最深的那一次压栈所耗费的空间的个数,因为递归最深的那一次所耗费的空间足以容纳它所有递归过程。
空间复杂度例题
1. 常数
int fun(int n,)
{
int k=10;
if(n==k)
return n;
else
return fun(++n);
}
递归实现,调用fun函数,每次都创建1个变量k。调用n次,空间复杂度O(n*1)=O(n)
2. 实现二分查找算法的递归及非递归
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<assert.h>
int BinarySearch(int arr[], int len, int num)
{
assert(arr);
int left = 0;
int right = len - 1;
int mid;
while (left <= right)
{
mid = left + (right - left) / 2;
if (num > arr[mid])
{
left = mid + 1;
}
else if (num < arr[mid])
{
right = mid - 1;
}
else
{
return mid;
}
}
return -1;
}
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9 };
int length = sizeof(arr) / sizeof(arr[0]);
int aim = 9;
int result;
result = BinarySearch(arr, length, aim);
if (result == -1)
{
printf("Can't find %d\n", aim);
}
else
{
printf("%d at %d postion\n", aim,result + 1);
}
return 0;
}
二分查找时,每次都在原有查找内容进行二分,所以时间复杂度为O(log2 n)
因为变量值创建一次,所以空间复杂度为O(1)
3. 递归算法
int BinarySearchRecursion(int arr[5], int lef, int rig,int aim)
{
int mid = lef + (rig - lef) / 2;
if (lef <= rig)
{
if (aim < arr[mid])
{
rig = mid - 1;
BinarySearchRecursion(arr, lef, rig, aim);
}
else if (arr[mid] < aim)
{
lef = mid + 1;
BinarySearchRecursion(arr, lef, rig, aim);
}
else if (aim == arr[mid])
{
return mid;
}
}
else
return -1;
}
int main()
{
int arr[] = { 1,2,3,5,6, };
int sz = sizeof(arr)/sizeof(arr[0]);
int result;
result = BinarySearchRecursion(arr, 0, sz - 1, 4);
if (-1 == result)
{
printf("Can't find it.\n");
}
else
printf("Aim at %d location\n", result+1);
}
时间复杂度为O(log2 n)
每进行一次递归都会创建变量,所以空间复杂度为O(log2 n)