在本文中,我们开始介绍数据结构的内容,那么就开始吧。
首先,为了了解数据结构,我们要知道什么是数据
什么是数据?
数据,是描述客观事物的符号,是计算机可以操作的对象,能够被计算机识别和处理的符号集合。
数据不仅仅指数值类型,还有声音、图像、视频等非数值类型。
在知道数据后,我们开始介绍数据结构
什么是数据结构?
数据结构,是计算机内部存储和处理数据的方式,指相互之间存在一种或多种关系的数据的集合。
什么是算法?
算法,简单说就是计算步骤。它将输入转化为输出。
要注意的是,数据结构和算法可不是分开的,而是紧密联系一起的
尤其是在校园招聘中,算法编程题越来越常见,而此时如果你没有掌握好数据结构,那么在做类似编程题的时候会非常吃力,所以学好它们非常重要。
算法效率
在谈算法效率前,我们先看一个代码
long long Fib(int N)
{
if(N<3)
return 1;
return Fib(N-1) + Fib(N-2);
}
上述代码其实是描述的斐波那契数列,它很有规律,代码看起来也很简洁,看到这里,我们就认为这个代码的算法效率很高。但是真实情况是这样吗?
答案当然是不对的
算法效率的好坏,我们都是从时间和空间两个维度来描述的。
时间:算法执行的时间越快越好。
空间:算法运行占用的内存空间越小越好。
根据这两个标准我们再来看斐波那契数列,当n很大的时候,这个代码运行所占的内存空间是非常大的,时间可能也不是很快,这时我们就不能认为这个代码很简洁。
复杂度
从上文知道了,算法有时间和空间两个维度,所以顺其自然的将复杂度分为时间复杂度和时间复杂度。
时间复杂度
时间复杂度,是程序执行的快慢程度。但是我们计算多个程序时间复杂度的时候,难道都要我们一个一个去运行程序吗?
而且在执行代码的时候,会受到多种方面的因素,比如计算机的处理器好坏。你想啊一个最新的计算机和一个五年前甚至十年前的计算机,跑程序用的时间肯定是不一样的呀
后来呀,我们想到算法执行的时间和算法的执行次数成正比,因此我们把代码执行次数定为了时间复杂度。这样我们评价多个程序时间复杂度时就不用一个一个跑了。
现在有个代码,我们来计算一下的时间复杂度
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;
}
Fun1它的复杂度为N2+2N+10
当N=10, 复杂度为130.
当N=100, 复杂度为10210.
当N=1000, 复杂度为1002010.
我们看到当N增加时,复杂度增加的越来越快,这其中其主要作用的时N2,此时,2N和10对复杂度的影像越来越小,可以忽略不计了。所以我们可以不需要计算精确的复杂度。
来到这里,学过高等数学的小伙伴就有种熟悉的感觉了,是不是类似求无穷大时候的极限。
求时间复杂度时,有一个专门的方法
大O阶方法
大O符号(Big O notation):是用于描述函数渐进行为的数学符号。
推导大O阶方法:
1、用常数1取代运行时间中的所有加法常数。
2、在修改后的运行次数函数中,只保留最高阶项。
3、如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶。
使用大O的渐进表示法以后,Func1的时间复杂度为:O(N2)
N = 10 F(N) = 100
N = 100 F(N) = 10000
N = 1000 F(N) = 1000000
通过上面我们会发现大O的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数。
另外有些算法的时间复杂度存在最好、平均和最坏情况:
最坏情况:任意输入规模的最大运行次数(上界)
平均情况:任意输入规模的期望运行次数
最好情况:任意输入规模的最小运行次数(下界)
下面我们在看一些例子
例1
void Func3(int N, int M)
{
int count = 0;
for (int k = 0; k < M; ++ k)
{
++count;
}
for (int k = 0; k < N ; ++ k)
{
++count;
}
printf("%d\n", count);
}
Fun2的复杂度为M+N,此时有两个未知数,当M远大于N时,复杂度为O(M);反之为O(N);当M与N相等时,复杂度O(M)和O(N)都可以。
例2
void Func4(int N)
{
int count = 0;
for (int k = 0; k < 100; ++ k)
{
++count;
}
printf("%d\n", count);
}
Fun4的复杂度为O(1),因为代码中并没有未知数,且循环执行100次,当复杂度为常数时都将其变成1.
例3
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;
}
}
BubbleSort的复杂度为O(N2),怎么算的呢?
程序执行的次数为从1加到N-1,想当与初项为1,公差d=1的等差数列求和。再根据大O阶算法求出复杂度。
空间复杂度
早期空间复杂度是代码运行临时占用的内存空间。那时候的计算机内存还很小,所以它的每个空间在当时看来都很珍贵。但是现在随着技术不断更新,如今的计算机内存空间已经很大了,完全能满足我们的需求。所以现在把内存被占用的大小作为评判标准没有太大的意义了,因此我们将空间复杂度定为变量的个数。
具体内容我们下一节在讲。
本次我们讲了:
什么是数据?
什么是数据结构?
什么是算法?
算法效率
复杂度
时间复杂度
大O阶方法
空间复杂度
—————————————————————————————————
结语
数据结构很重要,希望我们一起努力把它掌握。
好了,今天就这样了,我们下次见。别忘了评论互关呦