1.2.2 线性表 - 单循环链表

1 单循环链表的定义

在单链表中,将终端结点的指针域NULL改为指向表头结点或开始结点,整个链表形成一个环。
在这里插入图片描述

2 单循环链表与单链表的差异

  • 空链表的判断条件
  • 非空链表的循环条件
空链表非空链表的循环条件
单循环链表head->Next == headp->Next == head
单链表head->Next == NULLp==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个人形成一个单向循环链表,求最后剩余的两个节点。
在这里插入图片描述
解题思路:

  1. 单向循环链表无头结点
  2. 每次报3的结点删除掉
  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)
    解决方法:双向链表
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值