数据结构(三)循环链表

文章目录

  • 一、循环链表
    • (一)概念
    • (二)示意图
    • (三)操作
      • 1. 创建循环链表
        • (1)函数声明
        • (2)注意点
        • (3)代码实现
      • 2. 插入(头插,尾插,任意位置插入)
        • (1)头插
          • ① 函数声明
          • ② 注意点
          • ③ 代码实现
        • (2)尾插
          • ① 函数声明
          • ② 注意点
          • ③ 代码实现
        • (3)任意位置插入
          • ① 函数声明
          • ② 注意点
          • ③ 代码实现
      • 3. 删除(头删,尾删,任意位置删除)
        • (1)头删
          • ① 函数声明
          • ② 注意点
          • ③ 代码实现
        • (2)尾删
          • ① 函数声明
          • ② 注意点
          • ③ 代码实现
        • (3)任意位置删除
          • ① 函数声明
          • ② 注意点
          • ③ 代码实现
      • 4. 修改
        • (1)函数定义
        • (2)注意点
        • (3)代码实现
      • 5. 查询
        • (1)函数定义
        • (2)注意点
        • (3)代码实现
      • 6. 清空和销毁
        • (1)函数定义
        • (2)注意点
        • (3)代码实现
      • 7. 打印链表(方便查看实验现象)
        • (1)函数定义
        • (2)注意点
        • (3)代码实现
      • 8. 排序(以正序排序为例)
        • (1)函数定义
        • (2)注意点
        • (3)代码实现
      • 9.剔重
        • (1)函数定义
        • (2)注意点
        • (3)代码实现
    • (四)应用:实现约瑟夫环
      • 1.问题描述
      • 2. 问题分析
      • 3. 代码实现
  • 二、代码源码已上传资源

一、循环链表

(一)概念

操作和单向链表的操作基本一样
只是判断链表结束的条件不同

循环链表又分为有头循环链表和无头循环链表,其中无头结点的循环链表相对更常见些,因此下文以实现无头循环链表为例。

(二)示意图

在这里插入图片描述

(三)操作

1. 创建循环链表

(1)函数声明

int create_list(nd_t **phead,int num);

创建循环链表的第一个节点,
第一个节点的next指向它自己
将第一个节点的堆区地址传给main函数中的指针

(2)注意点
  1. 入参不能为空
  2. 因为需要将申请的第一个节点的地址写入main函数中的指针变量中,因此必须传入二级指针
  3. 申请内存空间后检查是否申请成功
(3)代码实现
int create_list(nd_t **phead,int num){
    if(NULL==phead){
        return -1;
    }
    *phead=(nd_t *)malloc(sizeof(nd_t));
    if(NULL==phead){
        return -1;
    }
    (*phead)->data=num;
    (*phead)->next=*phead; 
    return 0;
}

2. 插入(头插,尾插,任意位置插入)

(1)头插
① 函数声明

int insert_list_by_head(nd_t **phead,int num);

创建新节点
找到尾节点,将尾节点的next指向新的节点
新节点的next指向首节点
*phead指向pnew

② 注意点
  1. 入参不能为NULL
  2. 头插需要更改main函数中phead指针的值,因此需要传二级指针
  3. 需要保证链表中至少有一个节点,无头链表中只要phead不为NULL,就说明至少有一个节点
③ 代码实现
int insert_list_by_head(nd_t **phead,int num){
    //需要保证链表至少有一个节点
    if(NULL==phead){
        return -1;
    }
    //创建新节点
    nd_t *pnew=(nd_t *)malloc(sizeof(nd_t));
    if(NULL==pnew)
        return -1;
    pnew->data=num;
    //找到尾节点
    nd_t *ptemp=(*phead)->next;
    while(ptemp->next!=*phead)
    {
        ptemp=ptemp->next;
    }
    //插入
    ptemp->next=pnew;
    pnew->next=(*phead);
    *phead=pnew;
    return 0;
}
(2)尾插
① 函数声明

int insert_list_by_tail(nd_t **phead,int num);

创建新节点
找到尾节点,将尾节点的next指向新的节点
新节点的next指向首节点

② 注意点
  1. 头插和尾插的区别仅在于是否需要修改main函数中的指针变量
③ 代码实现
int insert_list_by_tail(nd_t *phead,int num){
    if(NULL==phead)
        return -1;
    //创建新节点
    nd_t *pnew=(nd_t *)malloc(sizeof(nd_t));
    if(NULL==pnew)
        return -1;
    pnew->data=num;
    //找到尾节点
    nd_t *ptemp=(phead)->next;
    while(ptemp->next!=phead)
    {
        ptemp=ptemp->next;
    }
    //插入
    ptemp->next=pnew;
    pnew->next=phead;
    return 0;
}
(3)任意位置插入
① 函数声明

int insert_list_by_pos(nd_t *phead,int pos,int num);

找到要插入的位置的前一个节点
创建新节点
插入节点

② 注意点
  1. 不支持插入在第一个位置
  2. 如果插入的前一个位置在最后一个可以,但是在第一个就不合理了
③ 代码实现
int insert_list_by_pos(nd_t *phead,int pos,int num){
    if(NULL==phead)
        return -1;
    //不支持插入在第0个位置
    if(pos<=0)
        return -1;
    //找到要插入的节点的前一位
    nd_t *ptemp=phead;
    for(int i=0;i<pos-1;i++){
        ptemp=ptemp->next;        
        //如果插入的前一个位置在最后一个可以,但是在第一个就不合理了
        if(ptemp==phead){
            printf("插入位置不合理\n");
            return -1;
        }
    }
    //创建新节点
    nd_t *pnew=(nd_t *)malloc(sizeof(nd_t));
    if(NULL==pnew)
        return -1;
    pnew->data=num;
    //插入节点
    pnew->next=ptemp->next;
    ptemp->next=pnew;

    return 0;
}

3. 删除(头删,尾删,任意位置删除)

(1)头删
① 函数声明

int delete_list_by_head(nd_t **phead);

找到尾节点
尾节点的next置成*phead->next
*phead=*phead->next
free(pdef)

② 注意点
  1. 需要传入二级指针
  2. 当表中只有一个节点时,进行删除操作时相当于将表销毁了。
③ 代码实现
int delete_list_by_head(nd_t **phead){
    //至少有一个节点
    if(NULL==phead)
        return -1;
    //只有一个节点
    if((*phead)->next==*phead){
        free(*phead);
        *phead=NULL;
        printf("表已清空\n");
        return 0;
    }
    //多个节点
    //找到尾节点
    nd_t *ptemp=(*phead)->next;
    while(ptemp->next!=*phead)
    {
        ptemp=ptemp->next;
    }
    //头删
    nd_t *pdel=*phead;
    *phead=(*phead)->next;
    ptemp->next=*phead;
    free(pdel);
    pdel=NULL;
    return 0;
}
(2)尾删
① 函数声明

int delete_list_by_tail(nd_t **phead);

找到尾节点的前一节点,
尾删操作

② 注意点
  1. 需要传入二级指针
  2. 当表中只有一个节点时,进行删除操作时相当于将表销毁了。
③ 代码实现
int delete_list_by_tail(nd_t **phead){
    //至少有一个节点
    if(NULL==phead)
        return -1;
    //只有一个节点
    if((*phead)->next==*phead){
        free(*phead);
        *phead=NULL;
        printf("表已清空\n");
        return 0;
    }
    //多个节点
    //找到尾节点的前一个节点
    nd_t *ptemp=(*phead)->next;
    while(ptemp->next->next!=*phead)
    {
        ptemp=ptemp->next;
    }
    //尾删
    nd_t *pdel=ptemp->next;
    ptemp->next=*phead;
    free(pdel);
    pdel=NULL;
    return 0;
}
(3)任意位置删除
① 函数声明

int delete_list_by_pos(nd_t **phead,int pos);

② 注意点
  1. 链表中至少有一个节点
  2. 如果只有一个节点时要删除第0个位置可以成功,此时链表销毁;其他位置均为不合理
  3. 如果多个节点时,需要区分是不是要删除头节点
  4. ptemp是要删除的节点的前一个节点,它不能是尾节点
③ 代码实现
int delete_list_by_pos(nd_t **phead,int pos){
    //至少有一个节点
    if(NULL==phead)
        return -1;
    if(0>pos){
        return -1;
    }
    //如果链表中只有一个节点
    if((*phead)->next==*phead){
        //删除第一个位置的节点
        if(0==pos){
            free(*phead);
            *phead=NULL;
            return 0;
        }
        //删除其他位置节点,均是位置不合理
        printf("位置不合理\n");
        return -1;
    }
    //链表有多个节点
    nd_t *pdel=*phead;
    //删除第一个位置的节点
    //找到尾节点
    nd_t *ptemp=(*phead)->next;
    while(ptemp->next!=*phead)
    {
        ptemp=ptemp->next;
    }
    if(0==pos){
        *phead=(*phead)->next;
        ptemp->next=*phead;
        free(pdel);
        pdel=NULL;
        return 0;
    }
    //删除其他位置节点
    //找到要删除的节点的前一个节点
    ptemp=(*phead);
    for(int i=0;i<pos-1;i++){
        //要删除的节点的前一个节点不能是尾节点
        if(ptemp->next->next==*phead){
            printf("删除位置不合理\n");
            return -1;
        }
        ptemp=ptemp->next;
    }
    pdel=ptemp->next;
    ptemp->next=pdel->next;
    free(pdel);
    pdel=NULL;
    return 0;
}

4. 修改

(1)函数定义

int modify_list_by_pos(nd_t *phead,int pos,int num);

遍历链表找到第pos个位置

(2)注意点
  1. 如果已经到达尾节点就不能再继续向下遍历修改
(3)代码实现
int modify_list_by_pos(nd_t *phead,int pos,int num){
    //至少有一个节点
    if(NULL==phead)
        return -1;
    if(0>pos){
        return -1;
    }
    //找到第pos个位置
    nd_t *ptemp=phead;
    for(int i=0;i<pos;i++){
        if(ptemp->next==phead){
            printf("位置不合理\n");
            return -1;
        }
        ptemp=ptemp->next;
    }
    ptemp->data=num;
    return 0;
}

5. 查询

(1)函数定义

int search_list_by_pos(nd_t *phead,int pos,int *num);

找到第pos个位置
读取数据域数据

(2)注意点
  1. 查询和修改的唯一区别是对数据的处理,修改是将新的数据写到第pos尾的数据域;修改是将pos位的数据域写到num中
  2. 循环结束条件与修改和删除一样
(3)代码实现
int search_list_by_pos(nd_t *phead,int pos,int *num){
    //至少有一个节点
    if(NULL==phead||NULL==num)
        return -1;
    if(0>pos){
        return -1;
    }
    //找到第pos个位置
    nd_t *ptemp=phead;
    for(int i=0;i<pos;i++){
        if(ptemp->next==phead){
            printf("位置不合理\n");
            return -1;
        }
        ptemp=ptemp->next;
    }
    *num=ptemp->data;
    return 0;
}

6. 清空和销毁

(1)函数定义

int destory_list(nd_t **phead);

先判断是不是只有一个节点
采用尾删(使用头删的话还要一直修改main函数中的指针变量的值)

(2)注意点
  1. 使用二级指针
  2. 入参不能为空
(3)代码实现
int destory_list(nd_t **phead){
    if(NULL==phead){
        return -1;
    }
    nd_t *ptemp=*phead;
    //不止一个节点
    //采用尾删,先找到尾节点前一个节点
    while(ptemp->next!=ptemp)
    {
        while (ptemp->next->next!=*phead){
            ptemp=ptemp->next;
        }
        nd_t *pdel=ptemp->next;
        ptemp->next=pdel->next;
        free(pdel);
    }
    //此时只有一个节点了
    free(*phead);
    *phead=NULL;
    printf("表已清空\n");
    return 0;
}

7. 打印链表(方便查看实验现象)

(1)函数定义

int print_list(nd_t *phead);

先打印出第一个节点,
遍历链表,直到ptemp->next==phead时结束

(2)注意点
  1. 在无头链表中phead为NULL,则说明表为空。phead->next==NULL,说明没有第二个节点。
(3)代码实现
int print_list(nd_t *phead){
    if(NULL==phead)
        return -1;
    nd_t *ptemp=phead->next;
    printf("%d ",phead->data);
    while(ptemp!=phead){
        printf("%d ",ptemp->data);
        ptemp=ptemp->next;
    }
    putchar(10);
    return 0;
}

8. 排序(以正序排序为例)

(1)函数定义

int sort_list(nd_t *phead);

选择排序思路

(2)注意点
  1. 外层循环可以不比较最后一个元素
(3)代码实现
int sort_list(nd_t *phead){
    if(NULL==phead){
        return -1;
    }
    nd_t *p=phead;
    nd_t *q=NULL;
    while(p->next!=phead){
        q=p->next;
        while(q!=phead){
            if(p->data>q->data){
                int temp=p->data;
                p->data=q->data;
                q->data=temp;
            }
            q=q->next;
        }
        p=p->next;
    }
    return 0;
}

9.剔重

(1)函数定义

int dedup(nd_t *phead);

动静指针配合
选择排序思路

(2)注意点
  1. 此时外层循环使用p->next!=phead或者p!=phead均可,循环链表此刻不会报段错误,但是用第一种效率会相对略高
(3)代码实现
int dedup_list(nd_t *phead){
    //至少有一个元素
    if(NULL==phead){
        return -1;
    }
    nd_t *p=phead;
    nd_t *q=NULL;
    nd_t *m=NULL;
    while(p->next!=phead){
        m=p;
        q=p->next;
        while(q!=phead){
            if(p->data==q->data){
                m->next=q->next;
                free(q);
                q=m->next;
            }else{
                m=q;
                q=q->next;
            }
        }
        p=p->next;
    }
}

(四)应用:实现约瑟夫环

1.问题描述

有一位叫约瑟夫的将军,在一次战斗中,连同手下的士兵一起被俘虏了。手下的士兵都非常爱国,宁死不投降,约瑟夫将军想了个办法:
让大家站成一圈,开始数数,从1开始数,数到7的人就自杀,
下一个人重新从1开始数,数到7再自杀,依次类推
直到只剩下一个人为止,最终剩下的就是约瑟夫将军,
然后他不想死,他投降了。这种“圈”,我们称之为“约瑟夫环”

要求:编写代码,模拟约瑟夫环淘汰人的过程,
命令行输入 ./a.out 总人数 数到几自杀 (总人数>1 数到几自杀>1 )
要求程序输出:
第x次 淘汰的是y号
以及最终剩下的是几号
如:输入 ./a.out 5 3 则程序输出
第1次 淘汰的是3号
第2次 淘汰的是1号
第3次 淘汰的是5号
第4次 淘汰的是2号
最后剩下的是 4 号

2. 问题分析

首先需要检查参数的合理性,参数都是以字符串形式保存的,需要转换成int型数据;
无头链表创建链表时就是创建第一个节点,即编号为1的人,之后依次开始创建节点

3. 代码实现

main.c文件:

#include "circle_list.h"

int del(nd_t *phead,int n);
int main(int argc, char const *argv[])
{
    if(3 != argc)
    {
        printf("参数不合理\n");
        return -1;
    }
    int num=atoi(argv[1]); //保存个数
    int n=atoi(argv[2]);//数到几
    if(num<=0)
    {
        printf("人数应当大于0\n");
        return-1;
    }
    nd_t *phead=NULL;
    //第一个人及其编号
    create_list(&phead,1);
    //后面的人
    for(int i=2;i<=num;i++){
        if(insert_list_by_tail(phead,i)){
            printf("插入失败\n");
            return -1;
        }
    }
    print_list(phead);  
    del(phead,n);
    return 0;
}

int del(nd_t *phead,int n)
{
    if(NULL==phead)
    {
        printf("传参错误\n");
        return -1;
    }
    if(0>=n)
    {
        printf("传参错误,n应当大于0\n");
        return -1;
    }
    int index=0;
    nd_t *pptemp=NULL;

    while(phead->next!=phead)
    {
        for(int i=0;i<n-1;i++) //phead默认移到下一位了,故只需要再移动n-1次
        {
            pptemp=phead;
            phead=phead->next;
        }
        //此时phead是需要删除的节点,执行删除操作
        pptemp->next=phead->next; 
        printf("第%d次删除%d\n",index+1,phead->data);
        free(phead);
        //删除完成后pead等于后一个节点
        phead=pptemp->next;
        index++;
    }
    printf("%d存活\n",phead->data);
    free(phead);
    phead=NULL;
    return 0;
}

二、代码源码已上传资源

链接:C语言实现循环链表源码链接

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/646277.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

专业渗透测试 Phpsploit-Framework(PSF)框架软件小白入门教程(十二)

本系列课程&#xff0c;将重点讲解Phpsploit-Framework框架软件的基础使用&#xff01; 本文章仅提供学习&#xff0c;切勿将其用于不法手段&#xff01; 接上一篇文章内容&#xff0c;讲述如何进行Phpsploit-Framework软件的基础使用和二次开发。 我们&#xff0c;继续讲一…

【Django项目】 音乐网站spotify复刻

代码&#xff1a;https://github.com/tomitokko/spotify-clone 注&#xff1a;该项目不是自己提供mp3文件&#xff0c;而是使用spotify 的api接口获取。

用实践结果告诉你为啥说 CloudFlare 是赛博菩萨?

最近几天明月都没有更新博客了,主要是接了几个 CloudFlare 代维配置的活儿,有需要加速优化的,有需要排除疑难故障的,有需要提高防御攻击能力的甚至还有纯粹为了体验“打不死”装逼需要的。总之,各种各样的需求,五花八门的,好在 CloudFlare 都能一一满足,最主要的是这些…

C++入门:从C语言到C++的过渡(3)

目录 1.内联函数 1.1内联函数的定义 1.2特性 2.auto关键字 2.1auto的简介 2.2注意事项 3.范围for 4.nullptr空指针 1.内联函数 在C语言中&#xff0c;无论使用宏常量还是宏函数都容易出错&#xff0c;而且无法调试。而C为了弥补这一缺陷&#xff0c;引入了内联函数的概…

【NumPy】关于numpy.clip()函数,看这一篇文章就够了

&#x1f9d1; 博主简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟&#xff0c;欢迎关注。提供嵌入式方向…

前端手写文件上传;使用input实现文件拖动上传

使用input实现文件拖动上传 vue2代码&#xff1a; <template><div><div class"drop-area" dragenter"highlight" dragover"highlight" dragleave"unhighlight" drop"handleDrop"click"handleClick&quo…

嵌入式C语言中结构体使用详解

各位开发者大家好,今天给大家分享一下,嵌入式C语言中结构体的使用方法。 第一个:内存对齐 内存对齐是指一个数据类型在内存中存放时,对其地址的要求。简单来说内存对齐就是使得其内存地址是该类型大小的整数倍,例如 double 类型的变量,其内存地址需要是8的倍数(double大…

数据结构(四)

数据结构&#xff08;四&#xff09; 算法算法的特征算法和程序的区别怎么样评判一个算法的好坏 常见的查找算法线性树状哈希查找构建哈希函数的方法质数求余法解决冲突 算法 一堆指令的有序集合 算法的特征 唯一性&#xff1a;每一句话只有一种解释 有穷性&#xff1a;算法能…

澳大利亚.德国-门户媒体投放通稿:需要注意什么地方

概述 在现代社会&#xff0c;新闻媒体的投放成为企业和组织宣传推广的重要手段之一。澳大利亚和德国作为全球重要的经济和科技中心&#xff0c;其新闻媒体也备受关注。本文将介绍澳大利亚和德国的一些主要新闻媒体&#xff0c;并讨论发表新闻稿时需要注意的地方。 澳大利亚媒…

Python小游戏——俄罗斯方块

文章目录 项目介绍环境配置代码设计思路1.初始化和导入库&#xff1a;2.定义颜色和屏幕尺寸&#xff1a;3.定义游戏逻辑&#xff1a;4.游戏循环&#xff1a; 源代码效果图 项目介绍 俄罗斯方块游戏是一款经典的益智游戏&#xff0c;玩家通过旋转和移动各种形状的方块&#xff…

VolWeb:集中式增强型数字取证内存分析平台

关于VolWeb VolWeb是一款最新开发的集中式增强型数字取证内存分析平台&#xff0c;该平台基于Volatility 3框架实现其功能&#xff0c;该工具旨在辅助广大研究人员执行安全分析和事件应急响应等任务。 VolWeb可以提供集中式、可视化的增强型网络应用程序&#xff0c;并提高安全…

如何在 Elasticsearch 中选择精确 kNN 搜索和近似 kNN 搜索

作者&#xff1a;来自 Elastic Carlos Delgado kNN 是什么&#xff1f; 语义搜索&#xff08;semantic search&#xff09;是相关性排名的强大工具。 它使你不仅可以使用关键字&#xff0c;还可以考虑文档和查询的实际含义。 语义搜索基于向量搜索&#xff08;vector search&…

flink cdc mysql整理与总结

文章目录 一、业务中常见的需要数据同步的场景CDC是什么FlinkCDC是什么CDC原理为什么是FlinkCDC业务场景flink cdc对应flink的版本 二、模拟案例1.阿里云flink sql2.开源flink sql(单机模式)flink 安装安装mysql3.flink datastream 三、总结 提示&#xff1a;以下是本篇文章正文…

行业分析---造车新势力之蔚来汽车

1 前言 在之前的博客中&#xff0c;笔者分析了苹果《行业分析---我眼中的Apple Inc.》&#xff0c;苹果已经成为世界级的公司。随后也分析了电动汽车公司特斯拉《行业分析---马斯克的Tesla》&#xff0c;特斯拉也在不断成长。目前能分析的新能源汽车公司不多&#xff0c;小米汽…

C#对文件进行批量重命名或者对某个单独的文件进行改名

目录 一、FolderBrowserDialog 二、OpenFileDialog 三、Path 四、ui设计 五、代码部分 一、FolderBrowserDialog FolderBrowserDialog是一个用于选择文件夹的对话框控件&#xff0c;可以在windows Forms应用程序中使用。使用它可以让用户选择一个文件夹&#xff0c;并返…

arping 一键检测网络设备连通性(KALI工具系列二)

目录 1、KALI LINUX简介 2、arping工具简介 3、在KALI中使用arping 3.1 目标主机IP&#xff08;win&#xff09; 3.2 KALI的IP 4、操作示例 4.1 IP测试 4.2 ARP测试 4.3 根据存活情况返回 5、总结 1、KALI LINUX简介 Kali Linux 是一个功能强大、多才多艺的 Linux 发…

QGIS开发笔记(二):Windows安装版二次开发环境搭建(上):安装OSGeo4W运行依赖其Qt的基础环境Demo

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/139136356 长沙红胖子Qt&#xff08;长沙创微智科&#xff09;博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV…

如何利用线程池实现互联网验证码保护服务

如何利用线程池实现互联网验证码保护服务 1、业务背景与实现思路2、代码实操1、业务背景与实现思路 首先介绍一下业务背景,假设我们的系统是一个短视频播放网站,每个新加入的用户都需要注册账号并绑定手机号。为了验证用户手机的正确性,我们的系统会发送一条验证码到用户注…

上下文视觉提示实现zero-shot分割检测及多visual-prompt改造

文章目录 一、Closed-Set VS Open-set二、DINOv2.1 论文和代码2.2 内容2.3 安装部署2.4 使用效果 三、多visual prompt 改造3.1 获取示例图mask3.2 修改函数参数3.3 推理代码3.4 效果的提升&#xff01; 四、总结 本文主要介绍visual prompt模型DINOv&#xff0c;该模型可输入八…

Linux基础(八):计算机基础概论

本篇博客简单介绍计算机的基础知识&#xff0c;为后续学习做个铺垫。 目录 一、计算机的基本组成 1.1 计算机组成五大部件 1.1.1 运算器&#xff08;Arithmetic Logic Unit&#xff0c;ALU&#xff09; 1.1.2控制器 &#xff08;Control Unit&#xff0c;CU&#xff09; …