数据结构和算法
数据结构就是内存中管理数据:增删查改
而数据库实在磁盘中管理数据:增删查改
算法最简单的例子就是排序
数据结构和算法是相辅相成的。
复杂度分为时间复杂度和空间复杂度。
1:如何衡量一个算法的好坏。
可以从时间维度和空间维度进行分析:
现在的算法更加关注时间效率。
算法的时间复杂度是一个函数,他定量描述了该算法的运行时间:通过计算算法中的基本操作的执行次数。
我们举一个例子,假如我们需要100w个算法的基本操作
冒泡排序的时间复杂度为O(N^2)
快排的时间复杂度为O(logN)
我们使用哪个排序更好呢?
假如我们使用冒泡排序的话
100w*100w的结果为1万亿,我们需要计算1万亿次
而假如我们使用快排的话
log100w的结果大概是20,我们只需要计算20次。
所以我们选择快排更好。
我们再举一个例子
count语句一共计算了多少次,N^2+2N+10
当N=10时,对应的结果为130
当N=100时,对应的结果为10210
当N=1000时,对应的结果为1002010
我们计算时间复杂度时,我们只需要取一个大概执行次数。我们把表达式影响不大的项都去掉,则对应的结果为
=N^2
总结:我们在计算时间复杂度时,我们只需要计算大概执行次数,这里我们使用大O的渐进表示法。
所以该算法的时间复杂度为O(N^2)
实例
这串代码的时间复杂度是多少。
初学者会说结果是N*2+10
但其实N和2N的量级是相同的。而常数10对结果的影响很小,所以忽略不计。
实例
这个时间复杂度是多少?
这个时间复杂度就是O(M+N)
但是当有前提M>>的时候或者N>>M的时候,时间复杂度是O(M)或者O(N).
冒泡排序的时间复杂度如何求呢?
假设end=N,我们计算他的基础运算的次数:第一次for循环执行N-1次,第二次执行N-2次,第三次执行N-3次,最后一次for循环执行1次,这是等差数列,等差数列的前N项的和的结果是
((N-1+1)*(N-1))/2,计算的结果为(N^2+N)/2
我们可以忽略二分之一和N,所以时间复杂度结果为O(N^2).
1:运行时间中的常数全部被取代为1.
2:只保留最高阶项
3:最高阶的系数为1.
实例
这个函数的时间复杂度是多少呢?
答:时间复杂度为O(1),因为只要是运行时间中的常数,都被当成是1. 这里的O(1)并不代表是1次,而是代表是常数次。
实例
我们知道,strstr是在一个字符串中找另外一个字符串。
那么strchr就是在一个字符串中找一个字符
那么算法的基本操作的执行次数也非情况
最好情况,1次就找到
平均情况,N^2次找到
最坏情况,N次找到。
在实际中一般关注的是算法的最坏情况,所以该函数的时间复杂度是O(N).
实例
接下来,我们对冒泡排序进行优化操作:
时间复杂度分情况
最好情况下,O(N)
最坏情况下,O(N^2)
假如不进行优化的话,无论是最好情况还是最坏情况,计算的结果都是N^2
注意:时间复杂度不能数循环。要看算法逻辑,运行计算时间复杂度。
实例
这个二分查找的时间复杂度是多少?
二分查找是这样的:我们是通过不断的折半,判断来求取我们的目标值的
最坏情况是不断地折半,直到范围中只有一个元素,或者找不到这个元素
假设最坏情况查找了x次。
N是初始的范围,N/2 ^x=1
所以x=logN
所以二分查找的时间复杂度O(logN).
首先,第一个函数是一个递归,递归的次数是N次,每一次递归中也有其他的计算,例如,判断,*,这些次数就相当与常数次,所以时间复杂度为O(N).
假如我们这样写呢?这样写的时间复杂度是多少呢?
答:时间复杂度为O(N^2),原因是我们有for循环,第一次循环,我们递归N次,第二次循环,我们递归N-1次,等等,形成了等差数列,等差数列的前N项和形成的时间复杂度为O(N^2)
那么斐波那契数列的复杂度呢?
我们首先要绘制图像
第一层有2^0个元素,第二层有2^1个元素,最后一层有2^(n-1)个元素
形成了等比数列,等比数列的前N项和就表示执行次数,计算结果为2^n-1,对应的时间复杂度为
O(2^N)
斐波那契数列在实际的算法中是没有用的一种算法。
因为斐波那契数的计算范围太小,当N=40,就需要计算大概1万亿次。
我们对斐波那契数列进行改动,使他的时间复杂度下降。
long long Fib(size_t N)
{
int i = 0;
long long f1 = 1, f2 = 1, f3;
for (size_t i = 3; i <= N; i++)
{
f3 = f1 + f2;
f1 = f2;
f2 = f3;
}
return f3;
}
int main()
{
long long ret = Fib(1000);
printf("%lld", ret);
return 0;
}
long long类型的原因:
答:Fib当输入的参数很小时,就能够计算出很大的值
size_t i = 3从3开始的原因
答:我们的f1和f2都是1,我们这里表示从3开始计算
这时候,我们输入参数1000,进行编译
一样能够打印出对应的结果。
实例
第一种思路,假如有N项,我们让最后一项移位拿出,把其他项往后移一位,再把第一项移位到首项,再通过for循环,循环k次,实现向右轮转k步
所以第一种思路的随时间复杂度使O(K*N)
最坏的情况下,时间复杂度为O(N^2)
当K%N==N-1的时候,时间复杂度就是(N^2).
第二种思路
我们创建一个数组元素为N的数组tmp,我们要向右轮转k步,所以我们直接取出后面k位,放在tmp的前K位,再取出前面的N-K位,放在tmp的后N-K位
这种思路需要额外开辟空间,但这种思路的时间复杂度仅为O(N).
第三种思路,我们可以前使前N-K个元素逆置,再取后k个元素逆置,再整体逆置,我们可以检验
前两项逆置
后两项逆置
再整体逆置
得到我们的结果,我们进行模拟实现
void reverse(int *p, int begin, int end)
{
int tmp = 0;
while (begin < end)
{
tmp = end;
end = begin;
begin = tmp;
begin++;
end--;
}
}
void rotate(int*p, int numsSize, int k)
{
if (k >= numsSize)
{
k %= numsSize;
}
reverse(p, 0, numsSize - k-1);
reverse(p, numsSize - k, numsSize - 1);
reverse(p, 0, numsSize - 1);
}