循环链表的基本操作
一、循环链表的基本概念
循环链表(circular linked list)是另一种形式的链式存储结构。它的特点是表中最后一个结点的指针域指向头结点,整个链表形成一个环。由此,从链表中任一结点出发都能找到表中的其他结点。
二、循环链表的实现
1. 数据的定义
常量:
#define TRUE 1
#define FALSE 0
#define EMPTY 0
#define NO_FOUND (-1)
#define NO_EXIST_ID (-999)
结构体(描述课程):
typedef struct course{
int id; //课程id
char * name; //课程名称
char * teacher; //任课老师
char * type; //课程类型
}Course;
2. 定义结点和循环链表的结构
/** 循环链表的结点 */
typedef struct cirNode{
//数据域
Course course;
//指针域
struct cirNode * next;
}CirNode;
/** 循环链表的结构 */
typedef struct circularLinkedList{
//头指针
CirNode * head;
//链表长度
int length;
}CircularLinkedList;
3. 循环链表的操作
//初始化循环链表
void InitCircularLinkedList(CircularLinkedList * circularLinkedList, Course * array, int length);
// 在循环链表的指定位置插入
void InsertElement(CircularLinkedList * circularLinkedList, int pos, Course course);
// 删除循环链表指定位置的结点
Course DeleteElem(CircularLinkedList * circularLinkedList, int pos);
// 查找循环链表指定位置的结点
Course GetElementByPosition(CircularLinkedList * circularLinkedList, int pos);
// 查询元素在循环链表中的位置
int GetPositionByElement(CircularLinkedList * circularLinkedList, Course course);
// 根据元素内容返回循环链表中对应的结点
CirNode * GetCircularLinkedListNode(CircularLinkedList * circularLinkedList, Course course);
// 查询某个结点直接前驱的结点
Course GetPriorNode(CircularLinkedList * circularLinkedList, Course course);
// 查询某个结点直接后继的结点
Course GetNextNode(CircularLinkedList * circularLinkedList, Course course);
// 求循环链表的长度
int GetLength(CircularLinkedList * circularLinkedList);
// 判断循环链表是否为空
int IsEmpty(CircularLinkedList * circularLinkedList);
// 清空循环链表
void ClearCircularLinkedList(CircularLinkedList * circularLinkedList);
// 根据循环链表中某个结点开始遍历整个循环链表
void VisitCircularLinkedList(CirNode * node);
// 从头结点打印循环链表
void PrintList(CircularLinkedList * circularLinkedList);
// 打印某个结点的内容
void PrintNode(Course course);
3.1 初始化
/**
* 初始化循环链表
* @param circularLinkedList 要操作的循环链表
* @param array 初始化数据(数组)
* @param length 循环链表的长度
*/
void InitCircularLinkedList(CircularLinkedList * circularLinkedList, Course * array, int length)
{
if(length < 0 || !array){
printf("初始化失败!\n");
return;
}
circularLinkedList->head = NULL;
circularLinkedList->length = 0;
for (int i = 0; i < length; ++i) {
InsertElement(circularLinkedList, i+1, array[i]);
}
}
3.2 插入元素
当 position=1 时,要考虑两种情况:
1. 循环链表为空时:直接将头指针指向新的结点,并且将该结点的next指向自己。
node->next = node;
2. 循环链表不为空时:首先将新结点node的next指向头结点,然后通过循环找到最后一个结点,
将lastNode->next 指向node,最后让头指针指向node,使其成为第一个结点。
当position != 1时,注意在length+1的位置插入后,新的结点node的next应该指向头结点
图示:
插入新结点时:
找到最后一个结点:
插入后:
代码实现:
/**
* 在循环链表的指定位置插入
* @param circularLinkedList 要操作的循环链表
* @param pos 插入的位置
* @param course 插入的元素
* 需要考虑两种情况:
* 1. 链表的长度为0:node->next = node
* 2. 链表的长度不为0:node->next = head; lastNode->next->node
*/
void InsertElement(CircularLinkedList * circularLinkedList, int pos, Course course)
{
if((circularLinkedList->length+1) < pos){
printf("位置超出链表长度,插入失败!\n");
return;
}
//1. 创建一个新的结点,将数据内容记录,将指针域暂时置为NULL
CirNode * node = (CirNode *) malloc(sizeof(CirNode));
node->course = course;
node->next = NULL;
//2. pos = 1,要分为两种情况
if(pos == 1){
node->next = circularLinkedList->head;
//如果链表为空
if(!node->next){
node->next = node;
} else {
//链表不为空
CirNode * lastNode = circularLinkedList->head;
for(int i = 1; i < circularLinkedList->length; ++i){
lastNode = lastNode->next;
}
lastNode->next = node;
}
circularLinkedList->head = node;
circularLinkedList->length++;
return;
}
//3. pos != 1
//找到第pos-1个结点
CirNode * current = circularLinkedList->head;
for (int i = 1; current && i < (pos-1); ++i) {
current = current->next;
}
if(current){
node->next = current->next;
current->next = node;
circularLinkedList->length++;
if(pos == circularLinkedList->length){
// TODO 这里current->next 已经指向了头结点,为了更加清晰循环链表的结构,强调此时node为最后一个结点,它的next应该指向头结点
node->next = circularLinkedList->head;
}
}
}
3.3 删除元素
/**
* 删除循环链表指定位置的结点
* @param circularLinkedList 要操作的链表
* @param pos 删除的位置
* @return 被删除的元素
*/
Course DeleteElem(CircularLinkedList * circularLinkedList, int pos)
{
Course course;
course.id = NO_EXIST_ID;
if(pos < 0 || pos > circularLinkedList->length || !circularLinkedList->head){
printf("删除失败!\n");
return course;
}
CirNode * node = circularLinkedList->head;
CirNode * current = circularLinkedList->head;
if(pos == 1){
//记录结点
course = node->course;
//移动头指针
circularLinkedList->head = node->next;
//尾结点指向头结点
for (int i = 1; i < circularLinkedList->length; ++i) {
current = current->next;
}
current->next = circularLinkedList->head;
circularLinkedList->length--;
} else {
//找到第pos-1个结点
for (int i = 1; i < pos-1; ++i) {
current = current->next;
}
//记录结点
node = current->next;
course = node->course;
current->next = node->next;
circularLinkedList->length--;
}
free(node);
return course;
}
3.4 查找元素
/**
* 查找循环链表指定位置的结点
* @param circularLinkedList 要操作的链表
* @param pos 查找的位置
* @return 已找到的元素,如果查找失败则返回
*/
Course GetElementByPosition(CircularLinkedList * circularLinkedList, int pos)
{
Course course;
course.id = NO_EXIST_ID;
if(pos < 0 || pos > circularLinkedList->length || !circularLinkedList->head){
printf("查找失败!\n");
return course;
}
CirNode * current = circularLinkedList->head;
for (int i = 1; i < pos; ++i) {
current = current->next;
}
course = current->course;
return course;
}
/**
* 查询元素在循环链表中的位置
* @param circularLinkedList 要操作的链表
* @param course 要查询的元素
* @return 找到元素的位置,如果未找到则返回NO_FOUND
*/
int GetPositionByElement(CircularLinkedList * circularLinkedList, Course course)
{
int pos = 1;
CirNode * current = circularLinkedList->head;
while (current){
Course cur_course = current->course;
if((course.id == cur_course.id) && (course.name == cur_course.name) && (course.teacher == cur_course.teacher) && (course.type == cur_course.type))
return pos;
pos++;
current = current->next;
}
return NO_FOUND;
}
3.5 求某个结点的前驱节点
/**
* 查询某个结点直接前驱的结点
* @param circularLinkedList 要操作的循环链表
* @param course 查询的元素
* @return 返回当前元素的前驱结点
* 循环链表中每个结点都有直接前驱
*/
Course GetPriorNode(CircularLinkedList * circularLinkedList, Course course)
{
Course prior_course;
int pos = GetPositionByElement(circularLinkedList, course);
CirNode * prior_node = circularLinkedList->head;
if(pos == 1){
for (int i = 1; i < circularLinkedList->length; ++i) {
prior_node = prior_node->next;
}
} else {
for (int i = 1; i < pos-1; ++i) {
prior_node = prior_node->next;
}
}
prior_course = prior_node->course;
return prior_course;
}
3.6 求某个结点的后继节点
/**
* 查询某个结点直接后继的结点
* @param circularLinkedList 要操作的循环链表
* @param course 查询的元素
* @return 返回当前元素的后继结点
* 循环链表中每个结点都有直接后继
*/
Course GetNextNode(CircularLinkedList * circularLinkedList, Course course)
{
Course next_course;
CirNode * next_node = circularLinkedList->head;
int pos = GetPositionByElement(circularLinkedList, course);
for (int i = 0; i < pos; ++i) {
next_node = next_node->next;
}
next_course = next_node->course;
return next_course;
}
3.7 以任一结点开始遍历整个循环链表
void VisitCircularLinkedList(CirNode * node)
{
if(!node){
printf("内容为空!\n");
return;
}
CirNode * current = node;
do {
PrintNode(current->course);
current = current->next;
} while (current != node);
}
3.8 其他操作
//链表的长度
int GetLength(CircularLinkedList * circularLinkedList)
{
if(!circularLinkedList->head){
return EMPTY;
}
return circularLinkedList->length;
}
//判空
int IsEmpty(CircularLinkedList * circularLinkedList)
{
return circularLinkedList->length == 0 ? TRUE : FALSE;
}
//清空循环链表
void ClearCircularLinkedList(CircularLinkedList * circularLinkedList)
{
CirNode * current = circularLinkedList->head;
CirNode * priorNode = NULL;
//应该选择for循环,经过特定的次数将链表中每个结点都释放内存,最后将头结点置为NULL,长度置0
for (int i = 0; i < circularLinkedList->length; ++i) {
priorNode = current;
current = current->next;
free(priorNode);
}
circularLinkedList->head = NULL;
circularLinkedList->length = 0;
}
//打印链表
void PrintList(CircularLinkedList * circularLinkedList)
{
if(circularLinkedList->length == 0 || !circularLinkedList->head){
printf("链表为空!\n");
circularLinkedList->length = 0;
return;
}
CirNode * current = circularLinkedList->head;
for (int i = 0; current && i < circularLinkedList->length; ++i) {
printf("[%d, %s, %s, %s]\n", current->course.id, current->course.name, current->course.teacher, current->course.type);
current = current->next;
}
}
void PrintNode(Course course)
{
printf("该结点内容:[%d, %s, %s, %s]\n", course.id, course.name, course.teacher, course.type);
}
4. 循环链表的测试
4.1 测试数据
Course data[] = {
{1, "Java", "小张", "选修课"},
{2, "数据结构", "小李", "必修课"},
{3, "计算机网络", "小王", "必修课"},
{4, "操作系统", "小刘", "必修课"},
{5, "计算机组成原理", "小徐", "必修课"}
};
4.2 测试函数
void testCircularLinkList()
{
printf("========初始化=========\n");
CircularLinkedList circularLinkedList;
InitCircularLinkedList(&circularLinkedList, data, 5);
PrintList(&circularLinkedList);
printf("\n========插入元素=========\n");
Course course = {12, "数据库基本概论", "小吴", "必修课"};
InsertElement(&circularLinkedList, 6, course);
PrintList(&circularLinkedList);
printf("头结点:\n");
PrintNode(circularLinkedList.head->course);
printf("\n========链表的长度=========\n");
printf("链表的长度是:%d\n", GetLength(&circularLinkedList));
printf("\n========删除元素=========\n");
Course del_course = DeleteElem(&circularLinkedList, 6);
printf("删除后:\n");
PrintList(&circularLinkedList);
printf("删除的结点:\n");
PrintNode(del_course);
printf("\n========按值查找=========\n");
int pos = GetPositionByElement(&circularLinkedList, data[4]);
printf("data[4]的位置是: %d\n", pos);
printf("\n========按位查找=========\n");
Course existed_course = GetElementByPosition(&circularLinkedList, 5);
printf("该位置上的元素:\n");
PrintNode(existed_course);
printf("\n========直接前驱=========\n");
Course prior_course = GetPriorNode(&circularLinkedList, data[0]);
printf("该结点的前驱结点:\n");
PrintNode(prior_course);
printf("\n========直接后继=========\n");
Course next_course = GetNextNode(&circularLinkedList, data[3]);
printf("该结点的后继结点:\n");
PrintNode(next_course);
// printf("\n========按照元素查找结点=========\n");
CirNode * findNode = GetCircularLinkedListNode(&circularLinkedList, data[3]);
// PrintNode(findNode->course);
printf("\n========按照某个结点遍历整个循环链表=========\n");
VisitCircularLinkedList(findNode);
printf("\n========清空链表=========\n");
ClearCircularLinkedList(&circularLinkedList);
IsEmpty(&circularLinkedList) == TRUE ? printf("循环链表为空!\n") : printf("循环链表不为空!\n");
}
5. 测试结果
========初始化=========
[1, Java, 小张, 选修课]
[2, 数据结构, 小李, 必修课]
[3, 计算机网络, 小王, 必修课]
[4, 操作系统, 小刘, 必修课]
[5, 计算机组成原理, 小徐, 必修课]
========插入=========
[1, Java, 小张, 选修课]
[2, 数据结构, 小李, 必修课]
[3, 计算机网络, 小王, 必修课]
[4, 操作系统, 小刘, 必修课]
[5, 计算机组成原理, 小徐, 必修课]
[12, 数据库基本概论, 小吴, 必修课]
头结点:
该结点内容:[1, Java, 小张, 选修课]
========链表的长度=========
链表的长度是:6
========删除=========
删除后:
[1, Java, 小张, 选修课]
[2, 数据结构, 小李, 必修课]
[3, 计算机网络, 小王, 必修课]
[4, 操作系统, 小刘, 必修课]
[5, 计算机组成原理, 小徐, 必修课]
删除的结点:
该结点内容:[12, 数据库基本概论, 小吴, 必修课]
========按值查找=========
data[4]的位置是: 5
========按位查找=========
该位置上的元素:
该结点内容:[5, 计算机组成原理, 小徐, 必修课]
========直接前驱=========
该结点的前驱结点:
该结点内容:[5, 计算机组成原理, 小徐, 必修课]
========直接后继=========
该结点的后继结点:
该结点内容:[5, 计算机组成原理, 小徐, 必修课]
========按照某个结点遍历整个循环链表=========
该结点内容:[4, 操作系统, 小刘, 必修课]
该结点内容:[5, 计算机组成原理, 小徐, 必修课]
该结点内容:[1, Java, 小张, 选修课]
该结点内容:[2, 数据结构, 小李, 必修课]
该结点内容:[3, 计算机网络, 小王, 必修课]
========清空链表=========
循环链表为空!
三、总结
前前后后经历很多次修改,画图是有助于理解数据结构的,通过博客也更加详细的梳理了循环链表的基本操作,包括使用指针遍历,插入结点和删除结点等,是通过debug工具一点一点磨出来的,总算辛苦没有白费。可能也存在一些我没有发现的bug,以后遇到了会重新修改的。同时,请大家评论指正!!!