知识与素材摘自:Java Data Structures and Algorithms Masterclass | Udemy
本文进行了总结与归纳
类型
- 单链表
- 循环单链表
- 双链表
- 循环双链表
单链表
定义
- 链表是一种顺序集合的结构,但并不意味着它是有序的
- 链表由独立的结点组成,这些结点可以包含任何类型的数据
- 每个结点都包含对链表中下一个结点的引用
图示
与Array的差异
- 内存分布——链表的元素是独立的对象,可以随机分布在内存中
- 长度可变——链表的大小不是预定义的
- 随机访问——在数组中访问一个元素非常高效,而链表的访问只能一个一个迭代进行
内存存储
创建过程
- 创建头部节点和尾部节点,初始化为null
- 创建一个空白节点,并赋值。另外,引用部分为null(因为没有下一个节点)
- 将此节点连接至头部节点和尾部节点
代码
public class SinglyLinkedList {
public Node head;
public Node tail;
public int size;
public Node createSinglyLinkedList(int nodeValue) {
head = new Node();
Node newNode = new Node();
newNode.next = null;
newNode.value = nodeValue;
head = newNode;
tail = newNode;
size = 1;
return newNode;
}
}
复杂度
- 创建过程时间复杂度:O(1)
- 创建过程空间复杂度:O(1)
插入节点
从头部插入
- 断开Head节点原来的引用链
- 新节点的引用为原来的头部节点
- Head节点更新引用为新的节点
- 时间复杂度O(1)
实现过程
从中间插入
- 从头遍历当前链表
- 断开指定位置节点的引用
- 更换为新节点的引用
- 时间复杂度O(n)
实现过程
从末尾插入
- 从头遍历当前链表
- 断开指定位置节点的引用
- 更换为新节点的引用
- Tail节点更换引用为新的节点
- 时间复杂度O(n)
实现过程
是否使用尾部节点?
在单链表中,当我们想要在末尾插入一个值时,我们确实可以利用尾指针来避免遍历整个链表。如果尾指针可用,我们可以简单地更新最后一个节点的下一个引用,使其指向新节点,然后更新尾指针为新节点。这样,我们就可以在不需要遍历整个链表的情况下高效地在末尾插入新节点。 重要的是要理解,在单链表中插入的效率取决于尾指针是否可用。如果尾指针存在,我们可以直接访问最后一个节点,并在常数时间内执行插入操作。然而,如果尾指针不可用,那么我们需要遍历整个链表以达到最后一个节点,然后再插入新节点。 所以,如果尾指针存在,我们不需要完全遍历到末尾来在单链表中插入一个值。我们可以利用尾指针来高效地在末尾插入新节点。
代码实现
public void insertInLinkedList(int nodeValue, int location) {
Node node = new Node();
node.value = nodeValue;
if (head == null) {
createSinglyLinkedList(nodeValue);
return;
} else if (location == 0) {
node.next = head;
head = node;
} else if (location >= size) {
node.next = null;
tail.next = node;
tail = node;
} else {
Node tempNode = head;
int index = 0;
while (index < location - 1) {
tempNode = tempNode.next;
index++;
}
Node nextNode = tempNode.next;
tempNode.next = node;
node.next = nextNode;
}
size++;
}
遍历节点
实现过程
public void traverseSinglyLinkList() {
if (head == null) {
System.out.println("list does not exist");
} else {
Node tempNode = head;
for (int i = 0; i < size; i++) {
System.out.println(tempNode.value);
if (i != size - 1) {
System.out.println(" => ");
} tempNode = tempNode.next;
}
}
System.out.println("\n");
}
复杂度
- 时间复杂度:O(n)
- 空间复杂度:O(1)
寻找节点
实现过程
public boolean searchNode(int nodeValue) {
if (head != null) {
Node tempNode = head;
for (int i = 0; i < size; i++) {
if (tempNode.value == nodeValue) {
System.out.println("found at location " + i);
return true;
}
tempNode = tempNode.next;
}
}
System.out.println("Node not found");
return false;
}
复杂度
- 时间复杂度:O(n)
- 空间复杂度:O(1)
删除节点
从头部删除
节点数量等于1
实现过程
- Head节点设置为null
- Tail节点设置为null
节点数量大于1
实现过程
- Head节点的下一个引用为首个节点的下一个节点
从中间删除
实现过程
- 遍历链表到指定位置
- 指定位置节点的下一个引用为指定位置节点的下一个的下一个的节点
从末尾删除
节点数量等于1
实现过程
- Head节点设置为null
- Tail节点设置为null
节点数量大于1
实现过程
- 遍历链表到指定位置
- 指定位置节点的下一个引用为null
- Tail节点为指定位置的节点
代码实现
public void deletionOfNode(int location) {
if (head == null) {
System.out.println("list does not exist");
return;
}
if (location == 0) {
head = head.next;
size--;
if (size == 0) {
tail = null;
}
} else if (location >= size) {
Node tempNode = head;
for (int i = 0; i < size - 1; i++) {
tempNode = tempNode.next;
}
if (tempNode == head) {
head = tail = null;
size--;
return;
}
tempNode.next = null;
tail = tempNode;
size--;
} else {
Node tempNode = head;
for (int i = 0; i < location - 1; i++) {
tempNode = tempNode.next;
}
tempNode.next = tempNode.next.next;
size--;
}
}
删除整个链表
将首尾节点的引用置为null
总结
循环单链表
图示
特点
- 最后一个节点存储了首个节点的地址
创建过程
代码
public class CircularSinglyLinkedList {
public Node head;
public Node tail;
public int size;
public Node createCSLL(int nodeValue) {
head = new Node();
Node newNode = new Node();
newNode.value = nodeValue;
newNode.next = newNode;
head = newNode;
tail = newNode;
size = 1;
return newNode;
}
}
复杂度
- 时间复杂度:O(1)
- 空间复杂度:O(1)
插入节点
从头部插入
实现过程
- 新节点的下一个节点引用原来的第一个节点
- Head节点引用新加入的节点
- 最后一个节点的下一个节点引用新加入的节点
从中间插入
实现过程
从末尾插入
实现过程
- 原来的最后一个节点的下一个节点引用新加入的节点
- Tail节点引用新加入的节点
- 新加入的节点的下一个节点引用第一个节点
代码实现
public void insertCSLL(int nodeValue, int location) {
Node node = new Node();
node.value = nodeValue;
if (head == null) {
createCSLL(nodeValue);
} else if (location == 0) {
node.next = head;
head = node;
tail.next = head;
} else if (location >= size) {
tail.next = node;
tail = node;
tail.next = head;
} else {
Node tempNode = head;
int index = 0;
while (index < location - 1) {
tempNode = tempNode.next;
index++;
}
node.next = tempNode.next;
tempNode.next = node;
}
size++;
}
遍历节点
实现过程
public void traverseCSLL() {
if (head != null) {
Node tempNode = head;
for (int i = 0; i < size; i++) {
System.out.print(tempNode.value);
if (i != size - 1) {
System.out.print(" -> ");
}
tempNode = tempNode.next;
}
System.out.println("\n");
} else {
System.out.println("\n CSLL is not exist!");
}
}
复杂度
- 时间复杂度:O(n)
- 空间复杂度:O(1)
寻找节点
实现过程
public boolean searchNode(int nodeValue) {
if (head != null) {
Node tempNode = head;
for (int i = 0; i < size; i++) {
if (tempNode.value == nodeValue) {
System.out.println("Found node at: " + i);
return true;
}
tempNode = tempNode.next;
}
}
System.out.println("Node not found");
return false;
}
复杂度
- 时间复杂度:O(n)
- 空间复杂度:O(1)
删除节点
从头部删除
节点数量等于1
实现过程
- 首个节点的下一个引用为null
- Head节点为null
- Tail节点为null
节点数量大于1
实现过程
- Head节点引用首个节点的下一个节点
- 末尾节点的下一个引用为新的Head节点
从中间删除
实现过程
- 遍历链表到指定位置
- 指定位置的节点的下一个引用为指定位置节点的下一个的下一个节点
从末尾删除
节点数量等于1
实现过程
- 首个节点的下一个引用为null
- Head节点为null
- Tail节点为null
节点数量大于1
实现过程
- 遍历链表到指定位置
- 指定位置节点的下一个引用为首个节点
- Tail节点引用指定位置的节点
代码实现
public void deleteNode(int location) {
if (head == null) {
System.out.println("CSLL is not exist!");
return;
} else if (location == 0) {
head = head.next;
tail.next = head;
size--;
if (size == 0) {
tail = null;
head.next = null;
head = null;
}
} else if (location >= size) {
Node tempNode = head;
for (int i = 0; i < size - 1; i++) {
tempNode = tempNode.next;
}
if (tempNode == head) {
head.next = null;
tail = head = null;
size--;
return;
}
tempNode.next = head;
tail = tempNode;
size--;
} else {
Node tempNode = head;
for (int i = 0; i < location - 1; i++) {
tempNode = tempNode.next;
}
tempNode.next = tempNode.next.next;
size--;
}
}
复杂度
- 时间复杂度:O(n)
- 空间复杂度:O(1)
删除整个链表
public void deleteCSLL() {
if (head == null) {
System.out.println("CSLL is not exist!");
}else{
head = null;
tail.next = null;
tail = null;
System.out.println("CSLL has been deleted!");
}
}
总结
双链表
图示
特点
- 每一个节点都存储了前一个节点和后一个节点的地址
创建过程
代码
public class DoublyNode {
public int value;
public DoublyNode next;
public DoublyNode prev;
}
public class DoublyLinkedList {
public DoublyNode head;
public DoublyNode tail;
public int size;
public DoublyNode createDLL(int nodeValue) {
head = new DoublyNode();
DoublyNode node = new DoublyNode();
node.next = null;
node.prev = null;
node.value = nodeValue;
head = node;
tail = node;
size = 1;
return node;
}
}
复杂度
- 时间复杂度:O(1)
- 空间复杂度:O(1)
插入节点
从头部插入
实现过程
- 创建一个空白节点
- 放入数值
- 新创建的节点的下一个引用原来的头部节点
- 新创建的节点上一个引用为null
- 原来的节点的上一个引用更新为新创建的节点
- Head节点更新引用为新创建的节点
从中间插入
实现过程
- 创建一个空白节点
- 分配数值
- 循环遍历链表到达指定位置
- 更新新节点的下一个引用
- 更新新节点的上一个引用
- 更新前后两个原始节点的下一个引用和上一个引用为新节点
从末尾插入
实现过程
- 创建一个空白节点
- 分配数值
- 新节点的下一个引用更新为null
- 新节点的上一个引用更新为原来的末尾节点
- 原来的末尾节点的下一个引用更新为新节点
- Tail节点的下一个引用更新为新节点
代码实现
public void insertDLL(int nodeValue, int location) {
DoublyNode newNode = new DoublyNode();
newNode.value = nodeValue;
if (head == null) {
createDLL(nodeValue);
return;
} else if (location == 0) {
newNode.next = head;
newNode.prev = null;
head.prev = newNode;
head = newNode;
} else if (location >= size) {
newNode.next = null;
tail.next = newNode;
newNode.prev = tail;
tail = newNode;
} else {
DoublyNode tempNode = head;
int index = 0;
while (index < location - 1) {
tempNode = tempNode.next;
index++;
}
newNode.prev = tempNode;
newNode.next = tempNode.next;
tempNode.next = newNode;
newNode.next.prev = newNode;
}
size++;
}
正向遍历节点
实现过程
public void traverseDLL() {
if (head != null) {
DoublyNode tempNode = head;
for (int i = 0; i < size; i++) {
System.out.print(tempNode.value);
if (i != size - 1) {
System.out.print(" -> ");
}
tempNode = tempNode.next;
}
} else {
System.out.println("DLL is not exist!");
}
System.out.println("\n");
}
复杂度
- 时间复杂度:O(n)
- 空间复杂度:O(1)
反向遍历节点
实现过程
public void reverseTraverseDLL() {
if (head != null) {
DoublyNode tempNode = tail;
for (int i = 0; i < size; i++) {
System.out.print(tempNode.value);
if (i != size - 1) {
System.out.print(" <- ");
}
tempNode = tempNode.prev;
}
} else {
System.out.println("DLL is not exist!");
}
System.out.println("\n");
}
复杂度
- 时间复杂度:O(n)
- 空间复杂度:O(1)
寻找节点
实现过程
public boolean searchNode(int nodeValue) {
if (head != null) {
DoublyNode tempNode = head;
for (int i = 0; i < size; i++) {
if (tempNode.value == nodeValue) {
System.out.println("The Node is found at location: " + i);
return true;
}
tempNode = tempNode.next;
}
}
System.out.println("Node not found!");
return false;
}
复杂度
- 时间复杂度:O(n)
- 空间复杂度:O(1)
删除节点
从头部删除
节点数量等于1
实现过程
- Head节点为null
- Tail节点为null
节点数量大于1
实现过程
- Head节点引用首个节点的下一个节点
- 新的Head节点的上一个引用为null
从中间删除
实现过程
- 遍历链表到指定位置
- 指定位置节点的下一个引用为指定位置节点的下一个的下一个节点
- 新的指定位置节点的下一个引用的上一个引用为指定位置节点
从末尾删除
节点数量等于1
实现过程
- Head节点为null
- Tail节点为null
节点数量大于1
实现过程
- Tail节点的引用为末尾节点的上一个节点
- 新的Tail节点的下一个引用为null
代码实现
public void deleteNodeDLL(int location) {
if (head == null) {
System.out.println("DLL is not exist!");
} else if (location == 0) {
if (size == 1) {
head = null;
tail = null;
size--;
return;
} else {
head = head.next;
head.prev = null;
size--;
}
} else if (location >= size) {
DoublyNode tempNode = tail.prev;
if (size == 1) {
head = null;
tail = null;
size--;
return;
} else {
tempNode.next = null;
tail = tempNode;
size--;
}
} else {
DoublyNode tempNode = head;
for (int i = 0; i < location - 1; i++) {
tempNode = tempNode.next;
}
tempNode.next = tempNode.next.next;
tempNode.next.prev = tempNode;
size--;
}
}
复杂度
- 时间复杂度:O(n)
- 空间复杂度:O(1)
删除整个链表
public void deleteDLL() {
DoublyNode tempNode = head;
for (int i = 0; i < size; i++) {
tempNode.prev = null;
tempNode = tempNode.next;
} head = null;
tail = null;
size = 0;
System.out.println("DLL has been deleted!");
}
总结
循环双链表
图示
特点
- 最后一个节点存储了首个节点的地址
- 首个节点存储了最后一个节点的地址
创建过程
代码
public class CircularDoublyLinkedList {
public DoublyNode head;
public DoublyNode tail;
public int size;
public DoublyNode createCDLL(int nodeValue) {
head = new DoublyNode();
DoublyNode newNode = new DoublyNode();
newNode.value = nodeValue;
head = newNode;
tail = newNode;
newNode.prev = newNode;
newNode.next = newNode;
size = 1;
return newNode;
}
}
复杂度
- 时间复杂度:O(1)
- 空间复杂度:O(1)
插入节点
从头部插入
实现过程
- 创建一个空白节点
- 分配数值
- 新创建的节点的下一个引用为原来的第一个节点
- 新创建的节点的上一个引用为当前的末尾节点
- 末尾节点的下一个引用为新创建的节点
- 原来的第一个节点的上一个引用为新创建的节点
- Head节点为新创建的节点
从中间插入
实现过程
- 创建一个空白节点
- 分配数值
- 遍历当前链表到指定位置
- 新创建的节点的下一个引用为指定位置的下一个节点
- 新创建的节点的上一个引用为指定位置的上一个节点
- 指定位置的节点的下一个引用为新创建的节点
- 指定位置的下一个节点的上一个引用为新创建的节点
从末尾插入
实现过程
- 创建一个空白节点
- 分配数值
- 新创建的节点的下一个引用为当前首个节点
- 新创建的节点的上一个引用为原来的末尾节点
- 原来的末尾节点的下一个引用为新创建的节点
- Tail节点为新创建的节点
- 首个节点的上一个引用为新创建的节点
代码实现
public void insertNode(int nodeValue, int location) {
DoublyNode newNode = new DoublyNode();
newNode.value = nodeValue;
if (head == null) {
createCDLL(nodeValue);
return;
} else if (location == 0) {
newNode.next = head;
newNode.prev = tail;
head.prev = newNode;
tail.next = newNode;
head = newNode;
} else if (location >= size) {
newNode.next = head;
newNode.prev = tail;
head.prev = newNode;
tail.next = newNode;
tail = newNode;
} else {
DoublyNode tempNode = head;
int index = 0;
while (index < location - 1) {
tempNode = tempNode.next;
index++;
}
newNode.prev = tempNode;
newNode.next = tempNode.next;
tempNode.next = newNode;
newNode.next.prev = newNode;
}
size++;
}
复杂度
- 时间复杂度:O(n)
- 空间复杂度:O(1)
正向遍历节点
实现过程
public void traverseCDLL() {
if (head != null) {
DoublyNode tempNode = head;
for (int i = 0; i < size; i++) {
System.out.print(tempNode.value);
if (i != size - 1) {
System.out.print(" -> ");
}
tempNode = tempNode.next;
}
} else {
System.out.println("CDLL is not exist!");
}
System.out.println("\n");
}
复杂度
- 时间复杂度:O(n)
- 空间复杂度:O(1)
反向遍历节点
实现过程
public void reverseTraverseCDLL() {
if (head != null) {
DoublyNode tempNode = tail;
for (int i = 0; i < size; i++) {
System.out.println(tempNode.value);
if (i != size - 1) {
System.out.println(" <- ");
}
tempNode = tempNode.prev;
}
} else {
System.out.println("CDLL is not exist!");
}
}
复杂度
- 时间复杂度:O(n)
- 空间复杂度:O(1)
寻找节点
实现过程
public boolean searchNode(int nodeValue) {
if (head != null) {
DoublyNode tempNode = head;
for (int i = 0; i < size; i++) {
if (tempNode.value == nodeValue) {
System.out.println("The node is found at location: " + i);
return true;
}
tempNode = tempNode.next;
}
}
System.out.println("Node not found!");
return false;
}
复杂度
- 时间复杂度:O(n)
- 空间复杂度:O(1)
删除节点
从头部删除
节点数量等于1
实现过程
- 删除当前节点自身的两个引用,即上一个引用和下一个引用
- 删除Head节点
- 删除Tail节点
节点数量大于1
实现过程
- 将末尾节点的下一个引用更新为首个节点的下一个节点
- Head节点更新为首个节点的下一个节点
- 首个节点的下一个节点的上一个引用更新为末尾节点
从中间删除
实现过程
- 遍历链表到指定位置
- 指定位置节点的下一个引用更新为下一个的下一个的节点
- 指定位置节点的下一个的下一个的节点的上一个引用更新为指定位置的节点
从末尾删除
节点数量等于1
实现过程
- 删除当前节点自身的两个引用,即上一个引用和下一个引用
- 删除Head节点
- 删除Tail节点
节点数量大于1
实现过程
- Tail节点更新为原来末尾节点的上一个节点
- 当前末尾节点的下一个引用更新为首个节点
- 首个节点的上一个引用更新为当前末尾节点
代码实现
public void deleteNode(int location) {
if (head == null) {
System.out.println("CDLL is not exist!");
return;
} else if (location == 0) {
if (size == 1) {
head.prev = null;
head.next = null;
head = null;
tail = null;
size--;
return;
} else {
head = head.next;
head.prev = tail;
tail.next = head;
size--;
}
} else if (location >= size) {
if (size == 1) {
head.prev = null;
head.next = null;
head = null;
tail = null;
size--;
return;
} else {
tail = tail.prev;
tail.next = head;
head.prev = tail;
size--;
}
} else {
DoublyNode tempNode = head;
for (int i = 0; i < location - 1; i++) {
tempNode = tempNode.next;
}
tempNode.next = tempNode.next.next;
tempNode.next.prev = tempNode;
size--;
}
}
复杂度
- 时间复杂度:O(n)
- 空间复杂度:O(1)
删除整个链表
实现过程
- 末尾节点的下一个引用为null
- 首个节点的上一个引用为null
- 遍历链表
- 删除每个节点的上一个引用
- Head节点的引用为null
- Tail节点的引用为null
public void deleteCDLL() {
DoublyNode tempNode = head;
for (int i = 0; i < size; i++) {
tempNode.prev = null;
tempNode = tempNode.next;
} head = null;
tail.next = null;
tail = null;
System.out.println("The CDLL has been deleted!");
}
复杂度
- 时间复杂度:O(n)
- 空间复杂度:O(1)