一、初识数据结构
1.什么是数据结构?
2.什么是是算法?
3.数据结构和算法的重要性
4.如何学好数据结构和算法?
5.数据结构和算法的拓展阅读推荐、
1.什么是数据结构?
数据结构是计算机存储、组织数据的方式,指相互之间存在一种或多种特定关系的数据元素的集合。
内存中存储管理数据,用什么样的结构来存储管理这些数据呢?这就是数据结构要研究的东西。
//数据结构和数据库的区别在于:本质上都是存储管理数据,而数据结构是在内存(带电存储)中存储管理数据,数据库是在磁盘(硬盘,持久化)中存储管理数据
2.什么是算法?
算法就是定义良好的计算过程,他去一个或一组的值为输入,并产生出一个或一组值作为输出。简单来说算法就是一系列的计算步骤,用来将输入数据转化成输出结果。例如:查找、排序、推荐
数据结构和算法的关系是:紧密相关,你中有我,我中有你
3.数据结构和算法的重要性
IT公司求职过程:投递简历-》笔试-》三次左右面试-》offer
笔试:大厂:4-5算法题 中小厂:20-30选择题 1-2算法题
4.如何学好数据结构和算法
(1).死磕代码 掉点头发 !
(2).注意画图和思考,多调试
5.拓展阅读
学有余力再看,《数据结构》严薇敏 《剑指offerOj》
总结:学习数据结构很重要,要求C语言功底非常扎实(能独立实现通讯录)。
二、算法的时间复杂度和空间复杂度
1.算法效率
2.时间复杂度
3.空间复杂度
4.常见时间复杂度和复杂度oj练习
1.算法效率
1.1如何衡量一个算法的好坏
对于如下斐波那契数列 :
long long Fib(int N)
{
if(N<3)
{
return 1;
}
return Fib(N-1)+Fib(N-2);
}
它的递归方式很简洁,但是简洁一定好吗?该如何衡量好坏呢?
1.2算法的复杂度
衡量一个算法的好坏有两个维度:时间和空间
即是:快慢和算法运行所需要的额外空间。
2.时间复杂度
2.1时间复杂度的概念
算法的时间复杂度是个函数,通过这个函数是算的是算法运行的准确的次数
由于准确的时间复杂度函数式不方便(每次都要算)在算法之间进行比较,进而引出渐进表示发(大概估算,算出数量级即可)
2.2大O的渐进表示法
大O符号:用于描述函数渐进行为的数学符号
推导大O阶方法:
1.用常数1取代运行时间中的所有加法常数。例如:O(1)不是表示一次,而是表示常数次
2.在修改后的运行次数函数中,只保留最高阶项。例如:N*N+10N+10-->N*N
3.如果最高阶项存在且不是1,则去除与这个项目相乘的常数。例如:2*N+10-->N
另外有些算法的复杂度存在最好、平均和最差情况:
例如:strstr的时间复杂度O(N)
最坏情况:N巨大
平均情况:N/2
最好情况:1
我们总是取最坏情况(底线思维)
2.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;
}
}
冒泡排序的时间复杂度:
准确:N-1+N-2+N-3+...+2+1=((N-1+1)*(N-1))/2=(N*N-N)/2
大O渐进:N*N
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;
}
计算BinarySearch(二分查找)的时间复杂度:
准确:通过思考我们知道N一直被“对折”,最坏情况(找不到/最后才找到)时:N/2/2/2/2.../2=1
这时除了多少个二(即被“对折”了多少次)就是找了多少次 ,这时有 2^x=N--->x=log(2为底)N;
大O渐进:由上面三则可得O(log(2为底)N)。
注意:由于在时间复杂度中,log(2为底)N经常出现,所以我们会把它简写成“logN”。
3.空间复杂度
空间复杂度也是一个数学表达式,是对一个算法在运行过程中临时占用存储空间大小的量度。
void BubbleSort(int* a, int n)
{
assert(a);
for(size_t end = n;end > 0;--end)
{
int exchange = 0;
for(size_t i = 0;i < end;i++)
{
if(a[i-1] > a[i])
{
Swap(&a[i-1],&a[i]);
exchange = 1;
}
}
if (exchange == 0)
break;
}
}
冒泡排序的空间复杂度:
size_t end 、int exchange 、size_t i、Swap(&a[i-1],&a[i]) 就这三个额外的空间
所以冒泡排序的空间复杂度为O(1)
时间是累计的,空间是不累计的,可以重复利用
函数运行时所需要的栈空间(存储参数、局部变量、一些寄存器信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数在运行时候显示申请的额外空间来确定。
另外,由于摩尔定律的存在,空间复杂度的重要性越来越低。
4.常见复杂度对比
5201314 | O(1) | 常数阶 |
3n+4 | O(N) | 线性阶 |
3n^2+4n+5 | O(N^2) | 平方阶 |
3log(2)n+4 | O(logN) | 对数阶 |
2n+3nlog(2)n+14 | O(NlogN) | NlogN阶 |
n^3+2n^2+4n+6 | O(n^3) | 立方阶 |
2^n | O(2^n) | 指数阶 |
5.复杂度的OJ练习
思路1:malloc一个额外的数组,每个人元素为-1,遍历一遍原数组,吧数字填到对应位置,最后再次遍历新建的这个数字,哪个位置为-1,这个位置的下标就是缺失的数字
这个思路的时间复杂度为O(n)
思路2:创建变量x异或一下(0~n)所有的数之后再对原数组的所有元素异或一次,最后所得的结果就是缺失的数
这个思路的时间复杂度O(n)
思路3:排序之后二分查找对比不缺失数字的数组
这个思路的时间复杂度取决于排序,二分查找为logn我们是知道的,但是这道题用这个思路是不好的,排序的时间复杂度过高且不谈,空间复杂度也过高
思路4:公式计算:直接吧原数组和不缺失元素的数组的和相减,所得结果就是要找的那个数字
一眼丁真:求阶乘
那么总共会求N+1次,时间复杂度就是O(N)
斐波那契:每次调用都要调用两个新的Fib,直到N=2
那么就是2^0+2^1+2^2+...2^(N-2)-(最下面两层调用,我这里省略了)=2^(N-1)-1---->时间复杂度就是O(2^N)
从这里我们可知斐波那契的递归写法是没有价值的,太拉了~。
思路1:把最后一个元素防到创建的tmp变量里面,然后数组后移一位,再把tmp赋给第一位,完成一次右旋,重复K次。
空间复杂度为O(1) 时间复杂度为O(N*K)
思路2:以空间换时间,开个额外的数组,把后K个一个一个拷贝考到前面,再把要旋转的元素一个一个拷贝到前面,在把这个数组赋给原数组
空间复杂度为O(N)时间复杂度为O(N)
✨思路3:把前n-k个数字逆序,再把后k个数字逆序,最后整体逆序。
空间复杂度为O(1)时间复杂度为O(N)
总结:在写代码时要注意关注时间复杂度和空间复杂度,以提高效率。