循环链表
概念
将单链表中的尾结点的指针由空指针指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表称为单循环链表,简称循环链表。
【注意】
因为循环链表没有NULL来表示链表的结束,因而遍历循环链表时需要特别小心,否则将会无线遍历循环链表,因为在循环链表中每个结点都有一个后续结点。
【作用】
因为循环链表中没有next指针为NULL的结点。所以,循环链表有时非常有用,比如:当多个进程需要在相同的时间内使用同一个计算机资源(CPU)时,必须确保在所有其他进程使用这些资源前,没有进程访问该资源(轮询算法)。
循环链表示意图如下:
循环链表的插入
主要操作如下:
◆统计循环链表的结点个数
循环链表可以通过标记为表头的结点进行访问。为了统计结点个数,只能从标记为表头的结点开始遍历,利用虚拟结点一一计数当前结点,当当前结点再次到达开始结点时,结束计数过程。若链表为空,表头结点为NULL,在这种情况下设结点个数等于0.否则,设置当前结点指向第一个结点(表头结点),然后遍历链表进行计数,直到当前结点达到开始结点。其主要代码如下:
int CircularListLength(CLLNode headNode){
int length=0;
CllNode currentNode=headNode;
while(currentNode!=null){
length++;
currentNode=currentNode.getNext();
if(currentNode==headNode)
break;
}
return length;
}
时间复杂度为0(n),用于扫描长度为n的链表。
空间复杂度为0(1),用于创建一个临时变量。
◆输出循环链表的内容
假设循环链表可以通过表头结点进行访问。由于所有的结点采用循环方式排列,所以链表的表头结点的前驱就是表尾结点。假设要输出从表头结点开始的结点内容,输出结点内容,移动到下一个结点,继续输出直至再次达到表头结点。其主要代码如下:
void PrintCircularData(CLLNode headNode){
CLLNode CLLNode=headNode;
while(CLLNode!=null){
System.out.println(CLLNode.getData()+"->");
CLLNode=CLLNode.getNext();
if(CLLNode==headNode)
break;
}
System.out.println("("CLLNode.getData()+")headNode");
}
时间复杂度为0(n),用于扫描长度为n的链表。
◆在循环链表的表尾插入结点
- 创建一个新节点,并且初始化其next指针指向该结点自身
- 更新新结点的next指针为表头结点,然后遍历循环链表直至表尾
- 更新表头的前驱结点的next指针指向新结点,得到循环链表
其主要代码如下:
void InsertAtEndInCLL(CLLNode headNode,CLLNode nodeToInsert){
CLLNode currentNode=headNode; //创建新结点
whlie(currentNode.getNext()!=headNode){
currentNode.setNext(currentNode.getNext());
}
nodeToInsert.setNext(nodeToInsert);
if(headNode==null) //判断头结点是否为空
headNode=nodeToInsert;
else{
nodeToInsert.setNext(headNode);
currentNode.setNext(nodeToInsert);
}
}
时间复杂度为0(n),用于扫描长度为n的链表。
◆在循环链表的表头插入结点
- 创建一个新节点,并且初始化其next指针指向结点自身
- 更新新结点的next指针为表头指针,然后遍历循环链表直至表尾
- 更新链表中表头的前驱结点的next指针,使其指向新结点
- 设置新结点为表头结点
其主要代码如下:
void InsertAtBeginInCLL(CLLNode headNode,CLLNode nodeToInsert){
CLLNode currentNode=headNode; //创建新结点
whlie(currentNode.getNext()!=headNode){
currentNode.setNext(currentNode.getNext());
} //遍历循环链表直至表尾
nodeToInsert.setNext(nodeToInsert);
if(headNode==null) //判断头结点是否为空
headNode=nodeToInsert;
else{ //设置新结点为表头结点
nodeToInsert.setNext(headNode);
currentNode.setNext(nodeToInsert);
headNode=nodeToInsert;
}
}
时间复杂度为0(n),用于扫描长度为n的链表。
循环链表的删除
■删除循环链表中最后一个结点
- 遍历循环链表,找到表尾结点及其前驱结点
- 更新表尾结点的前驱结点的next指针,使其指向表头结点
- 移除表尾结点
其主要代码如下:
void DeleteLastNodeFromCLL(CLLNode headNode){
CLLNode temp=headNode;
CLLNode currentNode=headNode;
if(headNode==null){
System.out.println("链表为空");
return;
}
whlie(currentNode.getNext()!=headNode){
temp=currentNode;
currentNode.setNext(currentNode.getNext());
} //遍历循环链表,找到表尾结点及其前驱结点
temp.setNext(headNode);
currentNode=null;
return;
}
时间复杂度为0(n),用于扫描长度为n的链表。
■删除循环链表中的第一个结点
- 遍历循环链表找到表尾结点。
- 更新表尾结点的next指针,使其指向第一个结点的后继结点
- 移除第一个结点
其主要代码如下:
void DeleteFirstNodeFromCLL(CLLNode headNode){
CLLNode temp=headNode;
CLLNode currentNode=headNode;
if(headNode==null){
System.out.println("链表为空");
return;
}
whlie(currentNode.getNext()!=headNode){
currentNode.setNext(currentNode.getNext());
} //遍历循环链表,找到表尾结点及其前驱结点
currentNode.setNext(headNode.getNext());
headNode=headNode.getNext();
temp=null;
return;
}
时间复杂度为0(n),用于扫描长度为n的链表。