一、循环链表的应用场景
1、拉丁方阵问题
拉丁方阵是一种n×n的方阵,方阵中恰有n种不同的元素,每种元素恰有n个,并且每种元素在一行和一列中 恰好出现一次。比如:
1 2 3
2 3 1
3 1 2
思路:很简单,直接循环链表遍历即可,每遍历完一行都将current指向下一个对象。
final int N = 10; //拉丁方阵的阶数
int i = -1, j = -1;
MyList myList = new MyList();
for (i = 0; i < N; i++) {
myList.add(i + 1);
}
myList.reset();
Node node = myList.current;
for (i = 0; i < N; i++) {
for (j = 0; j < N; j++) {
System.out.print(node.element + " ");
node = node.next;
}
System.out.println();
node = node.next;
}
2、约瑟夫问题
约瑟夫问题是个有名的问题:N个人围成一圈,从第一个开始报数,第M个将被杀掉,最后剩下一个,其余人都将被杀掉。求解:所有人被杀掉的顺序?例如N=6,M=5,被杀掉的顺序是:5,4,6,2,3,1。
思路:很简单,直接按人数依次构建循环链表,从1开始数,current每次往后挪N-1单位,之后把那个结点从链表中删除,current指向后一个结点。直到链表为空。
final int N = 6; //总人数
final int M = 5; //被杀掉的序号
MyList myList = new MyList();
Node node = myList.current;
for (int i = 1; i <= N; i++)
myList.add(i);
myList.reset();
while (myList.size != 0) {
for (int i = 0; i < M - 1; i++)
node = myList.next();
if (node == null)
return;
System.out.println(node.element);
myList.remove(node.element);
}
3、魔术师发牌问题
一位魔术师掏出一叠扑克牌,魔术师取出其中13张黑桃,洗好后,把牌面朝下。说:“我不看牌,只数一数就能知道每张牌是什么?”
魔术师口中念一,将第一张牌翻过来看正好是A;魔术师将黑桃A放到桌上,继续数手里的余牌,第二次数1,2,将第一张牌放到这叠牌的下面,将第二张牌翻开,正好是黑桃2,也把它放在桌子上。第三次数1,2,3,前面二张牌放到这叠牌的下面,取出第三张牌,正好是黑桃3,这样依次将13张牌翻出,全部都准确无误。求解:魔术师手中牌的原始顺序是什么样子的?
思路:很简单,初始化一个包含13个结点的循环链表,先把第一个结点赋值,之后都是将current往后挪动魔术师喊得数个单位,已经赋值过的结点就跳过。直到链表中所有结点都被赋值过。这个问题其实就是约瑟夫的逆问题。
public Node next() {
if (current == null)
return null;
current = current.next;
if (current.element != -1) //设定初始值, 这里初始化为-1
next();
return current;
}
public static void main(String[] args) {
int i = -1;
MyList myList = new MyList();
for (i = 0; i < 13; i++) {
myList.add(-1);
}
myList.reset();
Node node = myList.current;
for (i = 1; i <= 13; i++) {
if (i == 1) {
myList.set(node, i);
continue;
}
for (int j = 1; j <= i; j++) {
node = myList.next();
}
myList.set(node, i);
}
}
二、单向循环链表
1、单向循环链表明细
单向循环链表其实就是尾结点有指向头结点的指针,一般链表尾结点指向的都是空。
添加结点分为头部添加和普通情况(尾部不需要特殊处理因为尾部有指向头结点的指针)。头部添加结点的时候还需要对第一次在头部插入结点情况和其他头部插入情况分开讨论,head.next = head
。
删除结点分为头部删除和普通情况(尾部不需要特殊处理是因为尾部有指向头结点的指针)。头部删除的时候还需要对只有一个结点进行头部删除和其他头部删除情况分开讨论。
public class CircleLinkedList<E> extends AbstractList<E> {
private Node<E> head;
private Node<E> current;
public static class Node<E> {
E element;
Node<E> next;
public Node(E element, Node<E> next) {
this.element = element;
this.next = next;
}
@Override
public String toString() {
return "Node{" +
"element=" + element +
", next=" + next.element +
'}';
}
}
@Override
public void clear() {
this.size = 0;
head = null; //清除所有结点的内存空间
}
@Override
public void add(int index, E element) {
//链表注意头和尾
if (index == 0) { //头部插入
Node<E> newNode = new Node<E>(element, head);
if (size == 0 || head == null) { //插入第一个结点的时候
head = newNode;
head.next = head;
} else {
Node<E> tail = getNode(size - 1);
head = newNode;
tail.next = head;
}
} else { //普通情况, 尾部插入和普通情况一样, 因为尾部有指针指向头结点
rangeCheckForAdd(index);
Node<E> preNode = getNode(index - 1);
Node<E> newNode = new Node<E>(element, preNode.next);
preNode.next = newNode;
}
size++;
}
@Override
public void remove(int index) {
rangeCheck(index);
Node<E> preNode = head;
if (index == 0) { //头部删除
if (size == 1) {
head = null;
} else {
head = head.next;
Node<E> tail = getNode(size - 1 - 1); //head改变了, 寻找尾结点的参数也需要改变, 或者在改变head之前查
tail.next = head;
//等价写法1
// Node<E> tail = getNode(size - 1); head = head.next; tail.next = head;
//等价写法2
// preNode=preNode.next; Node<E> tail = getNode(size - 1); tail.next=preNode; head=preNode;
}
} else { //普通情况, 尾部删除和普通情况一样, 因为尾部有指针指向头结点
preNode = getNode(index - 1);
preNode.next = preNode.next.next;
}
size--;
}
public void reset() {
current = head;
}
public void next() {
if (current == null) return;
current = current.next;
}
/**
* 删除current结点并返回被删除结点的数据, 同时将current结点后移
*
* @return
*/
public E remove() {
if (current == null) return null;
E element = current.element;
int index = indexOf(element);
rangeCheck(index);
Node<E> preNode = head;
if (index == 0) { //头部删除
if (size == 1) {
head = null;
} else {
Node<E> tail = getNode(size - 1);
head = head.next;
tail.next = head;
}
} else { //普通情况, 尾部删除和普通情况一样, 因为尾部有指针指向头结点
preNode = getNode(index - 1);
preNode.next = preNode.next.next;
}
size--;
current = current.next;
return element;
}
@Override
public E set(int index, E element) {
rangeCheck(index);
Node<E> temp = getNode(index);
E old = temp.element;
temp.element = element;
return old;
}
@Override
public E get(int index) {
rangeCheck(index);
Node<E> temp = getNode(index);
return temp.element;
}
public Node<E> getNode(int index) {
Node<E> temp = head;
for (int i = 0; i < index; i++) {
temp = temp.next;
}
return temp;
}
@Override
public int indexOf(E element) {
Node<E> temp = head;
for (int i = 0; i < size; i++) {
if (temp.element.equals(element))
return i;
temp = temp.next;
}
return ELEMENT_NOT_FOUND;
}
@Override
public String toString() {
StringBuilder str = new StringBuilder();
str.append("size=").append(size);
if (size != 0) {
Node temp = head;
str.append(", [");
for (int i = 0; i < size; i++) {
str.append(temp);
if (i != size - 1) {
str.append(",");
}
temp = temp.next;
}
str.append("]");
}
return str.toString();
}
public static void main(String[] args) {
CircleLinkedList<Object> list = new CircleLinkedList<>();
list.add(new Person(23, "小杰"));
list.add(0, 66);
list.add(99.9);
list.add(list.size(), 100);
list.remove(0);
list.remove(list.size() - 1);
list.set(1, 666);
System.out.println(list.get(0));
System.out.println(list);
}
}
2、测试结果
三、双向循环链表
1、双向循环链表明细
双向循环链表其实类似双向链表,不过特殊之处就是尾结点有指向头结点的指针,头结点也有指向尾结点的指针。
双向循环链表和双向链表一样,在之前单向普通链表讨论情况的基础上,在头部插入的时候还需要对在头部插入第一个结点和其他头部插入情况分开讨论;在头部删除的时候也是一样,需要对只有一个结点进行头部删除和其他头部删除情况分开讨论。
绕的一批。。。
public class DoubleCircleLinkedList<E> extends AbstractList<E> {
private Node<E> head;
private Node<E> tail;
private Node<E> current;
public static class Node<E> {
Node<E> last;
E element;
Node<E> next;
public Node(Node<E> last, E element, Node<E> next) {
this.last = last;
this.element = element;
this.next = next;
}
@Override
public String toString() {
StringBuilder str = new StringBuilder();
str.append("Node{last=");
if (last != null)
str.append(last.element);
else
str.append("null");
str.append(", element=");
str.append(element);
str.append(", ");
str.append("next=");
if (next != null)
str.append(next.element);
else
str.append("null");
str.append("}");
return str.toString();
}
@Override
protected void finalize() throws Throwable {
System.out.println("Node挂了");
}
}
@Override
public void clear() {
this.size = 0;
//JVM中只要有空间 没有被GC Roots对象指向, 就会被销毁
head = null;
tail = null;
}
@Override
public void add(int index, E element) {
rangeCheckForAdd(index);
Node<E> node = getNode(index);
if (index == 0) { //在头部插入
Node<E> newNode = new Node<E>(tail, element, node); //双向循环链表头结点要指向尾结点
if (node == null || size == 0) { //插入第一个结点的时候
head = newNode;
tail = newNode;
head.last = tail;
tail.next = head; //头部插入完还要修改尾结点指向新的头结点
} else {
node.last = newNode;
head = newNode;
tail.next = head; //头部插入完还要修改尾结点指向新的头结点
}
} else if (index == size) { //尾部插入
Node<E> newNode = new Node<E>(tail, element, head); //双向循环链表尾结点要指向头结点
tail.next = newNode;
tail = newNode;
head.last = tail; //尾部插入完还要修改头结点指向新的尾结点
} else { //普通情况
Node<E> former = node.last;
Node<E> newNode = new Node<E>(former, element, node);
former.next = newNode;
node.last = newNode;
}
size++;
}
@Override
public void remove(int index) {
rangeCheck(index);
Node<E> node = getNode(index);
Node<E> former = node.last;
Node<E> latter = node.next;
if (index == 0) { //头部删除
if (size == 1) { //删除的时候只有一个结点
head = null;
tail = null;
} else {
latter.last = tail; //双向循环链表头结点要指向尾结点
head = latter;
tail.next = head; //双向循环链表尾结点要指向头结点
}
} else if (index == size - 1) { //尾部删除
former.next = head; //双向循环链表尾结点要指向头结点
tail = former;
head.last = tail; //双向循环链表头结点要指向尾结点
} else {
//普通情况
former.next = latter;
latter.last = former;
}
size--;
}
public void reset() {
current = head;
}
public void next() {
if (current == null) return;
current = current.next;
}
/**
* 删除current结点并返回被删除结点的数据, 同时将current结点后移
*
* @return
*/
public E remove() {
if (current == null) return null;
E element = current.element;
int index = indexOf(element);
rangeCheck(index);
Node<E> node = getNode(index);
Node<E> former = node.last;
Node<E> latter = node.next;
if (index == 0) { //头部删除
if (size == 1) { //删除的时候只有一个结点
head = null;
tail = null;
} else {
latter.last = tail; //双向循环链表头结点要指向尾结点
head = latter;
tail.next = head; //双向循环链表尾结点要指向头结点
}
} else if (index == size - 1) { //尾部删除
former.next = head; //双向循环链表尾结点要指向头结点
tail = former;
head.last = tail; //双向循环链表头结点要指向尾结点
} else {
//普通情况
former.next = latter;
latter.last = former;
}
size--;
current = current.next;
return element;
}
@Override
public E set(int index, E element) {
rangeCheck(index);
Node<E> temp = getNode(index);
E old = temp.element;
temp.element = element;
return old;
}
@Override
public E get(int index) {
rangeCheck(index);
return getNode(index).element;
}
public Node<E> getNode(int index) {
Node<E> temp = null;
if (index < this.size >> 1) {
temp = head; //从头遍历
for (int i = 0; i < index; i++) {
temp = temp.next;
}
} else {
temp = tail; //从尾遍历
for (int i = 0; i < size - 1 - index; i++) {
temp = temp.last;
}
}
return temp;
}
@Override
public int indexOf(E element) {
Node<E> temp = head;
for (int i = 0; i < size; i++) {
if (temp.element.equals(element))
return i;
temp = temp.next;
}
return ELEMENT_NOT_FOUND;
}
@Override
public String toString() {
StringBuilder str = new StringBuilder();
str.append("size=").append(size);
str.append(", head=");
str.append(head.element);
str.append(", tail=");
str.append(tail.element);
if (size != 0) {
Node temp = head;
str.append(", [");
for (int i = 0; i < size; i++) {
str.append(temp.toString());
if (i != size - 1) {
str.append(",");
}
temp = temp.next;
}
str.append("]");
}
return str.toString();
}
public static void main(String[] args) {
DoubleCircleLinkedList<Object> list = new DoubleCircleLinkedList<>();
list.add(new Person(23, "小杰"));
list.add(0, 66);
list.add(99.9);
list.add(list.size(), 100);
list.remove(0);
list.remove(list.size() - 1);
list.set(1, 666);
System.out.println(list.get(0));
System.out.println(list);
}
}