1. 单向循环链表的定义:如果把单链表的最后一个节点的指针指向链表头部,而不是指向null,那么就构成了一个单向循环链表。如图:
2. 单向循环链表的实现:单向循环链表实现与单向链表的实现相似,同样是实现线性表List的接口。
//单向循环链表的实现
public class LinkedSinglyCircularList<E> implements List<E> {
/*将数据封装成节点 节点包括两部分 数据域和指针域
数据域中存放的是数据内容
指针域中存放的是当前节点的下一跳节点的地址 即指针域指向当前节点的下一跳节点
因为是单向循环链表 因此尾节点的下一跳始终指向头节点
*/
class Node{ //封装数据域和指针域的类
E data; //表示节点的数据域
Node next; //表示节点的指针域
public Node(){ //节点的无参构造
this(null, null);
}
public Node(E element){ //节点的带参构造 参数为数据的内容
this(element, null);
}
public Node(E element, Node next){ //节点的带参构造 参数为数据的内容和下一个节点的地址
this.data = element;
this.next = next;
}
//格式化节点输出时的格式 即输出数据内容即可
@Override
public String toString() {
return data.toString();
}
}
//表示头指针 指向链表中的头节点
private Node head;
//表示尾指针 指向链表中的尾节点
private Node tail;
//表示链表中有效元素的个数
private int size;
public LinkedSinglyCircularList(){ //单向链表的无参构造 为head tail size初始化
head = null;
tail = null;
size = 0;
}
public LinkedSinglyCircularList(E[] arr){ //单向链表的带参构造 参数为一个数组 将数组中的元素依次添加到链表中
if(arr == null || arr.length == 0){
throw new IllegalArgumentException("arr is null");
}
for (int i = 0; i < arr.length; i++){
add(arr[i]);
}
}
//向链表的表尾追加元素
@Override
public void add(E element) {
add(size, element);
}
//向链表的指定位置添加元素
@Override
public void add(int index, E element) {
if(index < 0 || index > size){ //判断索引是否存在
throw new IllegalArgumentException("add index out of range");
}
Node node = new Node(element); //创建节点对象
if(size == 0) { //当表空时 添加元素 只需头尾指针同时指向要添加的节点 尾节点的下一跳指向头节点即可
head = node;
tail = node;
tail.next = head;
}else if(index == 0) { //向表头添加元素
/*
先要将要添加节点的下一跳指向头节点 再将头指针指向要添加的节点 使尾节点下一跳指向头节点
*/
node.next = head;
head = node;
tail.next = head;
}else if(index == size) { //向表尾添加元素
/*
先将尾节点的下一跳赋值给要添加节点的下一跳
先将尾节点的下一跳指向要添加的节点 再将尾指针指向要添加的节点
*/
node.next = tail.next;
tail.next = node;
tail = node;
}else { //向表中间添加元素
/*
先遍历到要添加的位置的前驱 将前驱节点的下一跳赋值给要添加节点的下一跳 前驱节点的下一跳指向要添加的节点
*/
Node p = head;
for(int i = 0; i < index - 1; i++){
p = p.next;
}
node.next = p.next;
p.next = node;
}
size++;
}
//删除链表中的指定元素
@Override
public void remove(E element) {
remove(indexOf(element));
}
//删除链表中的指定位置的元素 并返回要删除元素的内容
@Override
public E remove(int index) {
if(index < 0 || index >= size){ //判断索引是否存在
throw new IllegalArgumentException("remove index out of range");
}
E ele = null;
if(size == 1) { //当表中只有一个元素时删除
/*
当表中只有一个元素时删除:只需要先取出那一个元素 再将头指针head 尾指针tail同时指向null即可
*/
ele = head.data;
head = null;
tail = null;
}else if(index == 0) { //删除表头元素
/*
删除表头元素:
1.定义一个新的节点使其也指向头节点
2.取出这个新节点的元素
3.更新头指针 使其指向当前头节点的下一个节点
4.使新节点的指针域指向null
5.最后将尾节点的下一跳指向新的头节点
*/
Node n = head;
ele = n.data;
head = n.next;
n.next = null;
tail.next = head;
}else if(index == size - 1) { //删除表尾元素
/*
删除表尾元素:
1.先定义一个新节点p 使其开始指向头节点 遍历链表 直至新节点p指向链表的尾节点前驱节点
2.取出尾节点的数据
3.将尾节点的下一跳赋值给p节点的下一跳
4.更新尾指针 是尾指针指向p节点
*/
Node p = head;
while (p.next != tail){
p = p.next;
}
ele = tail.data;
p.next = tail.next;
tail = p;
}else { //删除表中间元素
/*
删除表中间元素:
1.先定义一个新节点p 使其开始指向头节点 遍历链表 直至遍历到指定索引的前驱节点的索引 每轮更新p节点
2.再定义一个节点为要删除的节点n 即当前p节点的下一跳节点 取出要删除的节点n的内容
3.使p节点的下一跳重新指向要删除的节点n的下一跳
4.使要删除的节点n的下一跳指向null
*/
Node p = head;
for(int i = 0; i < index - 1; i++){
p = p.next;
}
Node n = p.next;
ele = n.data;
p.next = n.next;
n.next = null;
}
size--;
return ele;
}
//获取指定索引处的节点的值
@Override
public E get(int index) {
if(index < 0 || index >= size){ //判断索引是否存在
throw new IllegalArgumentException("get index out of range");
}
if(index == 0){ //获取表头元素 即头指针指向的节点的值
return head.data;
}else if(index == size - 1){ //获取表尾元素 即尾指针指向的节点的值
return tail.data;
}else{ //获取表中间某一索引的值
/*
获取表中间某一索引的值:定义一个新节点p使其指向头节点 从表头开始遍历 遍历到索引位置 每次更新p节点
最后p节点就是要获取节点 返回该节点的值即可
*/
Node p = head;
for(int i = 0; i < index; i++){
p = p.next;
}
return p.data;
}
}
//修改指定位置的节点的值 并返回要修改节点的值
@Override
public E set(int index, E element) {
if(index < 0 || index >= size){ //判断索引是否存在
throw new IllegalArgumentException("set index out of range");
}
E ele = null;
if(index == 0){ //修改表头节点的值 即先获取表头结点的值 再将表头结点的值修改为指定的值element即可
ele = head.data;
head.data = element;
}else if(index == size - 1){ //修改表尾节点的值 即先获取表尾结点的值 再将表尾结点的值修改为指定的值element即可
ele = tail.data;
tail.data = element;
}else{ //修改表中间位置的节点的值
/*
修改表中间位置的节点的值:定义一个新节点p使其指向头节点
从头节点开始遍历 遍历到索引位置的前驱位置 每次更新p节点为p节点的下一个节点
遍历结束后 p节点即指定要修改的索引处的节点 获取p节点的值
修改p节点的值为element
*/
Node p = head;
for(int i = 0; i < index; i++){
p = p.next;
}
ele = p.data;
p.data = element;
}
return ele;
}
//获取链表有效元素的个数
@Override
public int size() {
return size;
}
//获取指定元素第一次在链表出现的索引
@Override
public int indexOf(E element) {
/*
定义一个新节点p使其指向头节点
遍历链表 直到p节点的数据与指定数据相等即可
每次更新p节点为p节点的下一个节点 索引index的值
判断 如果p节点再次指向头节点 则表示已经遍历了一圈了 都没有找到指定元素 则返回-1
*/
int index = 0;
Node p = head;
while (!p.data.equals(element)){
p = p.next;
index++;
if(p == head){
return -1;
}
}
return index;
}
//判断链表中是否包含指定元素
@Override
public boolean contains(E element) {
return indexOf(element) != -1;
}
//判断链表是否为空
@Override
public boolean isEmpty() {
return size == 0 && (head == null && tail == null);
}
//清空链表中的所有元素
@Override
public void clear() {
head = null;
tail = null;
size = 0;
}
//对链表进行排序 选择排序的思想 时间复杂度为O(n^2)
@Override
public void sort(Comparator<E> c) {
if(c == null){ //判断比较器是否为空
throw new IllegalArgumentException("comparator can not be null");
}
if(size == 0 || size == 1){ //如果链表中有效元素的个数等于0或1 就不需要在对链表进行排序
return;
}
Node nodeA = head; //定义一个节点A 使其指向头节点
Node nodeB = nodeA.next; //定义一个节点B 使其指向节点A的下一个节点
while (true){ //第一层循环 操作节点A
while (true){ //第二层循环 操作节点B
if(c.compare(nodeA.data, nodeB.data) > 0){ //如果节点A的值大于节点B的值 就交换节点A的数据和节点B的数据的位置
swap(nodeA, nodeB);
}
if(nodeB == tail){ //如果节点B等于尾节点 就中断内层循环
break;
}
//更新节点B为当前节点B的下一个节点
nodeB = nodeB.next;
}
if(nodeA.next == tail){ //如果节点A的下一跳指向尾节点 就中断外层循环
break;
}
nodeA = nodeA.next; //更新节点A为当前节点A的下一个节点
nodeB = nodeA.next; //更新节点B为当前节点B的下一个节点
}
}
//交换两个节点的值的位置
private void swap(Node nodeA, Node nodeB) {
E temp = nodeA.data;
nodeA.data = nodeB.data;
nodeB.data = temp;
}
//获取链表中指定索引范围的子链表 [fromIndex, toIndex]
/*
先定义一个节点A 使其指向头节点 遍历链表 更新节点A 使其到达fromIndex位置处
再定义一个节点B 也使其指向头节点 遍历链表 更新节点B 使其到达toIndex位置处
定义一个新节点使其指向节点A 从fromIndex位置遍历到toIndex位置 最后使其指向节点B
将[fromIndex, toIndex]范围内的节点重新依次加入到新的子链表中
该获取子链表的时间复杂度为O(n)
*/
@Override
public List<E> subList(int fromIndex, int toIndex) {
if(fromIndex < 0 || fromIndex > toIndex || toIndex >= size){ //判断两个索引是否存在
throw new IllegalArgumentException("must 0 <= fromIndex <= toIndex <= size - 1");
}
LinkedSinglyCircularList<E> linkedList = new LinkedSinglyCircularList<E>();
Node nodeA = head;
for(int i = 0; i < fromIndex; i++){
nodeA = nodeA.next;
}
Node nodeB = head;
for(int i = 0; i < toIndex; i++){
nodeB = nodeB.next;
}
Node p = nodeA;
for(int i = fromIndex; i < toIndex; i++){
linkedList.add(p.data);
p = p.next;
}
return linkedList;
}
//格式化链表输出是的格式
@Override
public String toString() {
StringBuilder str = new StringBuilder();
str.append("LinkedSinglyList: " + size + " [");
if(isEmpty()){
str.append(']');
}
Node p = head;
while (true){
str.append(p.data);
if(p == tail){
str.append(']');
break;
}else{
str.append(',');
str.append(' ');
}
p = p.next;
}
return str.toString();
}
//获取当前这个数据结构/容器 的 迭代器
//通过迭代器对象 更方便挨个取出每一个元素
//同时 实现了Iterable 可以让当前的数据结构/容器 被foreach循环遍历
@Override
public Iterator<E> iterator() {
return new LinkedSinglyCircularListIterator();
}
class LinkedSinglyCircularListIterator implements Iterator<E>{
private Node cur; //定义一个游标节点
private boolean flag = true; //表示是否在第一圈内 在第一圈是true 遍历到第二圈是false
@Override
public boolean hasNext() { //判断是否有下一个节点
return flag; //flag == true时 表示有下一个节点
}
@Override
public E next() { //获取下一个节点的值
E ele = cur.data; //先获取节点cur的值
cur = cur.next; //再更新cur节点为当前cur节点的下一个节点
if(cur == head){ //判断cur游标节点是否重新指向头节点 如果是则表示已经遍历一圈了 反之还在第一圈
flag = false;
}
return ele;
}
}
//单向循环链表实现约瑟夫环问题
/*
* 据说著名犹太历史学家Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与
* Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了
* 一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,
* 然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。
* 首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。
* 接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,
* 这个人就可以继续活着。Josephus要他的朋友先假装遵从,
* 他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。
*/
/*
先定义一个新节点p 使其指向头节点head
先更新p节点为当前p节点的下一个节点 再定义要删除的的节点del del为当前p节点的下一个节点
判断要删除的节点 如果是头节点 就使头节点指向要删除节点的下一个节点
如果是尾节点 就尾节点指向要删除节点的前驱节点p
将要删除节点的下一跳赋值给p节点的下一跳
使要删除节点的下一跳指向null(相当于删除了del节点)
再更新p节点为当前p节点的下一跳
有效元素个数减一
循环以上步骤 直至有效元素的个数等于2
*/
public void josephusLoop(){
if(size <= 2){
return;
}
Node p = head;
while (size != 2){
p = p.next;
Node del = p.next;
if(del == head){
head = del.next;
}else if(del == tail){
tail = p;
}
p.next = del.next;
del.next = null;
p = p.next;
size--;
}
}
//链表反转的问题
/*
我们可以定义一个虚拟头节点 将原链表的每一个节点(从头节点开始到尾节点结束)依次都插在虚拟头节点的下一跳节点
最后形成原链表的倒序链表(倒序新链表)
*/
public void reverse(){
if(size == 0 || size == 1){
return;
}
Node dummpyHead = new Node(); //虚拟头节点
Node p = head;
for(int i = 0; i < size; i++){
Node n = new Node(p.data); //创建新的节点对象 组成新的倒序链表
if(dummpyHead.next == null){
tail = n;
}
n.next = dummpyHead.next;
dummpyHead.next = n;
p = p.next;
}
head = dummpyHead.next;
}
}