链式存储结构之单项循环列表

  • 上篇博客主要以自己的理解介绍了普通的单链表,在此基础上,主要介绍一下这种特殊的单链表————单向循环链表。
  • 单项循环链表就像是一根价值连城的项链,或者路边摊的项链,环套环,首位相接形成一个大环,不管贵贱,都能说明它是一个循环链表结构。
概念

单项循环列表:上篇文章知道单链表的尾结点指向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 

单向循环链表关键就在增删这块,它不用考虑到扩容。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值