链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。
使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。链表最明显的好处就是,常规数组排列关联项目的方式可能不同于这些数据项目在记忆体或磁盘上顺序,数据的存取往往要在不同的排列顺序中转换。链表允许插入和移除表上任意位置上的节点,但是不允许随机存取。链表有很多种不同的类型:单向链表,双向链表以及循环链表。
下面将采用 jvav语言 演示链表及其相关操作
双向链表
class Node<V> {
public V data;
public Node last;
public Node next;
@Override
public String toString() {
return "Node{" +
"data=" + data +
'}';
}
public Node(V data) {
this.data = data;
}
//翻转链表
static Node reverse(Node head) {
Node last = null;
Node next = null;
while (head != null) {
next = head.next;
head.next = last;
head.last = next;
last = head;
head = next;
}
return last;
}
//获取链表最后的节点
static Node end(Node node) {
while (node.next != null) node = node.next;
return node;
}
//连接链表或节点
void link(Node node) {
end(this).next = node;
node.last = this;
}
}
单向链表
单向链表相比双向链表,节点只指向下一个节点,因此更加节省空间,实际操作也更困难,本篇主要演示单向链表。
1.单向链表的基本操作
class Node<V> {
public V data;
public Node next;
@Override
public String toString() {
return "Node{" +
"data=" + data +
'}';
}
//返回整条链表
public String toStr() {
Node node = this;
StringBuffer str = new StringBuffer();
str.append(this);
while (node.next != null) {
str.append(" -> ");
str.append(node.next);
node = node.next;
}
return str.toString();
}
public Node(V data) {
this.data = data;
}
//翻转链表
static Node reverse(Node head) {
Node last = null;
Node next = null;
while (head != null) {
next = head.next;
head.next = last;
last = head;
head = next;
}
return last;
}
//获取链表最后的节点
static Node end(Node node) {
while (node.next != null) node = node.next;
return node;
}
//获取第num个节点
public Node get(int num) {
Node node = this;
while (num != 0) {
node = node.next;
num--;
}
return node;
}
//连接链表或节点
public void link(Node node) {
end(this).next = node;
}
}
2.单向链表的进阶操作
//获取Node中间的节点
//原理:使用快指针 慢指针 时间复杂度O(N)
static Node mid(Node node) {
Node S = node; //slow
Node Q = node; //quick
while (Q.next != null && Q.next.next != null) {
S = S.next;
Q = Q.next.next;
}
return S;
}
//判断一个Node是否有环 有则返回环入口 否则返回null
//原理:使用快指针 慢指针 时间复杂度O(N)
static public Node ring(Node node) {
if (node == null || node.next == null || node.next.next == null) return null;
Node S = node.next; //slow
Node Q = node.next.next; //quick
while (S != Q) {
S = S.next;
Q = Q.next.next;
if (Q.next == null || Q.next.next == null) return null;
}
//我们终将重逢
Q = node;
while (S != Q) {
S = S.next;
Q = Q.next;
}
return Q;
}
//判断一个Node是否为回文结构
//例如 ① - ② - ③ - ③ - ② - ①
// ① - ② - ③ - ② - ①
//要求时间复杂度O(N) 额外空间复杂度O(1)
static boolean isPalindrome(Node node) {
//mid为mid或偏左
Node mid = mid(node);
mid = reverse(mid);
Node temp = mid;
while (temp.next != null) {
if (node.data != temp.data) {
reverse(mid);
return false;
}
temp = temp.next;
node = node.next;
}
reverse(mid);
return true;
}
效果演示
class View1 {
public static void main(String[] args) {
Node node = new Node<>(0);
node.link(new Node(1));
node.link(new Node(2));
// node.link(new Node(3));
// node.link(new Node(4));
node.link(new Node(5));
node.link(new Node(5));
// node.link(new Node(4));
// node.link(new Node(3));
node.link(new Node(2));
node.link(new Node(1));
node.link(new Node(0));
// node.link(new Node(null));
System.out.println(node.toStr());
System.out.println("node中间节点为:" + Node.mid(node));
System.out.println("node是否有回文:" + Node.isPalindrome(node));
System.out.println("node是否有环:" + Node.ring(node));
//形成环
node.link(node.get(2));
System.out.println("node是否有环:" + Node.ring(node));
}
}
//两个Node可能有环也可能无环
//判断是否相交(共用节点)
//是则返回第一个相交的Node 否则返回null
//要求时间复杂度O(N) 额外空间复杂度O(1)
static Node intersect(Node node1, Node node2) {
Node r1 = ring(node1);
Node r2 = ring(node2);
if (r1 == null && r2 != null) return null;
if (r1 != null && r2 == null) return null;
//都有环的情况下 且切入点不相等的情况下
if (r1 != r2) {
Node temp = r1;
while (temp != r2) {
temp = temp.next;
if (r1 == temp) return null;
}
return temp;
}
int n = 0;
Node n1 = node1;
Node n2 = node2;
//都有环 且切入点相等的情况下
while (n1 != r1) {
n1 = n1.next;
n++;
}
while (n2 != r1) {
n2 = n2.next;
n--;
}
n1 = node1;
n2 = node2;
if (n > 0) {
while (n != 0) {
n1 = n1.next;
n--;
}
}
if (n < 0) {
while (n != 0) {
n2 = n2.next;
n++;
}
}
while (n1 != n2) {
n1 = n1.next;
n2 = n2.next;
}
return n1;
}
}
效果演示
//创建3个Node
Node node = new Node<>(0);
node.link(new Node<>(1));
node.link(new Node<>(2));
node.link(new Node<>(3));
node.link(new Node<>(4));
node.link(new Node<>(5));
Node node1 = new Node<>(10);
node1.link(new Node<>(11));
node1.link(new Node<>(12));
node1.link(new Node<>(13));
node1.link(new Node<>(14));
node1.link(new Node<>(15));
Node node2 = new Node<>(20);
node2.link(new Node<>(21));
node2.link(new Node<>(22));
node2.link(new Node<>(23));
node2.link(new Node<>(24));
node2.link(new Node<>(25));
System.out.println(node.toStr());
System.out.println(node1.toStr());
System.out.println(node2.toStr());
System.out.println("是否有交点:" + Node.intersect(node1, node2));
演示情况1
//无环的情况下,相交的情况下
node1.link(node);
node2.link(node);
System.out.println(node1.toStr());
System.out.println(node2.toStr());
演示情况2
//均自环的情况下,无相交的情况下
node1.link(node1.get(1));
node2.link(node2.get(1));
演示情况3
//均有环,交点一致的情况下
node.link(node);
node1.link(node);
node2.link(node);
演示情况4
//均有环,交点不一致的情况下
node.link(node);
node1.link(node);
node2.link(node.get(3));
演示情况5
//一方有环 一方无环的情况下 无交点的情况下
node1.link(node1);
世界线回溯,从jvav到架构师