一、单向循环链表
单向循环链表是单链表的另一种形式,其结构特点是链表中最后一个结点的指针不再是结束标记,而是指向整个链表的第一个结点,从而使单链表形成一个环。和单链表相比,循环单链表的长处是从链尾到链头比较方便。当要处理的数据元素序列具有环型结构特点时,适合于采用循环单链表。
和单链表相同,循环单链表也有带头结点结构和不带头结点结构两种,带头结点的循环单链表实现插入和删除操作时,算法实现较为方便。带头结点的循环单链表结构如下:
带头结点的循环单链表的操作实现方法和带头结点的单链表的操作实现方法类同,差别仅在于:(a) 空链表 (b)非空链表
(1)在构造函数中,要加一条head.next = head 语句,把初始时的带头结点的循环单链表设计成图2-11 (a)所示的状态。
(2)在index(i)成员函数中,把循环结束判断条件current != null改为current != head。
实现:
public interface List { //插入元素 public void add(int index,Object obj) throws Exception; //删除元素 public void delete(int index) throws Exception; //获取某个元素 public Object get(int index) throws Exception; //判断是否为空 public boolean isEmpty(); //获得集合的长度 public int size(); }
//节点类 public class Node { //数据域 Object element; //指针域 Node next; //头结点的构造方法 public Node(Node nextval) { this.next=nextval; } //非头结点的额构造方法 public Node(Object obj,Node nextval) { this.element=obj; this.next=nextval; } //获取当前数据域的值 public Object getElement() { return this.element; } //获取当前节点的后继节点 public Node getNode() { return this.next; } //设置当前数据域的值 public void setElement(Object obj) { this.element=obj; } //设置当前节点的指针域 public void setNode(Node node) { this.next=node; } public String toString() { return this.element.toString(); } }
public class CycleLinkedList implements List { //头指针 Node head; //当前节点对象 Node current; //节点个数 int size; public CycleLinkedList() { //初始化头结点,让头指针指向头结点,并且让当前节点对象等于头结点 this.head=current=new Node(null); this.size=0; this.head.next=head; } //定为函数,将当前节点对象定为到前一个节点 public void index(int index) throws Exception { if(index<-1||index>size-1) { throw new Exception("参数错误!"); } //说明在头结点之后操作 if(index==-1) return; current=head.next; //循环变量 int j=0; while(current!=head&&j<index) { current=current.next; j++; } } //增加元素 @Override public void add(int index, Object obj) throws Exception { if(index<0||index>size) { throw new Exception("增加元素参数错误!"); } //定为到操作对象的前一个对象 index(index-1); current.setNode(new Node(obj,current.next)); size++; } //删除元素 @Override public void delete(int index) throws Exception { if(isEmpty()) { throw new Exception("链表为空,删除错误!"); } if(index<0||index>size) { throw new Exception("删除元素参数错误!"); } index(index-1); current.setNode(current.next.next); size--; } @Override public Object get(int index) throws Exception { if(index<-1||index>size-1) { throw new Exception("参数错误!"); } index(index); return current.getElement(); } @Override public boolean isEmpty() { return size==0; } @Override public int size() { return size; } }
import java.util.Random; public class Main { public static void main(String[] args) throws Exception { CycleLinkedList list=new CycleLinkedList(); Random random=new Random(); for (int i = 0; i < 10; i++) { int temp=random.nextInt(100); list.add(i, temp); System.out.print(temp+" "); } System.out.println(); for(int j=0;j<list.size();j++) { System.out.print(list.get(j)+" "); } System.out.println(); list.delete(5); for(int j=0;j<list.size();j++) { System.out.print(list.get(j)+" "); } } }结果:
二、双向循环链表
双向链表是每个结点除后继指针外还有一个前驱指针。和单链表类同,双向链表也有带头结点结构和不带头结点结构两种,
带头结点的双向链表更为常用;另外,双向链表也可以有循环和非循环两种结构,循环结构的双向链表更为常用。
在双向链表中,每个结点包括三个域,分别是element域、next域和prior域,其中element域为数据元素域,next域为指向
后继结点的对象引用,prior域为指向前驱结点的对象引用。下图为双向链表结点的图示结构。
双向链表的图示结构
如下图是带头结点的循环双向链表的图示结构。循环双向链表的next和prior各自构成自己的循环单链表。
在双向链表中,有如下关系:设对象引用p表示双向链表中的第i个结点,则p.next表示第i+1个结点,p.next.prior仍表示
第i个结点,即p.next.prior == p;同样地,p.prior表示第i-1个结点,p.prior.next仍表示第i个结点,即p.prior.next == p。下图是
双向链表上述关系的图示。
循环双向链表的插入过程如下图所示。图中的指针p表示要插入结点的位置,s表示要插入的结点,①、②、③、④表示
实现插入过程的步骤。
循环双向链表的删除过程如下图所示。图中的指针p表示要插入结点的位置,①、②表示实现删除过程的步骤。
实现:
//节点类 public class Node { //数据域 Object element; //后继指针域 Node next; //前驱指针域 Node prior; //头结点的构造方法 public Node(Node nextval) { this.next=nextval; } //非头结点的额构造方法 public Node(Object obj,Node nextval) { this.element=obj; this.next=nextval; } //获取当前数据域的值 public Object getElement() { return this.element; } //获取当前节点的后继节点 public Node getNext() { return this.next; } //获取当前节点的前驱节点 public Node getPrior() { return this.prior; } //设置当前数据域的值 public void setElement(Object obj) { this.element=obj; } //设置当前后继节点的指针域 public void setNext(Node node) { this.next=node; } //设置当前前驱节点的指针域 public void setPrior(Node priorval) { this.prior=priorval; } public String toString() { return this.element.toString(); } }
public class DoubleCycleLinkedList implements List { //头指针 private Node head; //当前节点对象 private Node current; //节点个数 int size; public DoubleCycleLinkedList() { //初始化头结点,让头指针指向头结点,并且让当前节点对象等于头结点 this.head=current=new Node(null); this.size=0; this.head.next=head; this.head.prior=head; } //定为函数,将当前节点对象定为到前一个节点 public void index(int index) throws Exception { if(index<-1||index>size-1) { throw new Exception("参数错误!"); } //说明在头结点之后操作 if(index==-1) return; current=head.next; //循环变量 int j=0; while(current!=head&&j<index) { current=current.next; j++; } } //增加元素 @Override public void add(int index, Object obj) throws Exception { if(index<0||index>size) { throw new Exception("增加元素参数错误!"); } //定为到操作对象的前一个对象 index(index-1); current.setNext(new Node(obj,current.next)); current.next.setPrior(current); current.next.next.setPrior(current.next); size++; } //删除元素 @Override public void delete(int index) throws Exception { if(isEmpty()) { throw new Exception("链表为空,删除错误!"); } if(index<0||index>size) { throw new Exception("删除元素参数错误!"); } index(index-1); current.setNext(current.next.next); current.next.setPrior(current); size--; } @Override public Object get(int index) throws Exception { if(index<-1||index>size-1) { throw new Exception("参数错误!"); } index(index); return current.getElement(); } @Override public boolean isEmpty() { return size==0; } @Override public int size() { return size; } }
public class Main { public static void main(String[] args) throws Exception { DoubleCycleLinkedList list=new DoubleCycleLinkedList(); Random random=new Random(); for (int i = 0; i < 10; i++) { int temp=random.nextInt(100); list.add(i, temp); System.out.print(temp+" "); } System.out.println(); for(int j=0;j<list.size();j++) { System.out.print(list.get(j)+" "); } System.out.println(); list.delete(5); for(int j=0;j<list.size();j++) { System.out.print(list.get(j)+" "); } } }
三、仿真链表
在链式存储结构中,我们实现数据元素之间的次序关系依靠指针。我们也可以用数组来构造仿真链表。方法是在数组中增加一个(或两个)int类型的变量域,这些变量用来表示后一个(或前一个)数据元素在数组中的下标。我们把这些int类型变量构造的指针称为仿真指针。这样,就可以用仿真指针构造仿真的单链表(或仿真的双向链表)。
(a)常规单链表 (b)仿真单链表一 (c)仿真单链表二
四、循环链表应用
编写击鼓传花小游戏。
游戏规则:N个人围成一个圈,从第一个人开始传花,当数到M时,该人退出游戏,直到剩下最后一个人。
//击鼓传花 思路:将num个数加入到单向循环链表中,从第一个数开始数,数到key,将key置为-1,最后剩下的一个不为-1的数就是余下的那个人 public class Game { CycleLinkedList list=new CycleLinkedList(); //总人数 private int num; //数到几退出 private int key; public Game(int num,int key) { this.num=num; this.key=key; } public void play() throws Exception { for (int i = 0; i < num; i++) { list.add(i, i); } System.out.println("游戏开始之前............"); for (int i = 0; i < list.size(); i++) { System.out.print(list.get(i)+" "); } System.out.println(); System.out.println("游戏开始之后............"); //开始等于总人数 int iCount=num; //累加器,看能否被key整除 int j=0; Node node=list.head; while(iCount!=1) { if(node.getElement()!=null&&Integer.parseInt(node.getElement().toString())!=-1) { j++; if(j%key==0) { node.setElement(-1); iCount--; System.out.println(); for (int i = 0; i < list.size(); i++) { System.out.print(list.get(i)+" "); } } } node=node.next; } System.out.println(); System.out.println("游戏结束..............."); for (int i = 0; i < list.size(); i++) { System.out.print(list.get(i)+" "); } } public static void main(String[] args) throws Exception { Game game=new Game(6,3); game.play(); } }
输出结果: