什么是单项循环链表?
单项循环链表顾名思义,就是一个单方向并且可以循环的链表。也就是说将我们之前学习的单链表中的尾节点的指针由空指向头结点(或第一个元素节点的),从而形成了一个环,这种头尾相接的单链表称为单项循环链表。
我们之前讲的单链表中存在着一个虚拟头结点,这个虚拟头结点是不存放任何数据元素的,那么在单项循环链表中,我们是否还能够继续使用虚拟头结点呢?答案是不能,因为我们在单项循环链表中是要进行循环,那么要求数据之间必须的连续的,不能有间断,如果我们继续使用虚拟头结点,会使数据之间不连续,无法进行循环。那么在单项循环链表中,我们应该采用的是真实头结点,即在第一个结点中存放了数据元素的。
我们来看单项循环链表代码的实现:
单项循环链表和之前的单链表一样,都要实现List 接口。
public class LoopSingle<E> implements List<E> {
}
这里我们要使用一个内部类Node,用来封装要插入的元素。
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();
}
}
成员变量——head:头指针 rear:尾指针 size :链表中有效元素的个数
private Node head;
private Node rear;
private int size;
构造函数——构造函数分为无参构造函数和有参构造函数。无参构造函数是创建一个空的链表,有参构造函数的参数是一个数组。当链表为空时,head和rear都是null,且size是0. 有参构造函数创建时,先调用无参的构造函数创建一个空链表,然后将数组中的元素循环的加入到链表中。
public LoopSingle() {
head=null;
rear=null;
size=0;
}
public LoopSingle(E [] arr) {
this();
for(E e:arr) {
addLast(e);
}
}
要实现List接口中的一些方法。getSize() 用来获取链表中有效元素的个数。 isEmpty() 判断链表是否为空
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return head==null&&rear==null&&size==0;
}
我们来看循环链表中的插入方法,分为以下几种情况:
1.链表为空时,插入第一个元素。此时的head和rear指针都是指向null的,我们插入新的元素A,先将A元素封装为结点,
然后将A的地址给头指针和尾指针,让尾指针的下一跳指向头指针。
2.尾插法——即在尾部插入一个新的结点。还是一样,先将新元素封装为一个结点。 然后将尾结点A的下一跳给新结点B的下一跳,再将新节点的地址给头结点的下一跳,再将rear指针后移。
3. 头插法——在链表的头指针前插入一个元素。先将新元素封装为一个结点。然后让新结点C的下一跳指向head,在移动head指针,在更新rear指针的下一跳指向当前的head。
4.一般插入——与单链表中的一般插入相同 ,先找到要插入位置的前一个元素。把插入位置的前一个结点的下一跳给新元素的下一跳,然后就新结点的地址给要插入位置结点的下一跳,例如:在上图A和B之间插入元素D,我们要先找到元素A,然后把A的下一跳给D的下一跳,再把D的地址个A的下一跳
@Override
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往后移动
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++;
}
这是一些在指定位置添加结点的方法,调用我们之前的写的add()方法就可以。
@Override
public void addFirst(E e) {
add(0,e);
}
@Override
public void addLast(E e) {
add(size,e);
}
这是一个查找方法get()。 先判断要查找的角标是否合法,如果合法继续往下找,如果不和法,则抛出一个异常。
此处有一些特殊情况,即要查找的角标是0或者角标为size-1,那就直接返回头指针和尾指针的data就可以。
@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-1);
}
set()方法——修改指定角标的值。和上面get()方法一样,这里就不过多解释了。
@Override
public void set(int index, E e) {
if(index<0||index>=size) {
throw new IllegalArgumentException("修改角标非法!");
}
if(index==0) {
head.data=e;
}else if(index==size-1){
rear.data=e;
}else {
Node p = head;
for(int i=0;i<index;i++) {
p=p.next;
}
p.data=e;
}
}
contains()和find()方法。 find()方法用来获取指定元素的角标,找到不到返回-1.contains()方法,判断链表是否包含当前指定元素,调用find()方法,让它不等于-1即可。
@Override
public boolean contains(E e) {
return find(e)!=-1;
}
@Override
public int find(E e) {
if(isEmpty()) {
return -1;
}
int index =0;
Node p = head;
while(p.data!=e) {
p=p.next;
index++;
if(p==head) {
return -1;
}
}
return index;
}
remove()删除一个结点。此时我们也要分情况:
1.当前链表只有一个元素。 此时我们让head和rear指针都为null,并将size--就可以。
2.头删——删除当前链表的第一个结点。 将head指针往前移动,指向head的下一跳。更新尾节点的下一跳,让它指向当前的head。
3.尾删——删除当前链表的最后一个结点。先找到当前尾节点的前一个结点,将尾结点的下一跳给找到的结点的下一跳,然后更新rear指针,让它指向找到的这个结点的位置。
4.一般删除——删除链表除头结点和尾结点的任意一个结点。 先找到要删除结点的前一个结点,将要删除结点的下一跳给它前一个结点的下一跳,然后让要删除结点的下一跳为null。
下面是用代码实现的:
@Override
public E remove(int 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) {//尾删
res=rear.data;
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;
del.next=null;
}
size--;
return res;
}
删除指定位置的结点。调用上面写好的remove()方法就可以。
@Override
public E removeFirst() {
return remove(0);
}
@Override
public E removeLast() {
return remove(size-1);
}
@Override
public void removeElement(E e) {
remove(find(e));
}
clear()方法——清空链表。让head和rear指针都为null,且size为0就可以0
@Override
public void clear() {
head=null;
rear=null;
size=0;
}
测试类:
public class LoopMain {
public static void main(String[] args) {
LoopSingle<Integer> loop = new LoopSingle<Integer>();
for (int i = 1; i <=10; i++) {
loop.addLast(i);
}
System.out.println(loop);
for(int i=0;i<30;i++) {
System.out.print(loop.get(i%loop.getSize())+" ");
}
}
}
运行结果: