什么是数据结构?
数据结构是计算机储存、组织数据的方式,指相互之间存在一种或多种特定关系的数据元素集合。
什么是算法?
算法效率
一般用时间复杂度和空间复杂度来衡量一个算法的好坏。其中,时间复杂度主要衡量一个算法的运行的快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间。因为在计算机的早期,计算机的储存容量很小,所以十分在乎空间复杂度。但是随着计算机行业的发展,计算机的储存容量已经达到了很高的水平,因此如今我们不再需要特别关注一个算法的空间复杂程度而更侧重于算法的时间复杂度。
时间复杂度
算法中的基本操作执行次数就为算法的时间复杂度。
例如:Func1中++count的执行次数为 N*N+3*N+10
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;
}
printf("%d\n", count);
}
实际上当当我们评判时间复杂度时并不一定需要精确的执行次数,而只需要大概的执行次数。有点类似于函数里取极限的思想,我们需要的是N趋向于正无穷时仍占主体的。所以我们使用的是大O的渐进表示法。
大O符号:是用于描述函数渐进行为的数学符号。
推导大O阶方法:
1.用常数1取代运行时间中的所有加法常数。
2.在修改后的运行次数函数中·,只保留最高阶项。
3.如果最高阶项存在且不是1,则除去与这个项目相乘的常数。得到的结果就是大O阶。
使用大O阶渐进表示法后,Func1的时间复杂度为O(N^2)
此外还要注意,有些算法的时间复杂度存在最好、平均和最坏情况:
最好情况:任意输入规模的最小运行次数(下界)
平均情况:任意输入规模的期望运行次数
最好情况:任意输入规模的最大运行次数(上界)
在实际情况中一般关注的是算法的最坏运行情况
示例1:计算BubbleSort的时间复杂度
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;
}
}
执行次数为等差数列求和, N+N-1+......+2+1 次,即[(N+1)*N]/2 次。时间复杂度为O(N^2)
示例2:计算BinarySearch的时间复杂度
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);
if (a[mid] < x)
begin = mid+1;
else if (a[mid] > x)
end = mid-1;
else
return mid;
}
return -1;
}
二分查找法的时间复杂度,最好情况为1次,最坏情况为O(logN)。计算方法为如下:
示例3:计算阶乘递归Fac的时间复杂度
long long Fac(size_t N)
{
if(0 == N)
return 1;
return Fac(N-1)*N;
}
时间复杂度为O(N)
示例4:计算斐波那契递归Fib的时间复杂度
long long Fib(size_t N)
{
if(N < 3)
return 1;
return Fib(N-1) + Fib(N-2);
}
时间复杂度为O(2^N),计算方法如下:
空间复杂度
空间复杂度是一个算法在运行过程中临时占用储存空间大小的量度。算的是变量的个数,也采用大O渐进表示法。
注意:函数运行是所需要的栈空间(储存参数、局部变量、一些寄存器信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数在运行时候显式申请的额外空间来确定。
示例1:计算BubbleSort的空间复杂度
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;
}
}
使用了常数个额外空间,所以空间复杂度为O(1)。
示例2:计算阶乘递归Fac的空间复杂度
long long Fac(size_t N)
{
if(N == 0)
return 1;
return Fac(N-1)*N;
}
由图知先调用到底再返回,由Fib(N)到 Fib(N-1) 到 Fib(N-1) 下的Fib(N-2)........所以空间复杂度为O(N).
注意:时间是累积的,而空间是可以重复利用的