链表、单向双向循环链表、遍历、快慢指针(从入门到入坑)
什么是链表
链表是一种基本数据结构,取值虽然没有数组方便,但是在扩容、删除时,比数组方便很多。链表的基本单位是一个个节点,节点内存储数据的同时还能存储下一个节点的地址,所以在删除、新建节点的时候,比数组高效很多。
链表的分类
单向链表:
如果最后一个节点的下一个节点指向是null,就是一个单向链表。
如果最后一个节点的下一个节点指向头结点,就是一个单向循环列表。
//单向链表的节点
public class Link {
int value; //可以存储数据
Link next; //下一个节点的地址
public Link() {}
public Link(int value) { this.value = value; }
}
link1 = new Link();
link2 = new Link();
link3 = new Link();
//单向链表
link1.next = link2;
link2.next = link3;
link3.next = null;
//单向循环列表
link1.next = link2;
link2.next = link3;
link3.next = link1
双向链表:
如果两个相邻的节点之间是有相互的指向,且最后一个节点的下一个个节点指向null,就是一个双向链表。
如果两个相邻的节点之间是有相互的指向,且最后一个节点的下一个节点指向头结点,就是一个双向循环列表。
//双向链表的节点
public class Link {
int value; //可以存储数据
Link next; //下一个节点的地址
Link last; //上一个节点的地址
public Link() {}
public Link(int value) { this.value = value; }
}
link1 = new Link();
link2 = new Link();
link3 = new Link();
//双向链表
link1.next = link2;
link1.last = null;
link2.next = link3;
link2.last = link1;
link3.next = null;
link3.last = link2;
//双向循环列表
link1.next = link2;
link1.last = link3;
link2.next = link3;
link2.last = link1;
link3.next = link1
link3.last = link2;
链表的遍历
双向链表和单向链表遍历方法相同,并且双向链表因为有上一项的地址,所以实际操作使用起来更方便。
public static void main(String[] args) {
Link link1 = new Link();
Link link2 = new Link();
Link link3 = new Link();
link1.next = link2;
link2.next = link3;
link3.next = link1;
Link index = link1;
while (true){
System.out.print(index.value+" "); //输出值
//判断是否为最后一个
if (index.next == null){
// 链表已经到达最后一位
System.out.println("非循环链表");
break;
}
index = index.next;
//判断是否是循环列表
if (index == link1){
System.out.println("循环链表");
break;
}
}
}
//这段代码会输出: 0 0 0 循环链表
快慢指针
快慢指针是定义两个指针(在java里没有指针的概念,应该是引用),快的指针一次前进两个节点,慢的指针每次前进一个节点,因为快指针是慢指针速度的两倍,所以快指针到达最后节点的时候,慢指针刚好到达链表中间节点(需要注意节点个数的奇偶)。利用快慢指针还可以判断链表是否为循环链表。
以上面的单向链表结构为例判断链表是否为循环链表:
public static void main(String[] args) {
Link link1 = new Link();
Link link2 = new Link();
Link link3 = new Link();
link1.next = link2;
link2.next = link3;
link3.next = link1;
//定义快慢指针
Link fast = link1;
Link slow = link1;
while (true){
if (fast.next==null || fast.next.next==null){
/*
只要有null就不是循环链表
当链表节点个数为奇数,就是(fast.next = null),慢指针正好是中间节点
当链表节点数为偶数,(fast.next = null)且(fast.next.next=null)慢指针是中间的两个节点的前一个
*/
System.out.println("非循环链表");
break;
}
fast = fast.next.next;
slow = slow.next;
if (fast.equals(slow)){
//当快慢指针重合,就说明这是一个环形链表
System.out.println("循环链表");
break;
}
}
}
快慢指针在算法题里面用到的非常多,实际开发中合理使用会大大增加代码的执行效率。