数据结构——循环链表

一、循环链表的基本概念

循环链表(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,以后遇到了会重新修改的。同时,请大家评论指正!!!

  • 3
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值