链表
- 不要求地址连续,因此可以建立在堆上。
- 插入的时间复杂度低。
节点定义
以下为无头节点的单链表。
1 | typedef struct _Node |
push_back
1 | void push_back(PNode node) |
make_node
1 | PNode make_node(unsigned id, char val) |
测试
1 | int main() |
traversal
1 | void traversal() |
insert_after_id
1 | void insert_after_id(unsigned id, PNode node) |
测试
1 | int main() |
insert_before_id
自己写的版本
1 | void insert_before_id(unsigned id, PNode node) |
测试
1 | int main() |
双指针法
1 | void insert_before(unsigned id, PNode node) |
remove_node
删除指定id
1 | void remove_node(unsigned id) |
测试
1 | int main() |
环
制造环
标记一个id,之后让尾节点next指向该id
1 | PNode find_id(unsigned id) |
如何判断是否有环?快慢指针
一个指针走一步,一个指针走两步。
如果两个指针重合则有环,如果无环的话,快指针肯定能到终点NULL。
1 | int has_cycle() |
让一个快指针和一个慢指针再次相遇当然可以说明此链表有环。但,难点在于如何找到环的入口?
设为头节点到循环入口处的距离,为循环入口处到相遇点的距离,为相遇点到尾节点的距离(即该节点下一个是循环口)。并结合指针和指针的步数关系,得出在相遇点:,(慢指针肯定不会在圈内转完一圈,可以想想,是合理的,因为快指针总比慢指针快圈,肯定在慢指针走完一圈前和它相遇!),则
\begin{align} x+n(y+z)+y&=2(x+y)\\ x&=n(y+z)-y\\ x&=n(y+z)-(y+z)+z\\ x&=(n-1)(y+z)+z\\ x&=z \end{align}
是圈长,我们可以想到,如果快指针要和慢指针相遇,必须要大于等于。至于,即圈数具体为几,实际上无所谓,因为快指针在里面转了圈和圈,效果是一样的!所以我们可以得出,!于是,已知为相遇点到尾节点的距离(尾节点下一个是循环口),并且已知(为头节点到循环入口处的距离)。那么,让一个指针在相遇点,另一个指针在头节点,同时步进,直到相遇,就是循环入口处!
参考:https://www.bilibili.com/video/BV1if4y1d7ob
保存到文件
保存所有节点信息到文件中。成功返回1,失败返回0。
不应当把结构体作为整体完全保存到一行。
而是每个属性一行一行地保存。因为next指针在下次运行失效的。需要按照每个属性重新构建每个节点。
1 | int save(char const * const filename) |
加载的时候,next指针的信息已经是失效的,需要重新make_node
。
1 | int load(char const * const filename) |