循环链表是另一种形式的链式存储结构。 它的特点是表中最后一个结点的 指针 域指向 头结点 ,整个链表形成一个环。
单循环链表——在 单链表 中,将终端结点的 指针 域NULL改为指向表头结点或开始结点即可。 多重链的循环链表——将表中结点链在多个环上。 用 尾指针tail表示的单循环链表对开始结点a1和终端结点an查找时间都是O (1)。 而表的操作常常是在表的首尾位置上进行,因此,实用中多采用尾指针表示单循环链表。
(转载自网络, 侵删)
单向循环链表与单向链表类似(具体参见单向链表), 下面主要提到其不同之处;
下面是循环链表的表头定义: 对比单向链表多了一个tail指针;
typedef struct LinkList{
ListNode head, *tail; // head为虚拟头结点, 其val为链表; tail为尾指针, 指向链表的最后一个元素
}LinkList;
表头的初始化: 至于为什么list->tail与list->head.next要先初始化成&(list->head)
可以感性的理解为循环链表需要首尾相接, 最初的空链表头尾指针重合
具体原因与下面各个函数实现均有关, 这里不做详细说明。
LinkList *initLinkList(){ // 初始化一个空链表
LinkList *list = (LinkList *)malloc(sizeof(LinkList));
list->head.next = list->tail = &(list->head);
// tail指向链表元素结点中的最后一个结点,
// 并且满足tail->head = &(tail->head), 初始tail为&(head->head);
list->head.val = 0; // 利用虚拟头结点head的val成员存储链表长度, 并在insert及erase中维护;
return list;
}
由于尾结点的存在, 使得尾插法得以O(1)的时间复杂度实现
链表的插入操作:
bool insert(LinkList *list, int ind, int val){
// printf("ind = %d, list->length = %d\n", ind, list->head.val);
if(list == NULL || ind < 0 || ind > list->head.val) return false; // 每种操作都要进行的合法性判断, 数据结构安全性的保证;
// 上面利用 || 运算的短路性保证了判断的合法性
// 利用initListNode()函数生成所需结点
ListNode *node = initListNode(val);
if(ind == list->head.val){ // list->head.val 即为list->length; 也就是此时插入为尾插
list->tail->next = node; // 先将之前的尾结点的next指向待插入结点
list->tail = node; // 再将链表尾指针指向该节点, 即尾结点的更新
list->tail->next = &(list->head);
}else{ // 否则为非尾插, 不需要更新尾结点
ListNode *p = &(list->head);
while(ind--) p = p->next;
// 此时p为待插入位置的前一个元素
// p->next为待插入位置的后一个元素
node->next = p->next; // 此行代码与下一行不可交换
p->next = node; // 否则会丢失p->next的引用
}
// 至此完成插入, 不要忘记维护链表长度(list->head.val += 1)
list->head.val++;
return true;
}
链表的删除操作:
bool erase(LinkList *list, int ind){
if(list == NULL || ind < 0 || ind >= list->head.val) return false;
ListNode *p = &(list->head);
if(ind == list->head.val - 1){ // 同尾插, 尾部元素的删除也需要维护tail
while(ind--) p = p->next;
// 此时p为list->tail的前驱, 也即新尾结点;
clearListNode(list->tail); // 先释放掉尾结点的空间
list->tail = p; // 然后尾指针前移
p->next = &(list->head); // 维护链表的循环性
}else{ //同非尾插
while(ind--) p = p->next;
ListNode *q = p->next->next;
clearListNode(p->next);
p->next = q;
}
//最后维护链表长度
list->head.val--;
return true;
}
由于list->tail->next != NULL 链表的遍历也与单向链表有所区别
单向循环链表的遍历:
void output(LinkList *list){
if(list == NULL) return;
printf("LinkList(%d): Head-> ", list->head.val);
for(ListNode *p = list->head.next; p != &(list->head); p = p->next)
printf("%d-> ", p->val);
puts("Head\n");
}
下面是完整实现代码:
#include<bits/stdc++.h>
using namespace std;
typedef struct ListNode{
ListNode *next;
int val;
}ListNode;
typedef struct LinkList{
ListNode head, *tail; // head为虚拟头结点, 其val为链表; tail为尾指针, 指向链表的最后一个元素
}LinkList;
ListNode *initListNode(int val){ // 初始化一个权值为val的结点并返回;
ListNode *node = (ListNode *)malloc(sizeof(ListNode));
node->next = NULL;
node->val = val;
return node;
}
LinkList *initLinkList(){ // 初始化一个空链表
LinkList *list = (LinkList *)malloc(sizeof(LinkList));
list->head.next = list->tail = &(list->head);
// tail指向链表元素结点中的最后一个结点,
// 并且满足tail->head = &(tail->head), 初始tail为&(head->head);
list->head.val = 0; // 利用虚拟头结点head的val成员存储链表长度, 并在insert及erase中维护;
return list;
}
void clearListNode(ListNode *node){
if(node == NULL) return;
free(node);
return;
}
void clearLinkList(LinkList *list){
if(list == NULL) return;
ListNode *p = list->head.next, *q;
while(p != &(list->head)){
q = p->next;
clearListNode(p);
p = q;
}
free(list);
return;
}
// 链表的增删改查
bool insert(LinkList *list, int ind, int val){
// printf("ind = %d, list->length = %d\n", ind, list->head.val);
if(list == NULL || ind < 0 || ind > list->head.val) return false; // 每种操作都要进行的合法性判断, 数据结构安全性的保证;
// 上面利用 || 运算的短路性保证了判断的合法性
// 利用initListNode()函数生成所需结点
ListNode *node = initListNode(val);
if(ind == list->head.val){ // list->head.val 即为list->length; 也就是此时插入为尾插
list->tail->next = node; // 先将之前的尾结点的next指向待插入结点
list->tail = node; // 再将链表尾指针指向该节点, 即尾结点的更新
list->tail->next = &(list->head);
}else{ // 否则为非尾插, 不需要更新尾结点
ListNode *p = &(list->head);
while(ind--) p = p->next;
// 此时p为待插入位置的前一个元素
// p->next为待插入位置的后一个元素
node->next = p->next; // 此行代码与下一行不可交换
p->next = node; // 否则会丢失p->next的引用
}
// 至此完成插入, 不要忘记维护链表长度(list->head.val += 1)
list->head.val++;
return true;
}
bool erase(LinkList *list, int ind){
if(list == NULL || ind < 0 || ind >= list->head.val) return false;
ListNode *p = &(list->head);
if(ind == list->head.val - 1){ // 同尾插, 尾部元素的删除也需要维护tail
while(ind--) p = p->next;
// 此时p为list->tail的前驱, 也即新尾结点;
clearListNode(list->tail); // 先释放掉尾结点的空间
list->tail = p; // 然后尾指针前移
p->next = &(list->head); // 维护链表的循环性
}else{ //同非尾插
while(ind--) p = p->next;
ListNode *q = p->next->next;
clearListNode(p->next);
p->next = q;
}
//最后维护链表长度
list->head.val--;
return true;
}
bool update(LinkList *list, int ind, int val){
if(list == NULL || ind < 0 || ind >= list->head.val) return false; // 同上
ListNode *p = &(list->head);
while(ind--) p = p->next;
p->val = val;
return true;
}
void output(LinkList *list){
if(list == NULL) return;
printf("LinkList(%d): Head-> ", list->head.val);
for(ListNode *p = list->head.next; p != &(list->head); p = p->next)
printf("%d-> ", p->val);
puts("Head\n");
}
int main(){
#define MAX_N 100
srand(time(0));
LinkList *list = initLinkList();
for(int i = 0; i < MAX_N; i++){
int op = rand() % 6, val = rand() * rand() % 10000000, ind = rand() % (list->head.val + 1);
switch(op){
case 0:
if(erase(list, ind)) printf("Erase elem at %d from the list\n", ind);
else puts("Failed to erase elem from the list");
break;
case 1:
case 2:
case 3:
case 4:
case 5:
if(insert(list, ind, val)) printf("Insert %d at %d to the list\n", val, ind);
else puts("Failed to insert elem to the list");
}
output(list);
}
clearLinkList(list);
printf("%d", list->head.val);
return 0;
}