导语
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的实现顺序是通过链表中的指针链接次序实现的。链表由一系列的节点(链表中每个元素成为节点)组成,节点可以在运行时动态生成。每个节点包括两部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址大的指针域。
常见链表结构
链表结构五花八门,常见的链表结构有:单链表、双向链表和循环链表。
- 单链表
单链表中两个结点比较特殊,分别是第一个结点和最后一个结点,我们习惯称第一个结点叫头结点把最后一个结点称尾结点,其中头结点用来记录链表的基地址,尾结点比较特殊指针不是指向下一个结点合适一个空地址NULL - 循环链表
循环链表是一种特殊的单链表,尾结点指针指向头结点地址 - 双向链表
顾名思义双向链表支持两个方向,每个节点不止有一个后继指针next指向后边的结点,还有一个前驱指针prev指向前面的结点
双向链表的优点
在实际开发中,从链表中删除一个数据有两种情况:
1:删除结点中“值等于某个给定值”的结点
2:删除给定指针指向的结点
对于第一种情况不管是单链表还是双向链表,为了查找到值等于给定值的结点,都需要从头结点开始一个一个遍历对比,直到找到值等于给定值得结点。然后在通过指针操作将其删除。时间复杂度分析,时间消耗在遍历数值上面复杂度为O(n);
第二种情况,我们已经找到了要删除的结点,由于单链表不支持直接获取他的前驱结点,所以为了找到该结点的前驱结点我们还是要从头遍历链表,直到p->next=q,说明p是q的前驱结点,然后在通过指针进行删除操作。双向链表一个结点中保存了它的前驱结点prev,所以很容易进行删除操作。时间复杂度分析O(1);
链表常见操作
- 链表实现LRU缓存淘汰算法
- 单链表反转
头结点插入法
public static ListNode reverseListByInsert(ListNode listNode){
ListNode resultList = new ListNode(-1); //定义一个哨兵结点
ListNode p = listNode;
while (p!= null){
ListNode tempNodeList = p.next;
p.next = resultList.next;
resultList.next = p;//插入到哨兵结点
p = tempNodeList;
}
return resultList.next;
}
就地反转法
public static ListNode reverseListByLocal(ListNode listNode) {
ListNode resultNode = new ListNode(-1);
resultNode.next = listNode;
ListNode q = listNode;
ListNode qNext = q.next;
while (qNext != null) {
q.next = qNext.next;//q 数据永远不变,指针随着遍历游标后移
qNext.next = resultNode.next; //获取反转后驱结点
resultNode.next = qNext;// 哨兵结点保存下反转后的"前驱"结点
qNext = q.next;
}
return resultNode.next;
}
- 链表中环的检测
快慢指针
public static boolean hasLoop(ListNode listNode) {
if (listNode == null) return false;
ListNode fastPoint = listNode;
ListNode slowPoint = listNode.next;
while (fastPoint != null && slowPoint != null) {
fastPoint = fastPoint.next;///指针移动一位
slowPoint = slowPoint.next.next;//指针移动两位
if (fastPoint == null) {
return false;
} else if (fastPoint == slowPoint) {
return true;
}
}
return false;
}
跟踪记录法
private static HashMap<ListNode, Integer> nodeMap = new HashMap<>();
public static boolean hasLoopV2(ListNode listNode, int index) {
if (listNode == null || listNode.next == null) return false;//最少三个成环
if (nodeMap.containsKey(listNode)) {
return true;
} else {
nodeMap.put(listNode, index);
return hasLoopV2(listNode.next, ++index);
}
}
- 两个有序链表合并,合并后仍有序
有序同向链表合并
public static ListNode mergeTwoList(ListNode head1, ListNode head2) {
if (head1 == null && head2 == null) return null;
if (head1 == null) return head2;
if (head2 == null) return head1;
ListNode head;
//此次演示的是两个递增链表,如果两个同为递减链表,则if判断句为head1.data < head2.data
if (head1.data > head2.data) {
head = head2;
head.next = mergeTwoList(head1, head2.next);
} else {
head = head1;
head.next = mergeTwoList(head1.next, head2);
}
return head;
}
反向链表可以先进行链表反转,然后在同向合并
- 删除链表倒数第n个结点
双指针法
public static ListNode removeNthFromEnd(ListNode head, int n) {
ListNode preNode = head;
ListNode curNode = head;
for (int i = 0; i < n; i++) {
preNode = preNode.next;
if (preNode == null && i != (n - 1)) return null;//判断是否超出最大个数
}
if (preNode == null) {//删除头结点判断
ListNode tempList = curNode.next;
curNode.next = null;
return tempList;
}
//两个指针,先到尾结点停止循环,后指针的下一个即为要删除的结点
while (preNode.next != null) {
curNode = curNode.next;
preNode = preNode.next;
}
curNode.next = curNode.next.next;
return head;
}
- 求链表中的间结点
快慢指针
public static ListNode middleNode(ListNode listNode) {
if (listNode.next == null) return listNode;
ListNode fastNode = listNode.next.next;
if (fastNode == null) return listNode.next;
ListNode slowNode = listNode;
while (fastNode != null) {
fastNode = fastNode.next.next;
slowNode = slowNode.next;
}
return slowNode;
}