链表
概述
定义
在计算机科学中,链表是数据元素的线性集合,每个元素都指向下一个元素,元素储存上并不连续
分类
-
单项链表:每个元素只知道下一个元素
-
-
双向链表:每个元素知道上一个元素和下一个元素
-
-
循环链表:通常的链表尾节点tail指向的都是null,而循环链表的tail指向的是节点头head
链表内还有一种特殊的节点称为哨兵节点,也叫哑元节点,他不存储数据,通常用作头尾,用来简化边界判断
单向带哨兵链表
不管是添加还是删除,大致思路都要找到要添加(删除)节点的前后两节点,然后添加就是创建新节点,让prev指向添加节点,让添加节点指向后节点,删除就是让前节点指向后节点,将删除节点未被引用,java中的未被引用的节点不会像C语言中的一样,不会占用内存
public class test1 implements Iterable<Integer>{
private Node head = new Node(666,null);//头指针,因为是Node类型,所以其实里面也有Node的两个属性,
// 其实就是一个Node,不过是放在了第一个位置
private static class Node{
int value;//值
Node next;//也就是下一个节点
public Node(int value,Node next) {
this.next = next;
this.value = value;
}
}
//向起始位置添加
public void addFirst(int value){
//链表不为空
//既可以处理为空也可以处理非空,因为为空时,next指向的是null,和上面head定义是一致的
// head = new Node(value,head);
insert(0,value);
}
//遍历链表1
//Consumer用来传递要执行的操作
public void loop1(Consumer<Integer> consumer){
Node p = head.next;
while( p != null ){
consumer.accept(p.value);
p = p.next;
}
}
//遍历链表2
public void loop2(Consumer<Integer> consumer){
for(Node p = head.next;p != null;p = p.next){
consumer.accept(p.value);
}
}
//遍历链表3,递归遍历
public void loop3(Consumer<Integer> before,
Consumer<Integer> after){
recursion(head.next,before,after);
}
private void recursion(Node curr, Consumer<Integer> before, Consumer<Integer> after){
if(curr == null){
return;
}
before.accept(curr.value);
recursion(curr.next, before, after);
after.accept(curr.value);
}
//匿名内部类
//当内部类用到了外部类的成员变量时,就不可以加static了
@Override
public Iterator<Integer> iterator() {
return new Iterator<Integer>() {
Node p = head.next;
@Override
public boolean hasNext() {//判断是否有下一个元素
return p != null;
}
@Override
public Integer next() {//返回当前值,并指向下一个元素
int v = p.value;
p = p.next;
return v;
}
};
}
//找到最后位置
//这样的分布操作可以写成private的形式,就不会被外部访问
private Node findLast(){
Node p ;
for( p = head;p.next != null;p = p.next){
}
return p;
}
//向最后位置添加
public void addLast(int value){
Node last = findLast();
last.next = new Node(value,null);
}
//根据索引找节点
private Node findNode(int index){
int i = -1;
for(Node p = head;p != null;p = p.next,i++){
if(i == index){
return p;
}
}
return null;
}
//根据节点找内容
public int get(int index){
Node node = findNode(index);
if(node == null){
throw illegalException(index);
}
return node.value;
}
//向链表中插入数据
public void insert (int index,int value){
//判断插入位置的上一个节点是否存在
Node node = findNode(index - 1);
if(node == null){
throw illegalException(index);
}
//插入数据
node.next = new Node(value, node.next);
}
//删除首位
public void removeFirst(){
remove(0);
}
//删除指定索引的节点
public void remove(int index){
//找到index前一位
Node pre = findNode(index - 1);
//判断前一位是否为空
if(pre == null){
throw illegalException(index);
}
//找到要删除的节点
Node node = pre.next;
if(node == null){
throw illegalException(index);
}
//让上一位指向删除节点的下一位
pre.next = node.next;
}
private static IllegalArgumentException illegalException(int index) {
return new IllegalArgumentException(String.format("index [%d] 不合法%n", index));
}
}
此外,对Consumer还有迭代器的遍历也与其他直接调用存在差异
public class test {
public static void main(String[] args){
test1 list = new test1();
/* //1.向首位添加
list.addFirst(1);
list.addFirst(2);
list.addFirst(3);
list.addFirst(4);
//2.while遍历
list.loop1(System.out::println);
//3.for遍历
list.loop2(System.out::println);
//4.迭代器遍历
for (Integer value : list) {
System.out.println(value);
}*/
//5.向末尾添加
list.addLast(1);
list.addLast(2);
list.addLast(3);
list.addLast(4);
/*//6.根据索引找value
int i = list.get(2);
System.out.println(i);*/
//7.插入节点
// list.insert(0,5);
//8.删除首位
//list.removeFirst();
//9.删除指定节点
/*list.remove(1);
for (Integer value : list) {
System.out.println(value);
}*/
//10.递归遍历
list.loop3(value-> System.out.println("before:"+value), value-> System.out.println("after:"+value));
}
}
双向带哨兵链表
-
差异1:与单向链表不同的是他有一个尾哨兵,这样可以避免链表的空指针异常,但是在删除的时候,要判断被删除节点是否是两个哨兵,避免将哨兵删除
-
差异2:在添加和删除是因为是双向的,所以在修改时除了新节点要改,前后两节点的next,prev也需要修改
public class DoublyLinkedListSentinel implements Iterable<Integer>{
private Node head;
private Node tail;
static class Node {
Node prev;//上一个节点指针
int value;
Node next;//下一个节点指针
public Node(Node prev, int value, Node next) {
this.prev = prev;
this.value = value;
this.next = next;
}
}
public DoublyLinkedListSentinel(){
head = new Node(null, 666, null);
tail = new Node(null, 666, null);
head.next = tail;//让头哨兵指向尾哨兵
tail.next = head;//让尾哨兵指向头哨兵
}
//向索引为0的位置插入
public void insertFirst(int value){
insert(0,value);
}
//根据索引查找节点
private Node findNode(int index){
int i = -1;
for(Node p = head;p != tail;p = p.next,i++){
if(index == i){
return p;
}
}
return null;
}
//根据节点插入节点
public void insert(int index , int value){
Node prev = findNode(index - 1);
if(prev == null){
throw illegalException(index);
}
Node next = prev.next;
Node node = new Node(prev, value, next);
prev.next = node;
next.prev = node;
}
//尾部添加
public void insertEnd(int value){
Node last = tail.prev;
Node inserted = new Node(last, value, tail);
last.next = inserted;
tail.prev = inserted;
}
//尾部删除
public void removeEnd(){
Node last = tail.prev;
if(last == head){
throw illegalException(0);
}
Node prev = last.prev;
prev.next = tail;
tail.prev = prev;
}
//头部删除
public void removeFirst(){
remove(0);
}
//根据节点删除元素
public void remove(int index){
//找到要删除节点的上一个节点
Node prev = findNode(index - 1);
if(prev == null){
throw illegalException(index);
}
//找到要删除节点,并判断是否为尾哨兵,不可以删除尾哨兵
Node removed = prev.next;
if(removed == tail){
throw illegalException(index);
}
//找到要删除节点的下一个节点
Node next = removed.next;
//
prev.next = next;
next.prev = prev;
}
//迭代器遍历
@Override
public Iterator<Integer> iterator() {
return new Iterator<Integer>() {
Node p = head.next;
@Override
public boolean hasNext() {
return p != tail;
}
@Override
public Integer next() {
int value = p.value;
p = p.next;
return value;
}
};
}
//抛出索引异常
private static IllegalArgumentException illegalException(int index) {
return new IllegalArgumentException(String.format("index [%d] 不合法%n", index));
}
}
双向带哨兵环形链表
环形链表中只有一个哨兵,他既代表头哨兵也代表了尾哨兵
public class CircularLinkedList implements Iterable<Integer>{
private Node sentinel = new Node(null,100,null);
private static class Node{
Node prev;
int value;
Node next;
public Node(Node prev, int value, Node next) {
this.prev = prev;
this.value = value;
this.next = next;
}
}
public CircularLinkedList() {
sentinel.next = sentinel;
sentinel.prev = sentinel;
}
//头部添加
public void addFirst(int value){
Node sentinel1 = sentinel;
Node next = sentinel1.next;
Node added = new Node(sentinel1, value, next);
sentinel1.next = added;
next.prev = added;
}
//尾部添加
public void addEnd(int value){
Node a = sentinel;
Node b = sentinel.prev;
Node added = new Node(b, value, a);
b.next = added;
a.prev = added;
}
//头部删除
public void removeFirst(){
Node a = sentinel;
Node removed = sentinel.next;
if(removed == sentinel){
throw new IllegalArgumentException("非法");
}
Node b = removed.next;
a.next = b;
b.prev = a;
}
//尾部删除
public void removeEnd(){
Node removed = sentinel.prev;
if(removed == sentinel){
throw new IllegalArgumentException("非法");
}
Node a = sentinel;
Node b = removed.prev;
b.next = a;
a.prev = b;
}
//根据值查找节点
private Node findByValue(int value){
Node p = sentinel.next;
while(p != sentinel){
if(p.value == value){
return p;
}
p = p.next;
}
return null;
}
//根据节点删除
public void removeByValue(int value){
Node removed = findByValue(value);
if(removed == null){
return;
}
Node a = removed.next;
Node b = removed.prev;
b.next = a;
a.prev = b;
}
//迭代器遍历
@Override
public Iterator<Integer> iterator() {
return new Iterator<Integer>() {
Node p = sentinel.next;
@Override
public boolean hasNext() {
return p != sentinel;
}
@Override
public Integer next() {
int value = p.value;
p = p.next;
return value;
}
};
}
private static IllegalArgumentException illegalException() {
return new IllegalArgumentException();
}
}
设计链表
class MyLinkedList {
int size;
ListNode head;
public MyLinkedList() {
size = 0;
head = new ListNode(0);
}
public int get(int index) {
if(index < 0 || index >= size){
return -1;
}
ListNode node = head;
for(int i = 0 ; i <= index ; i++){
node = node.next;
}
return node.val;
}
public void addAtHead(int val) {
addAtIndex(0, val);
}
public void addAtTail(int val) {
addAtIndex(size, val);
}
public void addAtIndex(int index, int val) {
if(index > size){
return;
}
if(index < 0){
index = 0;
}
size++;
ListNode pre = head;
for(int i = 0 ; i < index ; i++){
pre = pre.next;
}
ListNode insert = new ListNode(val);
insert.next = pre.next;
pre.next = insert;
}
public void deleteAtIndex(int index) {
if(index >= size || index < 0){
return;
}
size--;
ListNode pre = head;
for(int i = 0 ; i < index ; i++){
pre = pre.next;
}
pre.next = pre.next.next;
}
}
/**
* Your MyLinkedList object will be instantiated and called as such:
* MyLinkedList obj = new MyLinkedList();
* int param_1 = obj.get(index);
* obj.addAtHead(val);
* obj.addAtTail(val);
* obj.addAtIndex(index,val);
* obj.deleteAtIndex(index);
*/
删除节点
这里以链表 1 4 2 4 来举例,移除元素4。
如果使用C,C++编程语言的话,不要忘了还要从内存中删除这两个移除的节点, 清理节点内存之后如图:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeElements(ListNode head, int val) {
while (head != null && head.val == val) {
head = head.next;
}
//第一个循环就是避免了删除头节点失败的漏洞,如果要删除头节点,就让头节点向后移动一位
if(head == null){
return head;
}
//判断是否为空,若为空就结束
//让一个节点代替头节点,然后判断
for(ListNode p = head ; p.next != null ; ){
if(p.next.val == val){
p.next = p.next.next;
}else{
p = p.next;
}
}
return head;
}
}
反转链表
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
if(head == null){
return null;
}
//判断是否为空
ListNode pre = null;
ListNode cur = head;
//定义两个指针一个是先前的,一个是当前的,当前指针一直指向先前的节点,然后定义一个变量,来记录原来链表的节点下一位,通过当前指针以及下一指针来将指向前的指针和当前指针做一个移动
while(cur != null){
ListNode temp = cur.next;
cur.next = pre;
pre = cur;
cur = temp;
}
//最后返回的是向前的指针,因为原来的头节点已经翻了过来
return pre;
}
}
- /**
- * Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
//定义一个新链表为空
ListNode n = null;
//定义一个节点指向头节点
ListNode p = head;
while( p != null){
//不断获取旧链表的值并且插入新链表的头部
n = new ListNode(p.val,n);
//让头节点一直指向下一个节点
p = p.next;
}
return n;
}
}
class Solution {
public ListNode reverseList(ListNode head) {
ListNode list1 = head;
ListNode list2 = null;
while(true){
ListNode first = list1.removeFirst();
if(first == null){
break;
}
list2.addFirst(first);
}
return list2;
}
public ListNode removeFirst(){
ListNode first = head;
if(first == null){
return null;
}
head = first.next;
return first;
}
public void addFirst(ListNode first){
first.next = head;
head = first;
}
class Solution {
public ListNode reverseList(ListNode head) {
//让p等于头哨兵
ListNode p = head;
//当头等于空或者p的下一个节点也等于空时,结束
if(p == null || p.next == null){
return p;
}
//递归,一直向下一个递归
ListNode last = reverseList(p.next);
//将链表反过来,p.next = 5,让的下一节点指向4,3,2,1,最后实现反转
p.next.next = p;
//让p指向的为空
p.next = null;
return last;
}
}
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
if(head == null || head.next == null){
return head;
}
ListNode o2 = head.next;
ListNode n1 = head;
while(o2 != null){
//将旧链表的第二位置的节点不断取出,就是让第一节点指向第三节点
head.next = o2.next;
//让第二节点指向新链表第一节点
o2.next = n1;
//让新链表第一节点等于原先第二节点的值
n1 = o2;
//让原先的第二节点重新放回
o2 = head.next;
}
return n1;
}
### 反转链表5
```java
//大概思路就是有两个链表,然后让旧链表的指针做一个搬运,旧链表中o2就是一个定位的作用,方便o1复位
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
if(head == null || head.next == null){
return head;
}
ListNode n1 = null;
while(head != null){
//让o2做一个定位就是永远是o1的下一位
ListNode o2 = head.next;
//让o1指向新链表的头部
head.next = n1;
//给新链表的头部赋值成o1携带的值
n1 = head;
//让o1做一个复位
head = o2;
}
return n1;
}
}
交换顺序
两两交换顺序
初始时,cur指向虚拟头结点,然后进行如下三步:
操作之后,链表如下:
看这个可能就更直观一些了:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode swapPairs(ListNode head) {
//1.设置虚拟头节点
ListNode fictitious = new ListNode(0);
//2.让它指向头节点
fictitious.next = head;
//3.设置虚拟指针
ListNode pointer = fictitious;
//4.设置交换节点之后的另一个节点,其实也就是另一个指针
ListNode temp;
//5.设置第一个需要交换的节点
ListNode firstNode;
//6.设置第二个需要交换的节点
ListNode secondNode;
while(pointer.next != null && pointer.next.next != null){
temp = pointer.next.next.next;
firstNode = pointer.next;
secondNode = pointer.next.next;
//让虚拟指针指向第二节点
pointer.next = secondNode;
//让第二节点指向第一节点
secondNode.next = firstNode;
//让第一节点指向后指针
firstNode.next = temp;
//改变前指针位置到第二个节点的位置
pointer = firstNode;
}
return fictitious.next;
}
}
删除倒数第N个节点
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
//设置虚拟头节点
ListNode panelPoint = new ListNode(0);
panelPoint.next = head;
//设置快慢指针
ListNode fast = panelPoint;
ListNode slow = panelPoint;
//让两个指针中间空出来N个节点,slow指向的就是要删除节点的上一个节点
for(int i = 0 ; i <= n ; i++){
fast = fast.next;
}
//让两个指针同时移动,直到快指针等于null
while(fast != null){
slow = slow.next;
fast = fast.next;
}
//让慢指针指向目标节点的下一个节点,这样就能把目标节点断开连接
slow.next = slow.next.next;
//返回头节点
return panelPoint.next;
}
}
相交链表
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
//原理:若相交之后的节点应该都是一致的,这样的话将后面对齐,从短的那条的头节点开始比较就可以了
//思路就是先定义两个指针,然后通过指针移动来计算出两个链表的长度
//让后确定出一条长的,其实通过判断来也可以,就是把判断之后的重复步骤变成人为定义的哪一条更长
//之后算出长度差,通过长度差,将长的链表的指针向后移动,达到尾对齐的目的
//之后循环遍历找到交点
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode pointer1 = headA;
ListNode pointer2 = headB;
int len1 = 0 , len2 = 0;
while(pointer1 != null){
len1++;
pointer1 = pointer1.next;
}
while(pointer2 != null){
len2++;
pointer2 = pointer2.next;
}
pointer1 = headA;
pointer2 = headB;
if(len1 < len2){
int middle = len1 ;
len1 = len2;
len2 = middle;
ListNode middlePointer = pointer1;
pointer1 = pointer2;
pointer2 = middlePointer;
}
int len = len1 - len2;
while((len--) != 0){
pointer1 = pointer1.next;
}
while(pointer1 != null){
if(pointer1 == pointer2){
return pointer1;
}
pointer1 = pointer1.next;
pointer2 = pointer2.next;
}
return null;
}
}
注意点:
-
长度定义时需要赋值
-
还有就是计算出长度后指针一定要复位
环形链表找节点
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode detectCycle(ListNode head) {
//设置快慢指针
ListNode fast = head;
ListNode slow = head;
//让快指针一次移动两位,慢指针一次移动一位,这样就会有快指针出了圈,然后去追慢指针
while(fast != null && fast.next != null){
//因为快指针一次移动两位,所以快指针应该判断两次空
fast = fast.next.next;
slow = slow.next;
if(fast == slow){
//当快慢指针重合时,重合的点到进入循环的点的距离还有从头指针到循环点的距离是一致的,这样的话让他们相等时返回index1就是循环点
ListNode index1 = fast;
ListNode index2 = head;
while(index1 != index2){
index1 = index1.next;
index2 = index2.next;
}
return index1;
}
}
return null;
}
}