在我的上篇博客中,详细介绍了关于单链表的实现,链接:https://blog.csdn.net/CDCSPR/article/details/100856471
在这里我要介绍的是单向循环链表的实现及其可能的应用。
单向循环链表
定义:
单向循环链表和普通的单链表区别主要在于尾结点,单链表的尾结点指向null,而单向循环链表是将尾结点的指针指向了头结点,首尾相连,从而构成了循环链表。如下图所示:
实现思路:
循环链表的操作和线性链表基本一致,即链表中的最后一个节点的指针域指向头结点,整个链表形成一个环。
要注意的是:循环链表判空条件:有的说是判断p或p->next是否等于头指针,有的说判断tail是否等于head,有的说判断head是否为空,这其实要根据实际情况来判断。若是不带头结点(这里说带头结点是只一个空的节点做为头结点)循环链表,则可以用判断head是否为空来判断链表是否为空。若是带头结点,则可以根据判断head是否等rear(head==rear)于来判断循环链表是否为空。
实现代码:
首先先实现链表的存储单元,也就是结点类Node,结点类包括存储数据data和后继指针next两个元素:
private class Node {
E data;// 数据域
Node next;// 指针域
public Node() {
this(null, null);
}
public Node(E data, Node next) {
this.data = data;
this.next = next;
}
@Override
public String toString() {
return data.toString();
}
}
链表初始化:
private Node head;//头结点
private Node rear;//尾结点
private int size;//链表长度
public LoopSingle() {
head=null;
rear=null;
size=0;
}
public LoopSingle(E[] arr){
}
判断链表是否为空:
基于链表的结构特点,头结点是链表的开始位置,所以要判断链表是否为空,只要判断链表的头结点是为空即可
public boolean isEmpty() {
return size == 0 && head == null && rear == null;
}
链表的长度大小:原理:链表是存储的单元是结点,所有结点的数量也就是链表的长度。结点中存储着后继结点的引用地址,单向循环链表的终点是尾结点的指针为头结点,完成一个闭环,因此只需循环遍历所有结点,依次沿着后继指针循环,直到指针指向头结点,就可以得到链表的长度。
不过在这里,我们在对链表进行初始化时,链表有size属性,所以:
public int getSize() {
return size;
}
添加元素:
单向循环链表添加元素主要分为四种情况
1.空链表插入结点;
2.头结点插入元素,新增结点为头结点;
3.中间情况插入节点;
4.尾结点插入元素,也就是在插入在单链表末尾。
public void add(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("插入角标非法");
}
Node n = new Node(e, null);
if (isEmpty()) {//链表为空
head = n;
rear = n;
rear.next = head;
} else if (index == 0) { // 头插
n.next = head;
head = n;
rear.next = head;
} else if (index == size) { // 尾插
n.next = head;
rear.next = n;
rear = n;
} else { // 一般情况
Node p = head;
for (int i = 0; i < index - 1; i++) {
p = p.next;
}
n.next = p.next;
p.next = n;
}
size++;
}
@Override
public void addFirst(E e) {//在头结点添加
add(0, e);
}
@Override
public void addLast(E e) {//在尾结点添加
add(size, e);
}
查找链表中某个元素的值:
@Override
public E get(int index) {//获取指定结点的元素
if (index < 0 || index >= size) {
throw new IllegalArgumentException("查找角标非法");
}
if (index == 0) {
return head.data;
} else if (index == size - 1) {
return rear.data;
} else {
Node p = head;
for (int i = 0; i < index; i++) {
p = p.next;
}
return p.data;
}
}
@Override
public E getFirst() {//获取头结点的元素
return get(0);
}
@Override
public E getLast() {//获取尾结点的元素
return get(size);
}
查询链表中某个元素是否存在:
@Override
public boolean contains(E e) {
return find(e) != -1;
}
@Override
public int find(E e) {
if (isEmpty()) {
return -1;
}
Node p = head;
int index = 0;
while (p.data != e) {
p = p.next;
index++;
if (p == head) {
return -1;
}
}
return index;
}
删除链表中某个结点:
@Override
public E remove(int index) {//删除链表中的第index个结点
if (index < 0 || index >= size) {
throw new IllegalArgumentException("删除角标非法");
}
E res = null;
if (size == 1) {
res = head.data;
head = null;
rear = null;
} else if (index == 0) {
res = head.data;
head = head.next;
rear.next = head;
} else if (index == size - 1) {
Node p = head;
while (p.next != rear) {
p = p.next;
}
p.next = rear.next;
rear = p;
} else {
Node p = head;
for (int i = 0; i < index - 1; i++) {
p = p.next;
}
Node del = p.next;
res = del.data;
p.next = del.next;
}
size--;
return res;
}
@Override
public E removeFirst() {//删除链表的头结点
return remove(0);
}
@Override
public E removeLast() {//删除链表的尾结点
return remove(size - 1);
}
@Override
public void removeElement(E e) {//删除链表中元素值为e的结点
remove(find(e));
}
清空整个链表:
@Override
public void clear() {
head = null;
rear = null;
size = 0;
}
遍历整个链表:
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("LoopQueue:size=" + getSize() + "\n");
if (isEmpty()) {//判空
sb.append("[]");
} else {
sb.append("[");
Node p = head;
while (true) {
sb.append(p.data);
if (p.next == head) {
sb.append(']');
break;
} else {
sb.append(',');
}
p = p.next;
}
}
return sb.toString();
}
总结:我认为单向循环链表是单链表的基本类似,单向循环链表与普通链表的区别主要是在尾结点的判断。并且其结构特点链表中最后一个结点的指针域不再是结束标记,而是指向整个链表的第一个结点,从而使链表形成一个环。带头结点的单向循环链表实现插入和删除操作较为方便。