数据结构(笔记)——单向循环链表

1.单向循环链表的定义

单向链表:每个节点只有一个指针域,指向下一个节点。

循环链表:链表的最后一个节点指针不是 NULL,而是指向头节点。

所以 单向循环链表 就是:

链表中的最后一个节点的 next 指针指向头节点,而不是空指针,从而形成一个 环状结构。

2.特点

特点 描述
存储结构 用指针将节点串联起来
访问方式 只能沿着 next 方向单向访问
尾节点指针 list->last->next 指向头节点
没有空指针结尾 任意节点开始遍历,都能回到起点
节点插入删除 不需要移动其他节点,只改指针即可
判空条件 head == NULL

3.优缺点

优点

可以从任意一个节点开始遍历,形成一个闭环结构。

在循环处理中比较方便(例如:约瑟夫问题)。

插入、删除操作不需要整体移动元素。

缺点

只能单向遍历,不能反向。

查找效率低,必须从头开始。

代码实现比顺序表复杂。

4.实现函数

申请一个节点

cpp 复制代码
Node* _buynode(ElemType x)
{
	Node* s = (Node*)malloc(sizeof(Node));
	assert(s != NULL);
	s->data = x;
	s->next = NULL;
	return s;
}

目的:申请一个新节点,存放数据 x。

步骤:

malloc 分配一块 Node 结构体大小的内存。

assert(s != NULL) 保证申请成功。

把传入的数据 x 存到 data 域。

next 指针先置空(后续再连接)。

返回这个新节点指针。

初始化

cpp 复制代码
void InitSCList(List* list)
{
	Node* s = (Node*)malloc(sizeof(Node));
	assert(s != NULL);
	list->first = list->last = s;
	list->last->next = list->first;
	list->size = 0;
}

目的:初始化一个空的单向循环链表。

步骤:

申请一个头结点 s。

头和尾都指向这个结点,说明链表里还没有有效数据。

尾结点的 next 指向头结点,形成 环。

size=0,表示空表。

尾插

cpp 复制代码
void push_back(List* list, ElemType x)
{
	Node* s = _buynode(x);
	list->last->next = s;
	list->last = s;
	list->last->next = list->first;
	list->size++;
}

目的:尾插法,在链表最后添加一个元素。

步骤:

新建节点 s,存放 x。

旧尾节点 last->next = s,把新节点接在后面。

更新尾指针 last = s。

尾节点 next 指向头节点,保持循环。

长度 size++。

头插

cpp 复制代码
void push_front(List* list, ElemType x)
{
	Node* s = _buynode(x);
	s->next = list->first->next;
	list->first->next = s;
	if (list->first == list->last)
	{
		list->last = s;
	}
	list->size++;
}

目的:头插法,把新元素插到表头(头结点之后)。

步骤:

新建节点 s。

让 s->next 指向原来的第一个有效节点。

让头结点指向 s,完成插入。

如果插入前链表是空的(first==last),那么新节点也要成为尾节点。

长度 size++。

显示元素

cpp 复制代码
void show_list(List* list)
{
	Node* p = list->first->next;
	while (p != list->first)
	{
		printf("%d-->", p->data);
		p = p->next;
	}
	printf("Nul.\n");
}

目的:遍历并打印链表数据。

步骤:

从第一个有效节点开始(first->next)。

一直循环,直到回到头结点 first。

每次输出一个节点数据。

输出结束后打印 "Nul."。

尾删

cpp 复制代码
void pop_back(List* list)
{
	if (list->size == 0)
		return;

	Node* p = list->first;
	while (p->next != list->last)
	{
		p = p->next;
	}

	free(list->last);
	list->last = p;
	list->last->next = list->first;
	list->size--;
}

目的:删除最后一个节点。

步骤:

如果空表,直接返回。

找到尾节点的前一个节点 p。

释放原尾节点内存。

更新 last = p。

last->next = first 保持循环。

长度 --。

头删

cpp 复制代码
void pop_front(List* list)
{
	if (list->size == 0)
		return;
	Node* p = list->first->next;
	list->first->next = p->next;
	free(p);
	if (list->size == 1)
	{
		list->last = list->first;
	}
	list->size--;
}

目的:删除第一个有效节点。

步骤:

如果空表,直接返回。

取出第一个有效节点 p = first->next。

头结点绕过 p,指向 p->next。

释放 p。

如果删除后变空表,则 last=first。

长度 --。

插入值

cpp 复制代码
void insert_val(List* list, ElemType x)
{
	Node* p = list->first;
	while (p->next != list->last && p->next->data < x)
	{
		p = p->next;
	}

	if (p->next == list->last && p->next->data < x)
	{
		push_back(list, x);
	}
	else
	{
		Node* s = _buynode(x);
		s->next = p->next;
		p->next = s;
		list->size++;
	}
}

目的:按升序插入新元素 x。

步骤:

从头结点开始,找到第一个比 x 大的节点前驱 p。

如果到尾节点还比 x 小 → 直接尾插。

否则,在 p 和 p->next 之间插入新节点。

长度 ++。

查找

cpp 复制代码
Node* find(List* list, ElemType key)
{
	if (list->size == 0)
		return NULL;

	Node* p = list->first->next;
	while (p != list->first && p->data != key)
		p = p->next;

	if (p == list->first)
		return NULL;
	return p;
}

目的:查找值为 key 的节点。

步骤:

空表直接返回 NULL。

从第一个节点开始查找,直到回到头结点或找到 key。

如果回到头结点还没找到 → 返回 NULL。

否则返回找到的节点。

长度

cpp 复制代码
int length(List* list)
{
	return list->size;
}

目的:返回链表长度。

步骤:直接返回 size。

删除值

cpp 复制代码
void delete_val(List* list, ElemType key)
{
	if (list->size == 0)
		return;
	Node* p = find(list, key);
	if (p == NULL)
	{
		printf("要删除的数据不存在.\n");
		return;
	}

	if (p == list->last)
	{
		pop_back(list);
	}
	else
	{
		Node* q = p->next;
		p->data = q->data;
		p->next = q->next;
		free(q);
		list->size--;
	}
}

目的:删除值为 key 的节点。

步骤:

如果空表 → 返回。

调用 find 查找目标节点 p。

如果没找到 → 提示不存在。

如果要删的是尾节点 → 调用 pop_back。

否则:用 复制后继节点数据 的方法删除(O(1))。

把 q = p->next 的数据拷贝到 p。

删除 q 节点,相当于"跳过了它"。

size--。

排序

cpp 复制代码
void sort(List* list)
{
	if (list->size == 0 || list->size == 1)
		return;

	Node* s = list->first->next;
	Node* q = s->next;

	list->last->next = NULL;
	list->last = s;
	list->last->next = list->first;

	while (q != NULL)
	{
		s = q;
		q = q->next;

		Node* p = list->first;
		while (p->next != list->last && p->next->data < s->data)
		{
			p = p->next;
		}

		if (p->next == list->last && p->next->data < s->data)
		{
			s->next = list->last->next;
			list->last->next = s;
			list->last = s;
		}
		else
		{
			s->next = p->next;
			p->next = s;
		}
	}
}

目的:对链表进行升序排序(插入排序)。

步骤:

特殊情况:0 或 1 个节点,不用排。

先把第一个节点 s 当作有序链表,后面节点逐个插入。

遍历剩余节点 q,把每个节点 s 插入到合适位置。

插入方法:找到第一个比 s->data 大的节点之前。

如果都小于 → 插到尾部。

逆序

cpp 复制代码
void resver(List* list)
{
	if (list->size == 0 || list->size == 1)
		return;
	Node* p = list->first->next;
	Node* q = p->next;

	list->last->next = NULL;
	list->last = p;
	list->last->next = list->first;

	while (q != NULL)
	{
		p = q;
		q = q->next;

		p->next = list->first->next;
		list->first->next = p;
	}
}

目的:反转链表(头插法逆置)。

步骤:

空表/一个元素 → 不处理。

p 指第一个节点,q 指第二个节点。

暂时断开循环:last->next=NULL。

把第一个节点设为新尾节点。

从第二个节点开始,把每个节点插到头结点后面(头插法)。

直到所有节点反转完成。

清除

cpp 复制代码
void clear(List* list)
{
	Node* p = list->first->next;
	while (p != list->first)
	{
		list->first->next = p->next;
		free(p);
		p = list->first->next;
	}

	list->last = list->first;
	list->last->next = list->first;
	list->size = 0;
}

目的:清空链表,但保留头结点。

步骤:

从第一个有效节点开始,依次删除节点。

头结点指向下一个未删除节点。

最后 last=first,size=0。

销毁

cpp 复制代码
void destroy(List* list)
{
	clear(list);
	free(list->first);
	list->first = list->last = NULL;
}

目的:销毁整个链表,释放所有内存(包括头结点)。

步骤:

调用 clear 清空所有有效节点。

释放头结点。

把指针 first 和 last 置空,避免野指针。

相关推荐
华法林的小助手2 小时前
[学习笔记]在ros humble里使用qt
笔记·qt·学习
美式请加冰2 小时前
最短路径问题
java·数据结构·算法
会编程的土豆2 小时前
【数据结构与算法】 时间复杂度计算
数据结构·c++·算法
小年糕是糕手2 小时前
【35天从0开始备战蓝桥杯 -- Day9】
数据结构·数据库·c++·算法·蓝桥杯
Fanfffff7202 小时前
前端进阶:从请求竞态到并发控制(系统学习笔记)
前端·笔记·学习
山甫aa2 小时前
STL---常见数据结构总结
开发语言·数据结构·c++·学习
Oll Correct3 小时前
实验十四:IPv4地址的无分类编址方法
网络·笔记
bnmoel3 小时前
C语言自定义类型:联合和枚举
c语言·开发语言·数据结构·算法
计算机安禾3 小时前
【数据结构与算法】第34篇:选择排序:简单选择排序与堆排序
c语言·开发语言·数据结构·c++·算法·排序算法·visual studio