单向循环链表
观察单向链表和单向循环链表的区别:
显然,循环链表和普通链表的区别在于,尾节点的next指针指向了头节点。而对于这条指向的维护也只在于add和remove方法,因此循环链表仅仅在这个方法上有所不同。(普通链表的创建可见链表(一)用Java语言实现单向链表(有/无虚拟头节点))
add方法:
@Override
public void add(int index, E element) {
//考虑边界
if(index==0){
//新建节点指向first节点
Node<E> newFirst= new Node<E>(element, first);
//获取最后一个节点,若size为0则last就是newFirst
Node<E> last= size==0?newFirst:node(size-1);
first=newFirst;//并更新first节点
last.next=first;
}else{//由于添加时已经成环,尾部加入会自动连接头部
//获取插入节点的先前节点
Node<E> prev=node(index-1);
//新建节点指向prev的next节点,prev指向新节点
//如果prev是最后一个节点,则它本来就指向first
prev.next=new Node<E>(element,prev.next);
}
size++;
}
remove方法:
@Override
public E remove(int index) {
rangeCheck(index);
Node<E> oldNode=first;
//判断是否是第一个节点
if(index==0){
if(size==0){//只有一个节点
first=null;
}else{
//获取最后一个节点
Node<E> last=node(size-1);
first=first.next;
last.next=first;//跟新环
}
}else{//由于添加时已经成环,尾部删除时会自动连接头部
//获取前驱节点
Node<E> prev=node(index-1);
//保存被移除节点数据
oldNode=prev.next;
//跳过next指向next.next,不需要更新环
prev.next=prev.next.next;
}
size--;
return oldNode.element;
}
整体:
public class CircleLinkedList<E> extends AbstractList<E> {
//定义普通内部类Node
private class Node<E>{
private E element;
private Node<E> next;
public Node(E element, Node<E> next) {
this.element = element;
this.next = next;
}
@Override
public String toString() {
StringBuilder sb=new StringBuilder();
sb.append(element).append("->");
if(next!=null)
sb.append(next.element);
else
sb.append("null");
return sb.toString();
}
}
private Node<E> first;//头节点
@Override
public int indexOf(E element) {
Node<E> node=first;
//如果元素为空,只需要考虑链表元素为空
if(element==null){
for(int i=0;i<size;i++){
if(node.element==null)return i;
node=node.next;
}
}else{//元素不为空则调用equals方法判断
for(int i=0;i<size;i++) {
if (element.equals(node.element)) return i;
node=node.next;
}
}
//未找到则返回常量-1
return ELEMENT_NOT_FOUND;
}
@Override
public void clear() {
//将头节点设为null,则头节点指向Node对象将被销毁,
// 导致存储地址被销毁,直至所有Node对象被销毁
first=null;
size=0;
}
@Override
public void add(int index, E element) {
//考虑边界
if(index==0){
//新建节点指向first节点
Node<E> newFirst= new Node<E>(element, first);
//获取最后一个节点,若size为0则last就是newFirst
Node<E> last= size==0?newFirst:node(size-1);
first=newFirst;//并更新first节点
last.next=first;
}else{//由于添加时已经成环,尾部加入会自动连接头部
//获取插入节点的先前节点
Node<E> prev=node(index-1);
//新建节点指向prev的next节点,prev指向新节点
//如果prev是最后一个节点,则它本来就指向first
prev.next=new Node<E>(element,prev.next);
}
size++;
}
@Override
public E remove(int index) {
rangeCheck(index);
Node<E> oldNode=first;
//判断是否是第一个节点
if(index==0){
if(size==0){//只有一个节点
first=null;
}else{
//获取最后一个节点
Node<E> last=node(size-1);
first=first.next;
last.next=first;//跟新环
}
}else{//由于添加时已经成环,尾部删除时会自动连接头部
//获取前驱节点
Node<E> prev=node(index-1);
//保存被移除节点数据
oldNode=prev.next;
//跳过next指向next.next,不需要更新环
prev.next=prev.next.next;
}
size--;
return oldNode.element;
}
@Override
public E get(int index) {
Node<E> node=node(index);
return node.element;
}
@Override
public E set(int index, E element) {
Node<E> node=node(index);
E oldElement=node.element;
node.element=element;
return oldElement;
}
//获取索引为index的节点
private Node<E> node(int index){
rangeCheck(index);
Node<E> node=this.first;
for(int i=0;i<index;i++)
node=node.next;
return node;
}
@Override
public String toString() {
StringBuilder sb=new StringBuilder("[");
Node<E> node=first;
for(int i=0;i<size;i++){
if(i!=size-1)
sb.append(node).append(" ,");
else
sb.append(node);
node=node.next;
}
sb.append("]");
return sb.toString();
}
}
测试代码
public class CircleLinkedListTest {
public static void main(String[] args) {
CircleLinkedList<Integer> list=new CircleLinkedList<>();
for(int i=0;i<10;i++)
list.add(i);
System.out.println(list);//[0,1,2,...,9]
list.add(0,100);
System.out.println(list);//[100,0,1,2,...,9]
list.add(1,101);
System.out.println(list);//[100,101,0,1,2,...,9]
list.remove(0);
System.out.println(list);//[101,0,1,2,...,9]
list.remove(1);
System.out.println(list);//[101,1,2,...,9]
list.remove(list.size()-1);
System.out.println(list);//[101,1,2,...,8]
System.out.println(list.get(2));//2
list.set(2,99);
System.out.println(list.get(2));//99
list.clear();
System.out.println(list);//[]
}
}
可以看到结果和预期相同。
双向循环链表
鉴于之前有写过双向链表,在此先对比一下双向循环链表和双向链表的区别。
双向循环链表主要将头节点和尾节点相连,而非双向链表那样,前后分别指向空。主要的区别也在于add(int index, E element)方法和remove(int index)方法(有关双向链表见 链表(二)用Java语言实现双向链表),具体实现如下:
add方法
@Override
public void add(int index, E element) {
rangeCheckForAdd(index);//index合理范围0-size
if(index==size){//从尾部插入节点
Node<E> oldLast=last;//获取前节点
last=new Node<E>(oldLast,element,null);
if(oldLast==null)//若是插入的第一个节点
first=last;
else
oldLast.next=last;
}else{
Node<E> next=node(index);//获取插入后的后节点
Node<E> prev=next.prev;//获取前节点
Node<E> node=new Node<E>(prev,element,next);
next.prev=node;
if(prev==null)//前节点为空,相当于从头插
first=node;
else
prev.next=node;
}
size++;
}
remove方法
@Override
public E remove(int index) {
rangeCheck(index);
//获取要删除的节点
Node<E> node=node(index);
//前驱节点
Node<E> prev=node.prev;
//后继节点
Node<E> next=node.next;
if(prev==null)
first=next;//修改头节点位置
else
prev.next=next;
if(next==null)
last=prev;
else
next.prev=prev;
size--;
return node.element;
}
整体:
public class DoubleLinkedList<E> extends AbstractList<E> {
//定义普通内部类Node
private class Node<E>{
private E element;
private Node<E> next;
private Node<E> prev;
public Node(Node<E> prev,E element, Node<E> next) {
this.prev=prev;
this.element = element;
this.next = next;
}
@Override
public String toString(){
//用于打印链表的所有连接关系
StringBuilder sb=new StringBuilder();
if(prev!=null)
sb.append(prev.element);
else
sb.append("null");
sb.append("_").append(element).append("_");
if(next!=null)
sb.append(next.element);
else
sb.append("null");
return sb.toString();
}
}
private Node<E> first;//头节点
private Node<E> last;//尾节点
@Override
public int indexOf(E element) {
Node<E> node=first;
//如果元素为空,只需要考虑链表元素为空
if(element==null){
for(int i=0;i<size;i++){
if(node.element==null)return i;
node=node.next;
}
/*while(node!=null){
...
node=node.next;
}*/
}else{//元素不为空则调用equals方法判断
for(int i=0;i<size;i++) {
if (element.equals(node.element)) return i;
node=node.next;
}
}
//未找到则返回常量-1
return ELEMENT_NOT_FOUND;
}
@Override
public void clear() {
//将头尾指针置空,所有节点对象将不被gc root对象引用
//gc root对象,如被栈指针引用的对象
last=null;
first=null;
size=0;
}
@Override
public void add(int index, E element) {
rangeCheckForAdd(index);//index合理范围0-size
if(index==size){//从尾部插入节点
Node<E> oldLast=last;//获取前节点
last=new Node<E>(oldLast,element,null);
if(oldLast==null)//若是插入的第一个节点
first=last;
else
oldLast.next=last;
}else{
Node<E> next=node(index);//获取插入后的后节点
Node<E> prev=next.prev;//获取前节点
Node<E> node=new Node<E>(prev,element,next);
next.prev=node;
if(prev==null)//前节点为空,相当于从头插
first=node;
else
prev.next=node;
}
size++;
}
@Override
public E remove(int index) {
rangeCheck(index);
//获取要删除的节点
Node<E> node=node(index);
//前驱节点
Node<E> prev=node.prev;
//后继节点
Node<E> next=node.next;
if(prev==null)
first=next;//修改头节点位置
else
prev.next=next;
if(next==null)
last=prev;
else
next.prev=prev;
size--;
return node.element;
}
@Override
public E get(int index) {
Node<E> node=node(index);
return node.element;
}
@Override
public E set(int index, E element) {
Node<E> node=node(index);
E oldElement=node.element;
node.element=element;
return oldElement;
}
//获取索引为index的节点
private Node<E> node(int index){
rangeCheck(index);//index的取值只能是0-size-1
//判断从头往后还是从后往前获取节点更快
if(index<(size>>1)){
Node<E> node=this.first;
for(int i=0;i<index;i++)
node=node.next;
return node;
}else{
Node<E> node=this.last;
for(int i=size-1;i>index;i--)
node=node.prev;
return node;
}
}
@Override
public String toString() {
StringBuilder sb=new StringBuilder("[");
Node<E> node=first;
while(node!=null){
//判断是否是最后一个节点
if(node.next!=null)
sb.append(node).append(" ,");
else
sb.append(node);
node=node.next;
}
sb.append("]");
return sb.toString();
}
}
通过打印节点的连接关系观察对错
public class DoubleCIircleTest {
public static void main(String[] args) {
DoubleCircleLinkedList<Integer> list=new DoubleCircleLinkedList<>();
for(int i=0;i<10;i++)
list.add(i);
System.out.println(list);//[0,1,2,...,9]
list.add(0,100);
System.out.println(list);//[100,0,1,2,...,9]
list.add(1,101);
System.out.println(list);//[100,101,0,1,2,...,9]
list.remove(0);
System.out.println(list);//[101,0,1,2,...,9]
list.remove(1);
System.out.println(list);//[101,1,2,...,9]
list.remove(list.size()-1);
System.out.println(list);//[101,1,2,...,8]
System.out.println(list.get(2));//2
list.set(2,99);
System.out.println(list.get(2));//99
list.clear();
System.out.println(list);//[]
}
}
结果为:
可以发现节点的数据和前后连接关系都没有问题。