重温数据结构-链表

链表是线性表的一种,但是在存储上和同是线性结构的数组有很大差异,数组是要求存储连续的,链表却是可以分散的,当总存储空间够但是连续存储空间不够的时候数组申请是会失败的,但是链表是可以创建成功的。

链表数据结构的核心就是下面这样的,包含一个保存实际数值的val,还有一个指向下一个链表节点的引用或者是指针(java为引用,c为指针),这里描述的是单链表的基础节点数据结构,后面还会介绍循环链表、双向链表。

 public class ListNode {
     int val;
     ListNode next;
     ListNode(int x) { val = x; }
 }

单向链表

对于普通单链表的随机访问时间复杂度为O(n),因为需要从链表头依次顺序查找,但是对于链表结构添加和删除节点的时间复杂度都是O(1)不需要像数组一样进行数据迁移,不过查找插入和删除位置还要单独计算时间复杂度。插入和删除节点其实就是先通过查询找到需要进行操作的位置,然后修改节点的next连接就可以了。被删除的节点如果是C或者是C++需要进行资源回收,如果是java,因为已经没有其他地方引用,GC会自动回收资源。

循环链表

循环链表是一种特殊的单链表,实际上循环链表也非常简单,他和单链表的唯一区别就是在尾节点,单链表的尾节点指向null,表示链表结束,循环链表的尾是指向链表头节点。循环链表的操作也比较简单,在LeetCode中也有一些关于实现循环链表的题目,后面的文章我也会用不同的基础数据结构单独实现下循环链表。

双向链表

我们常见的单向链表因为是一个方向的链接,这就导致无法直接查找前继节点,需要从头节点遍历才行,单向链表查找前继节点的时间复杂度是O(n)。如果我们在一些场景频繁需要查找前继节点那么就可以使用双向链表,双向链表和单向链表的区别就在于在节点结构里多了一个前继节点引用或指针,在维护链表的时候也要多维护一个前继指针的关系。所以同样的数据,双向链表需要的空间要多出O(n),但是双向链表查找前继节点的时间复杂度降到了O(1)。

 public class ListNode {
     int val;
     ListNode next;
     ListNode pre;
     ListNode(int x) { val = x; }
 }

链表代码的编写技巧

1)防止指针丢失。在写链表代码的时候一定要注意插入节点时一定要注意操作顺序,防止指针丢失,造成链表断开。

2)对链表的插入、删除操作,需要对插入第一个节点和删除最后一个节点的情况进行特殊处理。

3)重点关注边界处理。如果链表为空,是否能正常处理;如果链表只有一个节点,是否能够正常处理;如果处理的是头节点或者尾节点是否可以正常处理。

4)通过画链表图来辅助思考。先在图上进行演算,再看图写代码。

总结

通过介绍3种链表我们可以看到对于链表结构我们要根据具体的使用场景来使用不同的数据结构来应对。对于通过下标随机读取非常多、插入和删除数据是从末尾操作的场景适合用数组,对于随机读较少、插入和删除操作较多的场景适合用链表,对于需要频繁查找前继和后继节点的场景适合用双向链表,对于资源可以循环利用的可以使用循环链表。

通过单向链表和双向链表的介绍,还需要明白可以通过定义数据结构用空间换时间的设计思想。当内存空间充足的时候,如果追求代码执行效率,可以选择空间复杂度更高的数据结构或算法来提高执行效率。

你可能感兴趣的:(重温数据结构-链表)