目录
前言
一相关概念
1什么是数据结构
2什么是算法
二算法效率
1如何衡量算法效率的好坏
2算法的复杂度
三时间复杂度
1时间复杂度表示
2计算时间复杂度
2.1题一
2.2题二
2.3题三
2.4题四
2.5题五
2.6题六
2.7题七
2.8题八
四空间复杂度
1题一
2题二
3题三
4题四
五轮转数组
前言
数据结构与算法在后期找工作时非常重要(笔试与面试都有,都要求手撕),所以学习时没学完一个知识点要找对应的题去做,反复练习(敲代码)才会使你的认识提升一个档次,下来也要进行归类,画图总结,复习时才轻松点~
一相关概念
1什么是数据结构
数据结构(Data Structure)是计算机存储、组织数据的方式,指相互之间存在一种或多种特定关系的数据元素的集合;
简单来说数据结构:在内存中管理数据;而与数据库又有什么关系呢?
数据库: 在磁盘中管理数据
在C语言中写过的通讯录其实就是数据结构中的一种(数据结构称为顺序表)
而数据结构中不仅只有顺序表,还有链表,栈,队列,树,图...学好数据结构本质就是对常见的数据结构有一定的认识与使用,在后面工作时这些认识也是非常重要的!
2什么是算法
算法(Algorithm):就是定义良好的计算过程,取一个或一组的值为输入,并产生出一个或一组值作为输出。简单来说算法就是一系列的计算步骤,用来将输入数据转化成输出结果
二算法效率
1如何衡量算法效率的好坏
对应斐波那契数列:
long long Fib(int N)
{
if(N < 3) return 1;
return Fib(N-1) + Fib(N-2);
}
斐波那契数列的递归实现方式非常简洁,但简洁一定好吗?这样实现是否效率?
2算法的复杂度
算法在编写成可执行程序后,运行时需要耗费时间资源和空间(内存)资源(不同电脑消耗与运行速度不同);衡量一个算法的好坏,一般是从时间和空间两个维度来衡量的,即: 时间复杂度和空间复杂度
时间复杂度主要衡量算法的运行快慢; 空间复杂度主要衡量算法运行所需要的额外空间 在计算机发展的早期,计算机的存储容量很小。所以对空间复杂度很是在乎;但是经过计算机行业的迅速发展,计算机的存储容量已经达到了很高的程度。所以我们如今已经只关心空间复杂度,不再关心时间复杂度
对于面试时的考察主要是写算法题时要求指定的时间复杂度去设计:
三时间复杂度
时间复杂度:算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。
一个算法执行所耗费的时间从理论上说,是算不出来的;只有你把你的程序放在机器上跑起来,才能知道。但是我们需要每个算法都上机测试吗? 是可以都上机测试,但是这很麻烦,所以才有了时间复杂度这个分析方式。
一个算法所花费的时间与其中语句的执行次数成正比例,算法中的基本操作的执行次数,为算法
的时间复杂度。
找时间复杂度:找到某条基本语句与问题规模N之间的数学函数,把对函数影响最大的项数找出来
1时间复杂度表示
一般用大O的渐进表示法
大O符号(Big O notation):是用于描述函数渐进行为的数学符号 推导大O阶方法:
1、用常数1取代运行时间中的所有加法常数。
2、在修改后的运行次数函数中,只保留最高阶项。
3、如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶
另外有些算法的时间复杂度可能存在最好、平均和最坏情况;
一般情况关注的是算法的最坏运行情况
这个道理就好像你中午约别人吃饭,至于要什么时候去,你要想好:正常情况下12:00就下课了,约12:02好?如果下课后有点事(有道题要问老师)岂不是会迟到,约12:15好?那如果最差情况老师拖堂了30分钟呢?所以按照最坏的情况来约时间12:40去吃饭好!其实这也是最好的安排!如果预期老师真的拖堂了,按照约定时间吃饭刚刚好;如果没拖堂的话,我就还有几十分钟的时间来做下一步的打算(回宿舍洗个头换个衣服什么的),按照最坏的情况来说便是最好的打算!
2计算时间复杂度
2.1题一
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);
}
Fun1实际执行的操作数:
实际中计算时间复杂度时,不是计算精确的执行次数,而只需要大概执行次数即可;
F(N)中N^2对该函数影响最大(当N越大时,N^2呈现线性增长),所以O(N) = N^2
2.2题二
void Func2(int N)
{
int count = 0;
for (int k = 0; k < 2 * N; ++k)
{
++count;
}
int M = 10;
while (M--)
{
++count;
}
printf("%d\n", count);
}
Func2的数学函数:O(N) = 2*N + 10 ,但2可以省略,所以O(N) = N
常数项去掉可以理解,为什么也要把2给忽略掉?
你可以这么想:假设当前科技探索到了两颗适合人类居住的星球:一个距离1亿光年,一个距离2亿光年,无论是近的还是远的,当前技术都无法到达那里,所以对于我们来说:无论是近还是远都是无所谓的(方正都到不了),这里省略2也类似的道理~
2.3题三
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);
}
遇到这种情况就要判断是N大还是M大?
如果是N大,O(N) = N ;如果是M大,O(N) = M
2.4题四
void Func4(int N)
{
int count = 0;
for (int k = 0; k < 100; ++k)
{
++count;
}
printf("%d\n", count);
}
N不影响循环的次数,所以:O(N) = 1(常数次)
2.5题五
const char *strchr(const char *str, int character);
该函数是库里面的函数,功能:在str字符串中找到character字符;
所以共有三种情况:
好的情况(第一次就找到了):O(N) = 1 ;
中等情况(在中间位置找到了):O(N) = 2/N
坏的情况(最后一个才找到/找到最后一个后也不是它就是找不到了):O(N) = N;
取最坏的情况作为时间复杂度O(N) = N
2.6题六
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;
}
}
如果只是看到两个for循环就判断O(N) = N^2,70%到80%的情况是正确的:分析一般要根据代码思路(本质)来分析得出O(N)(快排实现也是两个循环,但O(N) = N)
以上是实现冒泡函数的代码实现;
第一趟是 n-1 ,第二趟是 n-2,第三趟是 n-3...最后一趟 1;
这刚好就是一个等差数列,进行求和(总的代码执行次数):
n*(a1+an) / 2 = (n-1)*(n-1+1) /2 = (n-1)*n / 1
所以时间复杂度O(N) = N^2
2.7题七
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;
}
时间复杂度O(N) = log N(log默认下面的数是2)
2.8题八
long long Fib(size_t N)
{
if (N < 3)
return 1;
return Fib(N - 1) + Fib(N - 2);
}
这里可能有人算的时候是将每层进行相乘,这是不对的!!
因为我们计算的是代码执行的次数;
所以时间复杂度O(N) = 2^N
四空间复杂度
空间复杂度是一个数学表达式,是对一个算法在运行过程中临时占用存储空间大小的量度
空间复杂度不是程序占用了多少bytes的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数。空间复杂度计算规则基本跟实践复杂度类似,也使用大O渐进表示法。
空间复杂度主要通过函数在运行时候显式申请的额外空间来确定
为了区分时间复杂度与空间复杂度,空间复杂度我们用小写n来表示
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;
}
}
该冒泡排序只是在原来数组进行修改,没有申请额外空间,所以O(n) = 1
2题二
long long *Fibonacci(size_t n)
{
if (n == 0)
return NULL;
long long *fibArray = (long long *)malloc((n + 1) * sizeof(long long));
fibArray[0] = 0;
fibArray[1] = 1;
for (int i = 2; i <= n; ++i)
{
fibArray[i] = fibArray[i - 1] + fibArray[i - 2];
}
return fibArray;
}
以上函数实现第n个数时返回一个大小为n斐波那契数组(储存的是计算好的值)
申请了临时空间,所以O(n) = n
3题三
long long Fac(size_t N)
{
if (N == 0)
return 1;
return Fac(N - 1) * N;
}
递归的方式计算阶乘,递归层数是N-1层;递归时要计算空间复杂度是用累加的方式进行计算(而时间一去不复返,不能累加),所以O(n) = n
4题四
long long Fib(size_t N)
{
if (N < 3)
return 1;
return Fib(N - 1) + Fib(N - 2);
}
计算时间复杂度时利用每层递归的层数形成的是一个等比数列的关系后进行累加得到O(N) = 2^N;那么计算空间复杂度也是这样,答案也是O(n) = 2^n?
如果这么想的话,那那就错了!递归的顺序按照下面红线的方式递归
走到Fib(2)返回到Fib(3)时往下再递归时并不是重新开辟空间,而是重复利用Fib(2)递归时的空间,为什么??
因为此时Fib(2)的空间已经还给OS了,而OS对待即将销毁的空间,不是直接进行我们想象中的销毁,而是让后面来的数据进行覆盖!
所以递归层数 N-2 才是该函数的空间复杂度O(n) = n
五轮转数组
class Solution
{
public:
void rotate(vector<int> &nums, int k)
{
// O(N) = N O(n) = 1
// int n=nums.size();
// k%=n;//一定要%
// reverse(nums.begin(),nums.begin()+n-k);//前n-k个数翻转
// reverse(nums.begin()+n-k,nums.end());//后k个数翻转
// reverse(nums.begin(),nums.end());
// o(N) = N O(n) = N 空间换时间
int n = nums.size();
k %= n;
vector<int> ret;
for (int i = n - k; i < n; i++)
ret.push_back(nums[i]);
for (int i = 0; i < n - k; i++)
ret.push_back(nums[i]);
nums = ret;
}
};
以上便是全部内容,有问题欢迎在评论区指正,感谢观看!