- 上篇博客主要以自己的理解介绍了普通的单链表,在此基础上,主要介绍一下这种特殊的单链表————单向循环链表。
- 单项循环链表就像是一根价值连城的项链,或者路边摊的项链,环套环,首位相接形成一个大环,不管贵贱,都能说明它是一个循环链表结构。
概念
单项循环列表:上篇文章知道单链表的尾结点指向null,也就是一个空的地址,它表示这是最后一个结点,而单向循环链表就是将尾结点的指针指向了头结点,首尾相接,形成了一个环,循环使用。注意:单链表使用的是虚拟头结点完成的,而在此单项循环列表使用的是真实头结点。
- 如果链表为空时 head=head.next
-
正常的单向循环链表 rear.next=head
-
实现单向循环链表的顶级接口List与单链表一致,它也是一个线性表,上篇介绍到了List接口的方法,
-
接下来只要代码实现list方法就可以了。只不过它的增删方法会有所不同,因为是循环的,
-
头插:rear的下一跳指向新元素C ,将C元素的下一跳指向头结点A,head移向C元素
-
尾插:把新元素的下一跳指向head,尾的下一跳指向新元素,新元素就是尾结点。
-
一般插/一般删:都要找前驱,Node p = head;p从头开始,找前驱,
p=p.next;Node del=p.next;删除的结点 p是前驱,p.next=del.next;//把要删除结点的下一跳给p前驱,size–;return res;返回删除/添加的元素 -
删头: head 移向当前的下一跳,让rear结点指向当前head(A)的下一跳。C自己就走了。
-
删尾:找尾结点的前驱,把B的下一跳给A的下一跳,没人指向B, 尾指针指向A.
现在开始代码实现:
package com.snow.链表;
import com.snow.线性表.List;
//这里用的是真实头结点 他也是一个线性表 实现List方法
public class LoopSingle<E> implements List<E>{
private Node head;//定义头指针
private Node rear;//尾指针
private int size;//当前循环链表元素个数
//创建无参构造函数 初始化为空
public LoopSingle() {
head=null;//
rear=null;//
size=0;//
}
//有参构造函数,把数组封成单向链表
public LoopSingle(E[] arr){
}
@Override
public int getSize() {
return size;//有效元素个数
}
@Override
public boolean isEmpty() {
//有效元素个数为空 头尾指向空
return size==0&&head==null&&rear==null;
}
@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;//把n的地址给head rear
rear=n;
rear.next=head;//把rear的下一跳指向head
/*size++;*///size++ 不执行了
}
if(index==0){//铀元素的情况下 头插
n.next=head;//插入元素指向头
head=n;//head移向n
rear.next=head;//尾指针指向当前头
}else if(index==size){//尾插
n.next=head;//把新元素的下一跳指向head
rear.next=n;//尾的下一跳指向插入如元素n
rear=n;//n就是尾结点
}else{//一般插入
Node p = head;//游标 从头开始,
for(int i=0;i<index-1;i++){
p=p.next;//从o开始走到删除结点的前一位
}
n.next=p.next;//游标p的下一个就是插入的位置。
p.next=n;//再把游标p的下一个给n
}
size++;//都要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-1);
}
@Override
public void set(int index, E e) {//修改指定角标
if(index<0||index>=size){
throw new IllegalArgumentException("修改角标非法");
}
if(index==0){
head.data=e; //头元素为e 改头
}else if(index==size-1){
rear.data=e;//尾元素尾e 改尾
}else{
Node p=head;//把get方法拿来直接用
for(int i=0;i<index;i++){
p=p.next;
}
p.data=e;
}
}
@Override
public boolean contains(E e) {//先找
return find(e)!=-1;//判断!=-1就包含
}
@Override
public int find(E e) {
if(isEmpty()){//判空
return -1;
}
int index=0;
Node p=head;//从头开始
while(p.data!=e){//如果p不是找的元素
p=p.next;//后移
index++;//角标++
if(p==head){//如果p循环移到头了
return -1;//没找到
}
}
return index;//返回角标
}
@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;//head移向下一个结点
rear.next=head;//尾的下一跳就是head
}else if(index==size-1){//删尾
res=rear.data;
Node p=head;//找尾的前驱
while(p.next!=rear){//p的下一个不是尾指针的话
p=p.next; //p后移
}
p.next=rear.next;//把p的下一给尾的下一跳 头
rear=p;//把p的地址给rear
}else{//删除某一个位置
Node p = head;//p从头开始
for(int i=0;i<index-1;i++){//找前驱
p=p.next;
}
Node del=p.next;//删除的结点 p是前驱
res=del.data;//
p.next=del.next;//把要删除结点的下一跳给p前驱
}
size--;//删除size-- 直接放在循环外
return res;//返回删除的元素
}
@Override
public E removeFirst() {//删头
return remove(0);
}
@Override
public E removeLast() {//删尾
return remove(size-1);
}
@Override
public void removeElement(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("LoopSingle: size="+getSize()+"\n");
if(isEmpty()){
sb.append("[]");
}else{
sb.append('[');
Node p = head;//从头开始 p算一个元素进去了
while(true){//直接进去
sb.append(p.data);//加进来
if(p.next==head){//p往后移到p的下个为头时 就到了尾部 就停了
sb.append(']');
break;
}else{
sb.append(',');//不是头的话 加 ,
}
p=p.next;//p再往后移
}
}
return sb.toString();
}
//有结点对象,结点信息一样,直接复制。先内置结点对象,一般放在代码后边
private class Node{ //Node是链表的内部类 内部对象不需要私有化
E data;//数据域
Node next;//指针域
public Node(){
data=null;
next=null;
}
public Node(E data,Node next){
this.data=data;
this.next=next;
}
}
}
测试类通过
public class Test03 {
public static void main(String[] args) {
LoopSingle<Integer> loop = new LoopSingle<Integer>();
for(int i=10;i>=6;i--){
loop.addLast(i);
}
System.out.println(loop);
for(int i=1;i<=5;i++){
loop.addFirst(i);
}
System.out.println(loop);
for(int i =0;i<30;i++){//i为角标
System.out.print(loop.get(i%loop.getSize())+" ");
}
System.out.println();
/* LinkedList<Integer> ll = loop.josephusLoop(41, 3);
System.out.println(ll);
System.out.println(loop);
loop.magicPoker();*/
}
}
LoopSingle: size=5
[10,9,8,7,6]
LoopSingle: size=10
[5,4,3,2,1,10,9,8,7,6]
5 4 3 2 1 10 9 8 7 6 5 4 3 2 1 10 9 8 7 6 5 4 3 2 1 10 9 8 7 6
单向循环链表关键就在增删这块,它不用考虑到扩容。