1 单循环链表的定义
在单链表中,将终端结点的指针域NULL改为指向表头结点或开始结点,整个链表形成一个环。
2 单循环链表与单链表的差异
- 空链表的判断条件
- 非空链表的循环条件
空链表 | 非空链表的循环条件 | |
---|---|---|
单循环链表 | head->Next == head | p->Next == head |
单链表 | head->Next == NULL | p==NULL或p->Next ==NULL |
3 单循环链表操作的实现
3.1 初始化一条空链表
linklist init_list(){
// 申请一个头结点
linklist head = calloc(1, sizeof(listnode));
if( head != NULL) {
head->Next = head;
}
return head;
}
3.2 产生一个新节点
linklist new_node(datatype data){
// 分配一个内存
linklist new = calloc(1, sizeof(listnode));
if ( new!=NULL){
new->Data = data;
new->Next = new;
}
return new;
}
3.3 把新节点加到末尾
void list_add_tail(linklist new, linklist head ) {
// 0 定位末尾结点
linklist tail = head->Next;
while (tail->Next != head ){
tail = tail->Next;
}
// 1 将new指向原链表的头
new->Next = head;
// 2 将末尾结点指向新节点
tail->Next = new;
}
3.4 判断链表是否为空
bool is_empty(linklist head) {
return head == head->Next;
}
3.5 输出链表元素
void show(linklist head) {
if (is_empty(head)){
return;
}
linklist tmp = head->Next;
for (tmp; tmp!=head; tmp=tmp->Next){
printf("%d ", tmp->Data);
}
printf("\n");
}
3.6 释放所有结点
void destory(linklist head) {
linklist p, s;
s = head->Next;
// 释放所有正常结点
do {
p = s;
s = s->Next;
free(p);
}while(s!=head);
// 释放头结点
free(head);
}
4 带尾指针的单循环链表
4.1 定义
使用rear指针指向链表的尾端,称为尾指针表示的循环链表
4.2 特点
- 带头结点的单循环链表找头结点时间耗费为O(1),找表尾结点时间耗费为O(n)(需要遍历整个链表)
- 带尾指针的单循环链表找表头结点和表尾结点耗时都是O(1)
- 查找头节点:rear->Next;查找开始结点:rear->Next->Next
- 利用尾指针判断空链表的条件:rear== rear->Next
5 练习
据说着名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,犹太人与Josephus及他的朋友躲到一个洞中,族人决定宁愿死也不要被敌人到,于是决定了一个自杀方式,所有人排成一个圆圈,由第1个人 开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。
然而Josephus 和他的朋友并不想死,Josephus要他的朋友先假装遵从,他将朋友与自己安排在两个特殊的位置,于是逃过了这场死亡游戏。现在假设有n个人形成一个单向循环链表,求最后剩余的两个节点。
解题思路:
- 单向循环链表无头结点
- 每次报3的结点删除掉
- 最后剩下2人,所以当轮到自杀的那个人,如果他接下去两个人还是他自己,那就只剩下两人
#include "common.h"
#ifndef LL_NODE_TYPE
#define LL_NODE_TYPE int // 默认链表节点内的数据类型是int
#endif
typedef LL_NODE_TYPE datatype;
typedef struct node {
datatype Data;
struct node *Next;
}listnode, *linklist;
// 1. 初始化一条空链表,无头结点,返回空
linklist init_list(){
return NULL;
}
// 2.1 产生一个新节点
linklist new_node(datatype data){
// 分配一个内存
linklist newNode = calloc(1, sizeof(listnode));
if ( newNode!=NULL){
newNode->Data = data;
newNode->Next = newNode;
}
return newNode;
}
// 2.2 把新节点加到末尾
linklist list_add_tail(linklist newNode, linklist head ) {
if( head == NULL){
newNode->Next = newNode;
head = newNode;
return head;
}
// 否则
// 0 定位末尾结点
linklist tail = head;
while( tail->Next != head) {
tail = tail->Next;
}
// 1 将new指向原链表的头
newNode->Next = head;
// 2 将末尾结点指向新节点
tail->Next = newNode;
return head;
}
// 判断链表是否为空
bool is_empty(linklist head) {
// return head->Data.UserData == 0;
return head == head->Next;
}
// 3 输出无头结点链表元素
void show(linklist head) {
if (is_empty(head)){
return;
}
linklist tmp = head;
do{
printf("%d\t", tmp->Data);
tmp = tmp->Next;
}while(tmp != head);
printf("\n");
}
// 4.1 删除结点,并返回下一结点
linklist del(linklist delNode) {
if( delNode == NULL){
return NULL;
}
// 0.寻找被删结点的上一结点
linklist tail = delNode;
while ( tail->Next != delNode) {
tail = tail->Next;
}
// 1. 把删除结点从单链表弄出来
tail->Next = delNode->Next;
delNode->Next = NULL; // 被删链表别乱指
return tail->Next;
}
// 4 自杀
linklist kill3(linklist head) {
if ( head == NULL){
return NULL;
}
linklist delNode = head->Next->Next; // 需要自杀的人
linklist tmp; // 自杀后的下一个人
printf("自杀顺序:\n");
while (delNode != delNode->Next->Next) { // 3人自杀一个
printf("%d\t", delNode->Data);
tmp = del(delNode);
free(delNode);
delNode = tmp->Next->Next;
}
printf("\n");
return delNode;
}
// 销毁无结点链表
void destroy(linklist head) {
linklist p, s;
s = head->Next;
do {
p = s;
s = s->Next;
free(p);
} while(s!=head);
free(head);
}
int main(void){
// 1. 初始化一条空链表
linklist head = init_list();
// 2. 放入一些整数
int n;
scanf("%d", &n);
int i;
for (i=1; i<=n; i++){
// 产生一个新节点
linklist newNode = new_node(i);
// 把新节点加到末尾
head = list_add_tail(newNode, head);
}
// 3. 输出链表的元素
show(head);
// 4. 自杀
linklist exist = kill3(head);
// 显示存活的人
printf("存活的序号:\n");
show(exist) ;
// 销毁链表
destroy(exist);
}
代码引用常用头文件,如果报错可以参考另外一篇常用头文件 - common.h
6 单循环链表的优缺点
- 已知任一结点就可以找到其它的结点
- 查找后继元素(NextElement)的执行时间为O(1);但是查找前驱元素(PriorElement)的执行时间为O(n)
解决方法:双向链表