链表是由一个个结点(Node)连接而成的,结点分为数据域、指针域。指针域用于指向其后面的结点地址,而在链表尾部节点的指针域置为空(NULL),代表链表的结束。
通常节点的数据形式为:
struct listNode
{
listNode *next; //指针域:指向后面节点的指针
dataType data; //数据域:保存数据
};
和数组的区别:
1.遍历:
数组是一片连续的内存空间,所以其可以通过地址的相加减进行访问(不越界的情况下),也就是达到O(1)的访问速度,但数组这种存储方式,会造成不必要的内存浪费(数据未填满数组空间)。而链表是链式存储的,也就是不需要连续的内存空间,相对于数组,其内存使用率无疑更出色,但访问节点则只能通过O(n)的移动才能访问成功。
2.插入:
数组的插入非常麻烦,如果插入的是尾部,则realloc后赋值,但如果是插入在数组的首个元素,那么需要对数组做出n次的移动操作。反观链表,插入非常方便,前进至插入位置,连接指针即可。
环形链表:
环形链表则是一个链表的尾部连接在链表中某个节点,那么如何判断一个链表是否存在环?
1.哈希表
遍历链表的过程中把每个节点添加至哈希表,并计数为1,当链表遍历完毕,没有产生重复计数则说明链表没有环,反之则存在环。
如果存在环,那么在必定会访问哈希表中已经记录过的节点。
2.快慢指针
创建指针p1,p2,每次p1前进1步,而p2前进2步,如果这两个指针能相遇则说明存在环,反之不存在。
面试题:删除链表的倒数k个节点:
例如:
删除下面链表的倒数第二个节点
这条题其实就是对链表的遍历,我们需要三个指针,假设为p1,p2,parent。p1是要删除的节点,p2是辅助节点,而parent则是保存p1的父节点。
将p2先前进k-1个节点,然后两个指针同时向前,当p2前进至尾部时,则p1刚好到达倒数第k个节点。