06-循环链表

1.单向循环链表

单向循环链表:最后一个节点的next会指向头结点。

在这里插入图片描述

在原来的单向链表的基础上,我们实现一下单向循环链表。
在这里插入图片描述

2.1要改动的方法

1.add()方法:

  1. 插入头结点时,要维护循环指针
  2. 还要考虑只有1个节点时的特殊情况:自己指向自己。当size=0时,向index=0处插入。要找到插入位置的前一个节点node(index-1);,但是插入位置是index=0,那么就没有前一个节点,要做特殊处理。
    在这里插入图片描述
  3. 代码修改:注意node()获取节点,一定要在first修改之前。
    在这里插入图片描述

2.remove()方法

  1. 删除头结点时,要维护循环指针,让最后一个节点node(size-1);的next始终指向头结点。
	@Override
	public E remove(int index) {
    
    
		rangeCheck(index);
		Node<E> oldNode = null;
		if(index == 0) {
    
    
			oldNode = first;
			//拿到最后一个节点
			Node<E> lastNode = node(size-1);
			first = first.next;
			lastNode.next = first;
		}else {
    
    
			rangeCheck(index);
			Node<E> preNode = node(index-1);
			oldNode = preNode.next;
			preNode.next = preNode.next.next;
		}
		size--;
		return oldNode.element;
	}
  1. 当size=0时,边界检测rangeCheck(index);会报错。
  2. 当size=1时,这部分代码没有成功删除掉index=0的节点
		if(index == 0) {
    
    
			oldNode = first;
			//拿到最后一个节点
			Node<E> lastNode = node(size-1);
			first = first.next;
			lastNode.next = first;
		}
  1. 修改
    在这里插入图片描述

2.双向循环链表

双向循环链表的头结点的prev指针,指向尾节点;尾节点的next指针,指向头结点。

双向链表图示:
在这里插入图片描述
双向循环链表图示:
在这里插入图片描述

2.1.add和remove修改

在双向链表LinkedList的基础上做下修改

1.add
注意下一双向循环链表只有一个节点时的情况:这个唯一的节点的prev指针指向自己,next指针也指向自己。

在这里插入图片描述
双向链表只有一个节点时的情况:
在这里插入图片描述
插入尾部的情况:
在这里插入图片描述

代码:

在这里插入图片描述

2.remove

代码:
在这里插入图片描述

3.约瑟夫问题

在这里插入图片描述

3.1.增强双向循环链表

1.发挥循环链表的最大威力,增设1个成员变量,3个方法

  1. current:用于指向当前节点
private Node<E> current; //指向当前节点
  1. void reset():让current指向头结点first
   /**
    * 让current指向头结点first
    */
   public void reset() {
    
    
   	current = first;
   }
  1. E next():让current往后走一步,也就是current = current.next
	/**
	 * 让current往后走一步,也就是current = current.next
	 * @return:返回当前的值
	 */
	public E next() {
    
    
		if(current == null) return null;
		current = current.next;
		return current.element;
	}
  1. E remove():删除current指向的节点,删除成功后让current指向下一个节点
	/**
	 * 删除current指向的节点,删除成功后让current指向下一个节点
	 * @return
	 */
	public E remove() {
    
    
		if(current == null) return null;
		
		Node<E> next = current.next;
		E element = remove(current);
		if(size == 0) {
    
    
			current = null;
		}else {
    
    
			current = next;
		}
		return element;
	}

	/**
	 * 根据索引删除节点
	 * @param index
	 * @return
	 */
	@Override
	public E remove(int index) {
    
    
		rangeCheck(index);
		return remove(node(index));
	}
	/**
	 * 删除给定节点
	 * @param oldNode
	 * @return
	 */	
	private E remove(Node<E> oldNode) {
    
    
		if(size == 1) {
    
    
			//因为改后的代码无法对size的情况进行删除,所以这边单独处理
			first = null;
			last = null;
		}else {
    
    
			Node<E> prevNode = oldNode.prev;
			Node<E> nextNode = oldNode.next;
			nextNode.prev = prevNode;
			prevNode.next = nextNode;
			//如果删除的是0结点
			if(oldNode == first) {
    
     //index == 0
				first = nextNode;
			}
			//如果删除的是size-1结点
			if(oldNode == last) {
    
     //index == size -1
				last = prevNode;
			}		
		}
		size--;
		return oldNode.element;
	}

3.2Josephus测试

public class Josephus {
    
    

	public static void main(String[] args) {
    
    
		CircleLinkedList<Integer> list = new CircleLinkedList<>();
		for (int i = 1; i <= 8; i++) {
    
    
			list.add(i);
		}
		
		//current指向头结点
		list.reset();
		
		while(!list.isEmpty()) {
    
    
			list.next();//current后移一位
			list.next();//current后移一位
			System.out.println(list.remove());//删除current指向的节点
		}
	}

}

4.静态链表(了解)

  • 1.我们之前所学习的链表是依赖于指针(引用)的实现的,但是有些变成语言是没有指针的,比如BASIC、Fortran语言。这种情况下,怎么实现链表呢?

  • 2.我们可以通过数组来模拟链表,即静态链表:

    数组的每个元素存放存放两个数据:值和下个元素的索引。
    数组0下标位置存放头结点信息。
    尾结点处的下一个元素的索引是-1。

  • 3.怎么实现数组的每个元素存放两个值呢?

    C语言可以通过结构体来实现,构造一个结构体数组。
    其他语言可以使用两个数组来实现:A数组存放值,B数组的相同索引处存放下个元素的索引。

在这里插入图片描述

5.ArrayList的进一步优化

1.之前实现的ArrayList的一些缺点:

  1. 其实也是数组的一个通病:查询高效,修改低效。

  2. 因为有索引下标的存在,(下标相当于标注出来元素的位置)数组可以实现随机访问,每个数组元素的位置都可以通过索引算出来。因为为数组分配的是一篇连续的内存,所以只要有了下标,那么计算机就可以通过寻址公式:

    a[i]_address = base_address + i * data_type_size
    

    快速的定位到该元素的地址,进行访问。
    即:数组支持随机访问,根据下标随机访问的时间复杂度为O(1)。

  3. 数组适合查找操作得到,但是查找的时间复杂度并不是O(1),即使是排好序的数组,你用二分查找,时间复杂度也是O(logn)。

  4. 当对数组进行插入操作时:该位置处的所有元素都要后移;当对数组进行删除操作时,该位置出的所有元素都需要前移。

2.怎么优化:提高插入和删除的效率

  1. 为什么要移动那么多元素:

    因为我们锚定了数组的起始位置(0下标),而且数组的每个元素是连续的。那么我们破坏这个条件(但是要保证这个条件在逻辑上不变),就可以改善数组的插入和删除操作。

  2. 我们在逻辑上保证起始位置不变:用一个int变量存放数组真正的起始下标,锚定这个变量的值为起始位置。
    在这里插入图片描述
    每次删除头元素时,我们就让first变量存放头元素的下一个元素的索引,这样就不用移动后面的元素了

  3. 最多挪动一半的元素:

    插入和删除时,不是永远挪动后面的元素,看看修改的位置两侧哪边的元素少,就挪动哪边,并且修改更新后的first的值,指向新的头元素。在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/tttxxl/article/details/115289309