目录
前言
循环链表是链表的一个变种,它指的是头结点与尾结点相连的链表结构。
一、循环链表的定义
首尾相连的链表结构
二、循环单链表
1.循环单链表的实现
循环单链表的结点类与普通单链表的结点类相同,不同的只是链表泛型类。
public class CLinkedListClass<E> {
CLinkedNode<E> cHead;
public CLinkedListClass(){
cHead = new CLinkedNode<E>();
cHead.next = cHead; //循环链表的尾结点的后继结点不为null,而是其头结点,这里是构建一个空的循环单链表
}
}
由于循环单链表中的尾结点的后继结点是其头结点,所以循环遍历的条件就变成了pointer.next!=cHead。而其他的方法与普通单链表一致。
由于循环链表首尾相连的特性,所以我们一般不设置头结点,而是让其尾结点与首结点相连,方便操作。
2.循环单链表的应用
循环单链表的应用就涉及到一个经典问题:Joseph问题。有n个小孩围成一圈,给他们从1开始依次编号,并从1号开始依次报数,数到m的小孩出列,然后下一个小孩开始报数,重复以上操作,直到全部出列位置,求出列顺序。
解决这个问题,我们要先创建一个结点类,名为Kid
/**
* 循环单链表结点类,表示一个学生
*/
public class Kid {
int No;
Kid next;
public Kid(int No){
this.No = No;
next = null;
}
}
然后再创建一个Joseph问题求解类,其中,我们需要一个通过传入小孩数量n来创建循环单链表的构造方法,和一个传入出列序号m求解的方法。
/**
* Joseph问题求解类
*/
public class Joseph {
Kid first;
int n;
/**
* 构造函数,创建长度为n的循环单链表
* @param n: 有几个学生
*/
public Joseph(int n){
this.n = n;
first = new Kid(1); //将首结点设计为No为1的孩子
Kid pointer = first; //指针指向第一个结点,也就是首结点
Kid newKid; //表示新创建的Kid结点
for (int i = 2; i <=n ; i++) { //由于已经有了No为1的结点,所以从No为2开始添加
pointer.next = new Kid(i); //因为是从小到大的顺序排列,为了代码方便,采用尾插法
pointer = pointer.next;
}
pointer.next = first; //将尾结点与首结点相连
}
/**
* Joseph问题求解方法
* @param m: 第几个学生出列
* @return
*/
public String solution(int m){
Kid pointer = first; //指针指向首结点
int num; //小孩报的数
String answer = ""; //要返回的字符串
for (int i=0;i<n;i++){ //第一层循环:所有小孩出列,有多少个小孩就要循环多少次
num = 1; //初始化报的数为1
while (num<=m-2){
pointer = pointer.next; //移动指针
num++;
}
answer += pointer.next.No + " "; //先获取下一个结点的No,删除后就获取不到了
pointer.next = pointer.next.next; //单链表删除操作
pointer = pointer.next;
}
return answer;
}
这个问题的关键在于要想清楚是多少次循环:
三、循环双链表结构
1.循环双链表的实现
循环双链表的结点类也与普通双链表相同,有区别的只是双链表泛型类:
public class CDLinkedClass<E> {
CDLinkedNode<E> cdHead;
CDLinkedClass(){
cdHead = new CDLinkedNode<E>();
cdHead.next = cdHead;
cdHead.prior = cdHead;
}
}
2.循环双链表的应用
有一个带头结点的循环双链表L,其结点的data成员为整数,设计算法判断其所有元素是否对称。
这个问题其实可以不通过循环双链表解决,只需要创建一个链表,以头插法得到一个相反的链表,然后再一一比较,但这样的代码又浪费储存空间(建立新链表),又浪费时间(多次遍历)。用了循环双链表后,只需要一次遍历即可。
思路:设置双指针,一个从头结点顺序遍历,另一个同时从尾结点逆向遍历,然后比较,如果不相同则跳出循环。
public static boolean symmetry(CDLinkedClass<Integer> L){
boolean flag = true; //表示是否对称
CDLinkedNode<Integer> seqPointer = L.cdHead.next; //循序遍历的指针
CDLinkedNode<Integer> revPointer = L.cdHead.prior; //逆向遍历的指针
while (flag){
if (seqPointer.data!=revPointer.data){ //如果两个指针data不一样,则不为对称,flag=false,循环结束
flag = false;
}else if(seqPointer==revPointer || seqPointer.next==revPointer){ //如果两个结点相遇,则遍历完毕,跳出循环
break;
}
seqPointer = seqPointer.next;
revPointer = revPointer.prior;
}
return flag;
}
总结
总之,循环链表就是链表的一个变种,在熟练普通链表后,循环链表不是什么难事