它什么时候是申请结点,什么时候又是定义一个指针?
怎么区分它是在申请结点(申请结点一般都有malloc),还是在定义指针
可以把它类比为 int *,int *就是定义一个整型指针,换成Node *,就是定义一个Node类型(自己定义的类型)(长成Node的样子)的指针
Node的样子:
CNode* p = (CNode*)malloc(sizeof(CNode));
malloc的原本用法就是malloc前面有一个void*的指针,因为malloc申请出来的结点需要一个指针去指向它,才能让这个新结点被找到,但这个指针没有返回值,没有名字,所以在后面sizeof里面写完你要定义的结点类型后,前面对应的就要强转类型了。 最后等号左边给同类型的指针起一个名字来接收这个结点。
而这句话的意思是,有(定义)一个指针p,指向这个(新申请的)Node结点
也可以说,定义一个指针p,接收一下结点的返回值
这里的Node *是其强转类型的作用,malloc的原本类型是void *,无返回值
而这个就是直接定义一个头结点,没有指针*
所以只要有Node*(除了强转类型)就是定义指针,只是看它有没有接收结点,接收了结点的话后面写它就是代指其接收的结点,也就是p表示一个结点,p和p的next的实际值就是那一长串数字地址,将数字地址赋值也就是改变指针指向,而地址必须要存起来,否则链条就会断掉
这2句话的意思是将刚定义的头结点初始化置成空结点
然后是在链表里面的遍历循环,改变结构要依靠前驱就初始化为plist,单纯查找输出就初始化为plist的next。
查找函数——在链表plist中 查找第一个key值,找到返回key值的结点地址,没有找到返回空NULL
测试
删除函数——删除第一个val的值(考点)
删除函数一定会依赖前驱(穿透),所以第一步是先查找前驱
查找前驱函数——返回key的前驱地址,如果不存在(key无前驱,在表头)返回NULL
例如,如图找数字3的前驱即p所指位置
穿透如图所示
测试
删除函数——删除pos位置的值(跟链表一样)
测试
返回key的后继下标函数——如果不存在(key无后继,在表尾)返回NULL
清空链表中的数据函数
销毁整个链表内存函数(交回)
如图,销毁是将结点挨个一个一个的删除了销毁,所以这个算法也叫总是删除第一个结点。
删除一个结点的函数就是要找这个结点的前驱(每一次找前驱都要从头到尾遍历一遍),所以倒着从末尾结点开始删除就会复杂化,时间复杂度太大,但也能实现。
总是删除第一个结点,就是——先定义一个p,指向plist->next,(这里的p相当于删除函数中起暂保存的q的作用),然后p逐渐向后移动,逐个结点穿透删除。
即plist后面只要有结点(就是第一个结点)(也就是plist->next!=NULL)就删除它
(也可以总是删除第二个结点,最后再删除第一个结点,时间复杂度同样,这就是代码的多样性)
测试
代码为0即正常没有崩溃,否则会显示一个负数
销毁完成后还剩下一个头结点plist是不用销毁的
如图,因为在测试前面定义的头结点head就不是malloc出来的,且初始化里面也没有malloc
所以这里的head头结点就是一个临时变量,不用free
如果初始化里面malloc了(此时外面就不用定义临时变量了,但需要定义一个指针接收一下),那么销毁函数里面的头结点也要free一下,否则会内存泄漏。所以没有必要malloc,临时变量就好
循环链表与单链表的区别就是表尾的next为plist还是NULL,循环链表中不能出现NULL,出现就死循环了,因为循环链表本身就是一个环,里面没有找不到NULL.
而头插,尾插,按val值删除三个的时间复杂度跟前面单链表还一样,头插是O(1),头删也是O(1),尾插(for找尾巴)是O(n),尾删也是O(n)