节点的概念:
链表以节点为元素构成,一个节点一部分储存自己的数据,一部分存储下一个节
点本身的地址。
为什么 头节点head 是 Node* 类型?
ex,next要存放下一个节点的地址,所以是Node*类型,头节点也是要放下一个节点的位置,所以说是Node*类型
头节点和next的原理是一样的
如图看出:链表全由指针驱动,有了一个头指针就可以操作整个链表
都是上级指针保存下级指针的 地址和值
所以,与其他数据结构不同,链表的定义是定义指针的结构体
头节点(分清带头和不带头两种):
头节点一直都是储存的是第一个节点本身的地址
所以我们要用替罪羊的思想保证头节点指向第一个数
头节点是不考虑本身地址的(要取地址才拿得到)
若头节点为空,则该连边为空链表
把头指针算局外人
误区,千万不要认为这个不带头
只是现在是空链表,head头中储存的是零罢了。
语句翻译(重中之重):
tail 表示尾节点本身的地址或倒数第二个节点储存的地址。
tail -> next ,表示的是tail储存的地址,
所以 tail = tail -> next,
表示的是tail从本身的地址变为了
tail储存的地址 (而tail储存的地址就是上一个节点储存的地址)
注意:语句翻译是一个常见的技巧,把人话翻译成鬼话
命名习惯:
Node(节点);
list(表格);
为了区分单双链表:
sListNode(单链表);
常见错误:
容易解引用空指针(->有解引用的作用):图
申请一个字节的时候,不要头指针引导,因为只要找到尾巴就好了。
返回这个尾巴的地址,和NULL交换。
函数自己的事情自己干,不要混淆了,ex(创造节点关我找尾什么事啊)
链表开空间的时候:
开的单位是结构体,而不是像顺序表一样(数组数据)类型
的大小。
在有节点创建的情况下,这时tail表示的是原链表的末端。
而新链表的末端是newNode。
链式结构创建新节点不需要传一个地址的,直接一个值解
决问题,如图
链表的修改:
链表由头节点控制,一些可能改变头的操作就要传二级指针
ex:尾插,为删(当链表为空)
ex:头插,头删(一定会改变头指针)
链表的销毁:
链表在物理上不连续,因此必须要一个一个节点的去销毁。
一个一个的把malloc出来的节点消除了。
顺序表在物理上连续,直接销毁数组就能够解决问题。
链表缺点:
无法随机访问(所有链表的缺点)
通过单链表的后一个节点,找不到单链表的前一个节点(单链表的缺点)
双向循环带头链表:
1.双向循环带头链表是最优的链表,几乎不用分类讨论。
接口分析:
1.ListPopFind(查找的作用)是为了配合下面的定位插入。
2.这里面没有传二级指针就是这个双向链表的优势 所有没有传二级指针。
注意:
头节点不是有效节点,当链表只有一个头节点的时候是自己指向自己
这个叫做哨兵位节点。
这个头节点的辅助作用实在夸张:
1.带哨兵位的头节点永远不会改变头,所以不需要二级指针
2.哨兵位头节点方便尾插(空链表也能直接往里头插,不要分类讨论)