循环链表
将单链表中终端结点的指针端由空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表称为单循环链表,简称循环链表(circular linked list)
单链表解决了一个很麻烦的问题就是:从链表当中的某一结点出发,遍历整个链表的问题
循环链表和单链表在实现上的主要区别在于如何判断是否到达链表结尾:
单链表中:
while(temp.next!=null){
temp=temp.next;
}
循环链表中:
while(temp.next!=head){
temp=temp.next;
}
手写循环链表
类结构图
循环链表实现类demo
package com.company.datastructure;
public class MyCircleLinkedList<E> {
//结点内部类
private class Node<E> {
public E element; //默认初始化为null
private Node<E> next; //默认初始化为null
public Node(){
}
public Node(E element){
this.element = element;
}
}
private Node<E> head; //头结点
private int size; //元素总数
//构造器(初始化链表)
public MyCircleLinkedList(){
head = new Node<E>(); //默认初始化为null
head.next = head; //空的循环链表头结点指针指向本身
}
//增加
/**
* 指定位置插入结点
* @param index 索引
* @param element 结点元素值
*/
public void insert(int index,E element){
rangeCheck(index);
Node<E> newNode = new Node<E>(element);
Node<E> temp = head;
for (int i = 0; i < index; i++) {
temp = temp.next;
}
newNode.next = temp.next; //注意顺序
temp.next = newNode;
size++;
}
/**
* 头插法
* @param element 结点元素值
*/
public void addFirst(E element){
Node<E> newNode = new Node<>(element);
newNode.next = head.next;
head.next = newNode;
size++;
}
/**
* 尾插法
* @param element 结点元素值
*/
public void addLast(E element){
//
Node<E> newNode = new Node<E>(element);
Node<E> temp = head;
while(temp.next!=head){ //与单链表判断条件不同
temp = temp.next;
}
temp.next = newNode;
newNode.next = head;
size++;
}
//删除
/**
* 删除指定索引处元素(不包含头尾元素)
* @param index 索引
* @return 成功返回true,否则返回false
*/
public E remove(int index){
rangeCheck(index);
Node<E> temp = head;
for (int i = 0; i < index; i++) {
temp = temp.next;
}
E oldElement = temp.next.element;
temp.next = temp.next.next;
size--;
return oldElement;
}
/**
* 从表头删除元素
* @return 删除元素值
*/
public E removeFirst() {
if (size==0){
throw new IndexOutOfBoundsException("链表为空");
}
E oldElement = head.next.element;
head.next = head.next.next;
size--;
return oldElement;
}
/**
* 从表尾删除元素
* @return 删除元素值
*/
public E removeLast(){
if (size==0){
throw new IndexOutOfBoundsException("链表为空");
}
Node<E> temp = head;
while(temp.next.next!=head){
temp = temp.next;
}
E oldElement = temp.next.element;
temp.next = null;
size--;
return oldElement;
}
//修改
/**
* 修改指定索引处元素值
* @param index 索引
* @param element 元素值
* @return 成功返回原元素值
*/
public E set(int index,E element){
rangeCheck(index);
Node<E> temp = head;
Node<E> oldValue = new Node<>();
for (int i = 0; i < index; i++) {
temp = temp.next;
}
oldValue = temp.next;
temp.next.element = element;
return oldValue.element;
}
//查找
/**
* 根据索引返回元素值
* @param index 索引
* @return 索引处元素值
*/
public E get(int index){
rangeCheck(index);
Node<E> temp = head;
for (int i = 0; i < index; i++) {
temp = temp.next;
}
return temp.next.element;
}
/*---------------------------------------------------------------------------------*/
/**
* 获得链表长度
* @return 链表长度
*/
public int size(){
return size;
}
/**
* 链表是否为空
* @return true为空,false不为空
*/
public boolean isEmpty(){
return size==0?true:false;
}
/**
* 清空链表
*/
public void clear(){
Node<E> temp = head;
while(temp.next != head){
Node<E> next = temp.next;
temp.element = null;
temp.next = null;
temp = next;
}
temp.element = null;
temp.next = null;
head.next = head;
size = 0;
}
/*---------------------------------------------------------------------*/
/**
* 索引检查
* @param index 索引
*/
private void rangeCheck(int index){
if (index <0|| index >= size){
throw new IndexOutOfBoundsException("索引不合法"+index);
}
}
}
测试类demo
package com.company.datastructure;
public class TestMyCircleLinkedList {
public static void main(String[] args) {
MyCircleLinkedList<Integer> mcl = new MyCircleLinkedList<>();
mcl.addFirst(1);
mcl.addFirst(2);
mcl.addFirst(3);
mcl.addLast(0);
mcl.insert(2,10);
mcl.set(2,100);
System.out.println(mcl.size());
System.out.println(mcl.isEmpty());
for (int i = 0; i < mcl.size(); i++) {
System.out.println(mcl.get(i));
}
mcl.removeFirst();
mcl.removeLast();
mcl.remove(1);
System.out.println(mcl.size());
for (int i = 0; i < mcl.size(); i++) {
System.out.println(mcl.get(i));
}
System.out.println(mcl.get(1));
}
}
当然,在单链表中有了头结点时,我们可以使用O(1)的时间访问第一个结点,但是对于要访问的最后一个结点,却需要将单链表进行遍历,所需时间为O(n)。可以对循环链表进行适当修改,不用头指针,而是使用指向终端结点的尾指针来表示循环链表,此时查找开始结点和终端结点就都很方便了
从上图中可以看到,终端结点使用rear指示,则查找终端结点是O(1),而开始结点,就是rear->next->next,其时间复杂度也是O(1)
比如如果需要将两个循环链表合并成一个链表时就会十分简单
要想把他们合并,只需要进行如下操作即可