单向循环链表
单向循环链表的尾节点会指向首节点形成循环
单向循环链表的设计
import com.company.AbstractList;
public class SingleCircleLinkedList<E> extends AbstractList<E> {
private Node<E> first; // 首节点
// 内部类
private static class Node<E> {
E element; // 当前节点的元素
Node<E> next; // 下一个节点
public Node(E element, Node<E> next) {
this.element = element;
this.next = next;
}
}
@Override
public void clear() {
size = 0;
first = null; // 首节点为null,后面的所有节点也就都会被释放了
}
@Override
public E get(int index) {
return node(index).element;
}
@Override
public E set(int index, E element) {
Node<E> node = node(index);
E old = node.element;
node.element = element; // 拿到当前节点元素并覆盖
return old;
}
@Override
public void add(int index, E element) {
rangeCheckForAdd(index); // 先判断边界元素是否合格
if (index == 0) {
Node<E> newFirst = new Node<>(element, first);
// 拿到最后一个节点(让最后一个节点的next指向首节点)
// 如果只有一个节点,那么该节点既是首节点,也是尾节点,next指向自己
Node<E> last = (size == 0) ? newFirst : node(size - 1);
last.next = newFirst;
first = newFirst;
} else { // 如果是其他元素,找到其上一个节点来创建
Node<E> prev = node(index - 1);
prev.next = new Node<>(element, prev.next);
}
size++; // 增加容量
}
@Override
public E remove(int index) {
rangeCheck(index);
Node<E> node = first;
if (index == 0) { // 如果删除的是首节点
if (size == 1) { // 如果只有一个元素,那么直接置空
first = null;
} else {
// 让最后一个节点的next指向新的节点
Node<E> last = node(size - 1);
first = first.next;
last.next = first;
}
} else { // 如果是其他元素,找到其上一个节点,将其next指向要删除的节点的下一个节点
Node<E> prev = node(index - 1);
node = prev.next;
prev.next = node.next;
}
size--; // 减少容量
return node.element;
}
@Override
public int indexOf(E element) {
Node<E> node = first;
if (element == null) {
for (int i = 0; i < size; i++) {
if (node.element == null) return i;
node = node.next;
}
} else {
for (int i = 0; i < size; i++) {
if (element.equals(node.element)) return i;
node = node.next;
}
}
return ELEMENT_NOT_FOUND;
}
/**
* 获取index位置对应的节点对象
* @param index
* @return
*/
private Node<E> node(int index) {
rangeCheck(index); // 首先判断边界元素是否合格
// 从首节点一直循环找到index的位置
Node<E> node = first;
for (int i = 0; i < index; i++) {
node = node.next;
}
return node;
}
@Override
public String toString() {
StringBuilder string = new StringBuilder();
string.append("size=").append(size).append(", [");
Node<E> node = first;
for (int i = 0; i < size; i++) {
if (i != 0) {
string.append(", ");
}
string.append(node.element);
node = node.next;
}
string.append("]");
return string.toString();
}
}
设计点的详细讲解:
1.添加元素时,要注意首节点的逻辑
如果是空链表添加元素,那么添加的节点既是首节点,也是尾节点,该节点的next
指向的也是自己
如果已有元素,那么插入首节点位置需要将尾节点的next
指向新的首节点
注意先用一个临时变量保存新的首节点,以防止查找尾节点时的first
更改造成查找有误
@Override
public void add(int index, E element) {
rangeCheckForAdd(index);
if (index == 0) {
Node<E> newFirst = new Node<>(element, first);
Node<E> last = (size == 0) ? newFirst : node(size - 1);
last.next = newFirst;
first = newFirst;
} else {
Node<E> prev = node(index - 1);
prev.next = new Node<>(element, prev.next);
}
size++;
}
2.删除元素时,也是需要考虑首节点的问题
如果链表只有一个元素,那么删除该节点就直接让first
指向null
就可以了
如果链表中不止一个元素,那么删除首节点后需要将尾节点的next
指向新的首节点
@Override
public E remove(int index) {
rangeCheck(index);
Node<E> node = first;
if (index == 0) { // 如果删除的是首节点
if (size == 1) { // 如果只有一个元素,那么直接置空
first = null;
} else {
// 让最后一个节点的next指向新的节点
Node<E> last = node(size - 1);
first = first.next;
last.next = first;
}
} else { // 如果是其他元素,找到其上一个节点,将其next指向要删除的节点的下一个节点
Node<E> prev = node(index - 1);
node = prev.next;
prev.next = node.next;
}
size--; // 减少容量
return node.element;
}
双向循环链表
双向循环链表的首节点的next
指向的是尾节点,尾节点的prev
指向的是首节点,这样形成循环链表
双向循环链表的设计
public class CircleLinkedList<E> extends AbstractList<E> {
private Node<E> first; // 首节点
private Node<E> last; // 尾节点
private Node<E> current; // 指向某个节点
// 内部类
private static class Node<E> {
E element; // 当前节点的元素
Node<E> prev; // 上一个节点
Node<E> next; // 下一个节点
public Node(Node<E> prev, E element, Node<E> next) {
this.prev = prev;
this.element = element;
this.next = next;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if (prev != null) {
sb.append(prev.element);
} else {
sb.append("null");
}
sb.append("_").append(element).append("_");
if (next != null) {
sb.append(next.element);
} else {
sb.append("null");
}
return sb.toString();
}
}
// 让current指向头结点
public void reset() {
current = first;
}
// 让current向后执行一步
public E next() {
if (current == null) return null;
current = current.next;
return current.element;
}
// 删除current指向的节点
public E remove() {
if (current == null) return null;
Node<E> next = current.next;
E element = remove(current);
if (size == 0) {
current = null;
} else {
current = next;
}
return element;
}
@Override
public void clear() {
size = 0;
first = null;
last = null;
}
@Override
public E get(int index) {
return node(index).element;
}
@Override
public E set(int index, E element) {
Node<E> node = node(index);
E old = node.element;
node.element = element; // 拿到当前节点元素并覆盖
return old;
}
@Override
public void add(int index, E element) {
rangeCheckForAdd(index);
if (index == size) { // 往最后面添加元素
Node<E> oldLast = last;
last = new Node<>(oldLast, element, first);
if (oldLast == null) { // 如果链表没有元素的时候,也会进来这里
first = last; // first、last也都指向自己
first.next = first; // 自己的prev和next都指向的自己
first.prev = first;
} else {
oldLast.next = last;
first.prev = last;
}
} else { // 从前面添加元素
Node<E> next = node(index);
Node<E> prev = next.prev;
Node<E> node = new Node<>(prev, element, next);
next.prev = node;
prev.next = node;
if (next == first) { // index == 0,也就是插入到第一个节点的位置
first = node;
}
}
size++;
}
@Override
public E remove(int index) {
rangeCheck(index);
return remove(node(index));
}
private E remove(Node<E> node) {
if (size == 1) { // 如果只有一个元素,全都指向空
first = null;
last = null;
} else {
Node<E> prev = node.prev;
Node<E> next = node.next;
prev.next = next;
next.prev = prev;
if (node == first) { // index == 0,删除的是首节点
first = next;
}
if (node == last) { // index == size - 1,删除的是尾节点
last = prev;
}
}
size--;
return node.element;
}
@Override
public int indexOf(E element) {
if (element == null) {
Node<E> node = first;
for (int i = 0; i < size; i++) {
if (node.element == null) return i;
node = node.next;
}
} else {
Node<E> node = first;
for (int i = 0; i < size; i++) {
if (element.equals(node.element)) return i;
node = node.next;
}
}
return ELEMENT_NOT_FOUND;
}
/**
* 获取index位置对应的节点对象
* @param index
* @return
*/
private Node<E> node(int index) {
rangeCheck(index);
if (index < (size >> 1)) { // 分出两部分来查找,从前往后找
Node<E> node = first;
for (int i = 0; i < index; i++) {
node = node.next;
}
return node;
} else { // 从后往前找
Node<E> node = last;
for (int i = size - 1; i > index; i--) {
node = node.prev;
}
return node;
}
}
@Override
public String toString() {
StringBuilder string = new StringBuilder();
string.append("size=").append(size).append(", [");
Node<E> node = first;
for (int i = 0; i < size; i++) {
if (i != 0) {
string.append(", ");
}
string.append(node);
node = node.next;
}
string.append("]");
return string.toString();
}
}
设计点的详细讲解:
1.添加元素时,需要注意边界元素的逻辑
如果添加的是最后一个元素的位置,那么就要将首尾节点的next
和prev
指向新的节点,并且将last指向新的节点
还要考虑空链表的情况,这时first、last
指向的都是新的节点,并且新节点的next、prev
也都是指向自己
如果添加的是首节点的位置,也要对应的将first
指向新的节点
@Override
public void add(int index, E element) {
rangeCheckForAdd(index);
if (index == size) { // 往最后面添加元素
Node<E> oldLast = last;
last = new Node<>(oldLast, element, first);
if (oldLast == null) { // 如果链表没有元素的时候,也会进来这里
first = last; // first、last也都指向自己
first.next = first; // 自己的prev和next都指向的自己
first.prev = first;
} else {
oldLast.next = last;
first.prev = last;
}
} else { // 从前面添加元素
Node<E> next = node(index);
Node<E> prev = next.prev;
Node<E> node = new Node<>(prev, element, next);
next.prev = node;
prev.next = node;
if (next == first) { // index == 0,也就是插入到第一个节点的位置
first = node;
}
}
size++;
}
2.删除元素时,同样需要考虑边界元素的逻辑
如果链表中只有一个元素时,那么就直接将first、last
为null
就可以了
如果删除的是首尾节点的位置,那么对应的要将上下节点的next、prev
做更改,还有first、last
更改指向
private E remove(Node<E> node) {
if (size == 1) { // 如果只有一个元素,全都指向空
first = null;
last = null;
} else {
Node<E> prev = node.prev;
Node<E> next = node.next;
prev.next = next;
next.prev = prev;
if (node == first) { // index == 0,删除的是首节点
first = next;
}
if (node == last) { // index == size - 1,删除的是尾节点
last = prev;
}
}
size--;
return node.element;
}
3.增加一个成员变量current
及对应的函数使循环链表更方便操控
private Node<E> current; // 指向某个节点
// 让current指向头结点
public void reset() {
current = first;
}
// 让current向后执行一步
public E next() {
if (current == null) return null;
current = current.next;
return current.element;
}
// 删除current指向的节点
public E remove() {
if (current == null) return null;
Node<E> next = current.next;
E element = remove(current);
if (size == 0) {
current = null;
} else {
current = next;
}
return element;
}
静态链表
前面所学习的链表,是依赖于指针(引用)实现的,有些编程语言是没有指针的,例如早期的BASIC、FORTRAN语言
没有指针的情况下,我们如何实现链表呢?
可以通过数组来模拟链表,称为静态链表
静态链表的实现如下:
- 数组的每个元素存放两个数据:真实值和下一个元素的索引
- 数组0位置存放的是头结点信息,如下图所示
- 最后一个节点元素存放的索引可以是-1,来表示尾节点
- 如果要是做成循环链表,那尾节点的索引可以存放的是首节点的索引
其真实顺序如下图所示
如果数组的每个元素只能存放1个数据要怎么做?
第一种方法,可以考虑用两个数组来做,一个数组存放真实数据,一个数组存放索引,利用数组本身的自己的索引值来相互关联
第二种方法,可以做成二维数组,数组里每个元素又是一个数组,然后元素数组里对应放着两个元素,一个真实数据,一个索引
练习题
1.利用循环链表来实现约瑟夫问题
约瑟夫问题(Josephus Problem)的场景如下
8个人围成一圈,然后从第一个人开始,每数到3的人就要被杀死,这样一直循环,只有最后一个人能存活下来
从1开始,执行杀死的顺序为:3, 6, 1, 5, 2, 8, 4
最后存活的是7
这个示例我们刚好可以用双向循环链表来实现
static void josephus() {
CircleLinkedList<Integer> list = new CircleLinkedList<>();
for (int i = 1; i <= 8; i++) {
list.add(i);
}
// 指向头结点(指向1)
list.reset();
while (!list.isEmpty()) {
list.next();
list.next();
System.out.println(list.remove());
}
}
public static void main(String[] args) {
josephus();
}
// 执行结果:3, 6, 1, 5, 2, 8, 4,7
实现方案有很多,这里我们就不一一概述了
2.删除链表中的节点
题目概述
题目链接: leetcode-cn.com/problems/de…
题解:
最终目的只要将第三个节点的值为1换掉就可以了,所以可以将第四个节点的值赋给第三个节点,然后第三个节点的next指向第五个节点就可以了,第四个节点就会被释放掉了
public class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
}
}
public class Solution {
public void deleteNode(ListNode node) {
node.val = node.next.val;
node.next = node.next.next;
}
}
3.反转一个单链表
题目概述
题目链接: leetcode-cn.com/problems/re…
题解(递归方式)
将首节点传进去,通过next
会递归调用到最后一个不为null
的节点作为新的首节点,而最初的首节点要是想要反转就要通过最初的第二个节点,也就是反转后的最后一位的节点,将其next指向最初的首节点,形成了反转;最后将最初的首节点,也就是反转后的最后一位节点的next
指向null
就可以了,这样就通过递归形成了反转
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; }
}
public class Solution {
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) return head;
ListNode newHead = reverseList(head.next);
head.next.next = head;
head.next = null;
return newHead;
}
}
题解(迭代方式)
先用一个临时变量保存首节点的next
,也就是第二个节点,第二个节点指向新的节点,也就是null
;再将首节点给了新节点,首节点再去指向临时变量,也就是第二个节点,这样就形成了新的链表;这种方式叫头插法,一直循环进行置换
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; }
}
public class Solution {
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) return head;
ListNode newHead = null;
while (head != null) {
ListNode tmp = head.next;
head.next = newHead;
newHead = head;
head = tmp;
}
return newHead;
}
}
4.判断链表中是否有环
题目概述
题目链接: leetcode-cn.com/problems/li…
题解
是否形成环形链表主要看快慢指针是否会相遇,就好比两个人在操场跑圈,一个跑的快,一个跑的慢,那么最终肯定会相遇。这道题的思路也是如此
class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
next = null;
}
}
public class Solution {
public boolean hasCycle(ListNode head) {
if (head == null || head.next == null) return false;
ListNode slow = head;
ListNode fast = head.next;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) return true;
}
return false;
}
}