单向循环链表

单向循环链表

什么是循环链表?

我们之前定义了链表,所谓的循环链表就是在原先链表的基础上加以改动,让他进行循环执行,这样就成了循环链表,之前链表的尾指针(rear)的下一个指向的是null,而循环链表最后的尾指针的下一个指向头。

  • 注意
    我们之前定义的单链表的头节点,是不存储任何元素的,现在我们定义的新的循环链表的头节点,是存储元素的。
    在这里插入图片描述在这里插入图片描述

循环链表的结构
在这里插入图片描述

循环链表的定义实现List 接口的方法

	/**
	 * 获取线性表中元素的个数(线性表的长度)
	 * @return 线性表中有效元素的个数
	 * */
	public int getSize();
	
	/**
	 * 判断线性表是否为空
	 * @return 是否为空的布尔类型值
	 * */
	public boolean isEmpty();
	
	/**
	 * 在线性表中指定的index角标处添加元素e
	 * @param index 指定的角标 0<=index<=size
	 * @param e	要插入的元素
	 * */
	public void add(int index,E e);
	
	/**
	 * 在线性表的表头位置插入一个元素
	 * @param e 要插入的元素 指定在角标0处
	 * */
	public void addFirst(E e);
	
	/**
	 * 在线性表的表尾位置插入一个元素
	 * @param e 要插入的元素 指定在角标size处
	 * */
	public void addLast(E e);
	
	/**
	 * 在线性表中获取指定index角标处的元素
	 * @param index 指定的角标 0<=index<size
	 * @return 该角标所对应的元素
	 * */
	public E get(int index);
	
	/**
	 * 获取线性表中表头的元素
	 * @return 表头元素 index=0
	 * */
	public E getFirst();
	
	/**
	 * 获取线性表中表尾的元素
	 * @return 表尾的元素 index=size-1
	 * */
	public E getLast();
	
	/**
	 * 修改线性表中指定index处的元素为新元素e
	 * @param index 指定的角标
	 * @param e 新元素
	 * */
	public void set(int index,E e);
	
	/**
	 * 判断线性表中是否包含指定元素e 默认从前往后找
	 * @param e 要判断是否存在的元素
	 * @return 元素的存在性布尔类型值
	 * */
	public boolean contains(E e);
	
	/**
	 * 在线性表中获取指定元素e的角标 默认从前往后找
	 * @param e 要查询的数据
	 * @return 数据在线性表中的角标
	 * */
	public int find(E e);
	
	/**
	 * 在线性表中删除指定角标处的元素 并返回
	 * @param index 指定的角标 0<=index<size
	 * @return 删除掉的老元素
	 * */
	public E remove(int index);
	
	/**
	 * 删除线性表中的表头元素
	 * @return 表头元素
	 * */
	public E removeFirst();
	
	/**
	 * 删除线性表中的表尾元素
	 * @return 表尾元素
	 * */
	public E removeLast();
	
	/**
	 * 在线性表中删除指定元素e
	 * */
	public void removeElement(E e);
	
	/**
	 * 清空线性表
	 * */
	public void clear();

** 定义的方法**

public class LoopSingle<E> implements List<E>

实现的思想:
我们在实现这个循环链表时,我们已经知道了,链表是由一个个的结点来构成的,而结点里面又存了,元素和指向下一个结点的地址。所以我们需要定义一个结点的内部类,用来实现这个循环链表。

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 int getSize() {
		return size;
	}

思想: 返回当前结点的个数,就直接返回当前size 的数。

//判断当前链表是否为空 
public boolean isEmpty() {
		return size==0&&head==null&&rear==null;
	}

思想:当我们的链表为空的时候 size 是为零的,头指针和尾指针都是指向空的。

//往指定角标处添加元素e
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++;
	}

思想: 添加元素时,我们有几种的特殊的情况,当我们的当前的链表为空的时候,我们插入第一个结点时,此时我们的头节点和尾结点都指向我们插入的第一个结点。此时,head 指向头节点,rear 指向头节点,而rear 的下一个指向的还是当前的结点。还有我们的链表不是为空的时候,我们就要考虑是头插,还是尾插,还是任意插,当我们头插的时候,直接把要插入结点的一个指向当前的头节点,然后把head (头指针)更新到要插入的结点上,然后将尾指针(rear)的下一个指向更新后的头。插尾的时候,先把尾的下一个给插入的结点,把rear (尾指针)更新到当前的结点,把新的尾指针的下一个指向head (头指针),而任意插 的时候,我们需要找到要插入角标结点的前一个结点,我们可以根据告知我们的角标,找到它的前驱,然后进行插入。
插入第一个
在这里插入图片描述
头插
在这里插入图片描述
尾插
在这里插入图片描述

//获取指定角标处结点的元素
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;
		}
	}

思想:这个也是一样的,获取指定处的角标的时候,判读要获取的元素的位置,头 ,尾,或者是任意的位置时,找到当前的结点,然后返回当前的结点的元素。

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;
		}
	}

思想:修改指定角标处结点所存的元素为e;

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;
	}

思想:查找当前的链表中是否存在元素e;这和我们之前找结点的方法不同,我们之前是直接利用角标来进行判断的,现在我们没有了角标,只能利用while循环来进行查找。

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;
		}
		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;
	}

思想:删除指定角标的结点,我们在这也要判断许多的特殊情况,当我们要删除当前的结点的时候,一般我们要找到当前结点的前驱,可是当我们删到空的时候,那么什么时候为空呢,当当前的链表里只有一个结点的时候,我们再删,此时链表就为空了,当为空时,它的头指针,和尾指针,此时都指向的时空,
删头的时候,我们直接把头更新到下一个,然后把rear的下一个指向新的头,删尾的时候,我们需要找到它的前驱,然后把要删的结点的下一个给前驱的下一个,把尾指针更新到当前尾的前驱;我们要删任意的时候,找到它的前驱,直接把要删的结点的下一个给前驱的下一个,最后让它的size–.
删除到最后一个
在这里插入图片描述
删头
在这里插入图片描述
删尾
在这里插入图片描述

public String toString() {
		StringBuilder sb=new StringBuilder();
		sb.append("LoopSingle: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();
	}

思想:打印toString 方法,使用拼接的方法去进行拼接。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值