目录
1.空间复杂度
空间复杂度不是内存所占的大小,而是变量的个数,且与变量是什么类型无关。
记住是额外创建的,外面传入的不算。空间复杂度基本上都是O1或者On
有例外比如说开个二维数组可能就是O(n^2)
若数组的最后一个下标是N 则你应该malloc n+1个 因为数组下标是从0开始的
易错1:
这个空间复杂度是O(n),递归要不断的创建函数和变量的栈帧,每个栈帧你可以认为它里面有常数个变量,创建n+1次栈帧 空间复杂度是O(n)
函数栈帧的创建就像借东西一样,向操作系统借一块空间,用完之后还给操作系统,之后操作系统还能把这块空间继续借给别人使用
调用这俩函数 发现他们的变量的地址是相同的,说明占据相同的栈帧。
而递归调用,假设从F(N)递归到F(1),从始F(N)创建 至终F(1)函数栈帧一直创建,先递,然后再一个个的归。
原因是递归的栈帧创建只要不return它就一直存在
易错2:
这个递归是如何调用的呢?先调用Fib(N-1)一直往下调用,知道遇见Fib(2)返回。然后在调用Fib(N-2)。可惜的是Fib(N-2)调用的空间全部在Fib(N-1)这些递归调用之前所开辟的栈帧里面(已经还给了操作系统)。所以它的空间复杂度是O(n)。
它的时间复杂度是O(n^2).
这个体现了:时间是一去不复返的,调用一次栈帧就要花费一次时间。而空间是可以重复利用的。
2.strstr函数
3.memcpy 和memmove
他们都是按一个个字节来替换内存的
都是把n大小的字节,从str2开始覆盖到str1中,值得注意的是memmove包括了内存重叠的情况,memcpy不包括内存重叠的情况。一般用memmove就行。 memcpy效率更高,但是可能有内存重叠问题。
他们的时间复杂度都是O(N)
4.线性表和非线性表
线性表:顺序表 链表 队列 栈 字符串
数据是挨着存储的(adjoin)
非线性表:树 图
顺序表就是数组,可以扩容的数组。注意malloc的时候第二个变量是字节的大小不是扩容元素的数量,别忘了乘以sizeof
free使用的时候不能free掉malloc出来的一部分。只能一起释放。还有free出问题了可能是野指针,也可能是malloc出来的对象越界访问,比如开辟了40个字节,却访问了第41个字节。
realloc 分为原地扩容和异地扩容
原地扩容效率很高 ,要求是后面有足够大的空间。
异地扩容有代价,就是要拷贝原来的元素,然后在存入新的大的内存中去,再返回这个新的大的内存的地址 ,然后再把原来的小的内存释放掉。
realloc缩放还是扩大都是对使用权限进行操作的
申请空间malloc之类的,释放空间free之类的都是对使用权进行操作的 申请使用权对操作系统,和还给操作系统
5.使用qsort排序顺序表
第一个是要排序的元素的地址
第二个参数是排序多少个
第三个是每个的字节数
第四个是compare函数 自己写的,注意compare函数传入的参数要带const修饰不然不对
数组最后一个位置的下一个位置的下标就是数组的个数
6.为什么有顺序表了还要有链表
顺序表是顺序存储,是一块连续的内存空间,头插 头删 以及中间的插入和删除都要动员整个数组,代价比较。顺序表可以根据下标来随机访问,而链表只能顺序访问,通过一个个的链子来访问。链表元素的插入和删除比较方便。
顺序表的缺点:
1.中间插入和删除以及头部插入和删除代价大,O(n)
2.空间不够要扩容,扩容的代价比较大。尤其是异地扩容,还有一定的空间浪费。
链表我们将单链表和带头循环双向链表
7.单链表的增删查改
结构体不能这样写,这是因为编译器查找类型,函数,变量是从上面查找的 ,即使函数写在下面也要声明。
为什么链表的Print不用assert
为什么顺序表就要断言,因为顺序表指针指向的是个结构体,结构体 里面有指针,size和capacity
单链表的尾插:首先再main函数定义头节点让它赋值为NULL然后需要改变这个指针就要传入二级指针,首先malloc一个新的节点,初始化这个节点(值和next),然后判断phead为空只需让phead指向新节点即可,如果不为空遍历找到最后一个节点,cur->next=newnode即可
要改Int 参数传入int*
要改int* 参数传入 int**
要改结构体指针 list *p 传入 list**p
要改结构体的成员,需要结构体指针。要改内容传入的参数总是高一级