【C数据结构】无头非循环单向链表_SList

目录

无头非循环单向链表LinkedList

【1】链表概念

【2】链表分类

【3】无头单向非循环链表

【3.1】无头单向非循环链表数据结构与接口定义

【3.2】无头单向非循环链表初始化

【3.3】无头单向非循环链表开辟节点空间

【3.4】无头单向非循环链表销毁

【3.5】 无头单向非循环链表头插

【3.6】 无头单向非循环链表尾插

【3.7】 无头单向非循环链表在pos位置插

【3.8】 无头单向非循环链表头删

【3.9】 无头单向非循环链表尾删

【3.10】 无头单向非循环链表在pos位置删除

【3.11】 无头单向非循环链表查找

【3.12】 无头单向非循环链表修改

【3.13】 无头单向非循环链表打印

【2.14】 无头单向非循环链表大小

【3.15】 无头单向非循环链表判空


无头非循环单向链表LinkedList

【1】链表概念

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。

        链表是指逻辑结构上一个挨一个的数据,在实际存储时,并没有像顺序表那样也相互紧挨着。恰恰相反,数据随机分布在内存中的各个位置。

        由于分散存储,为了能够体现出数据元素之间的逻辑关系,每个数据元素在存储的同时,要配备一个指针,用于指向它的直接后继元素,即每一个数据元素都指向下一个数据元素(最后一个指向NULL(空))。

        如图所示,当每一个数据元素都和它下一个数据元素用指针链接在一起时,就形成了一个链,这个链子的头就位于第一个数据元素,这样的存储方式就是链式存储。

【2】链表分类

  • 单向或者双向链表

  • 带头或不带头链表

  • 循环非循环链表

  • 虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:

【3】无头单向非循环链表

每个元素本身由两部分组成:

  • 存储数据的区域,称为“数据域";
  • 指向直接后继的指针,称为“指针域”。

        这两部分信息组成数据元素的存储结构,称之为“结点”。n个结点通过指针域相互链接,组成一个链表。

        由于每个结点中只包含一个指针域,生成的链表又被称为单链表。

【3.1】无头单向非循环链表数据结构与接口定义

        链表中存放的不是基本数据类型,需要用结构体实现自定义:

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
​
/* 无头单向非循环链表数据结构 */
typedef int SLTDataType;
typedef struct SingleListNode{
    SLTDataType _data;              // 节点存储的数据.
    struct SingleListNode* _next;   // 节点中存储指向下一个节点的指针.
}SListNode;
​
/* 无头单向非循环链表开辟新节点接口 */
SListNode* BuySingleListNode(SLTDataType val);

/* 无头单向非循环链表销毁节点接口 */
​void SingleListDestory(SListNode** ppHead)

/* 无头单向非循环链表初始化函数接口 */
void SingleListInit(SListNode** ppHead);
​
/* 无头单向非循环链表头插函数接口 */
void SingleListPushFront(SListNode** ppHead, SLTDataType val);
​
/* 无头单向非循环链表尾插函数接口 */
void SingleListPushBack(SListNode** ppHead, SLTDataType val);
​
/* 无头单向非循环链表指定位置插函数接口 */
void SingleListInsert(SListNode** ppHead, SListNode* pPos ,SLTDataType val);
​
/* 无头单向非循环链表头删函数接口 */
void SingleListPopFront(SListNode** ppHead);
​
/* 无头单向非循环链表尾删函数接口 */
void SingleListPopBack(SListNode** ppHead);
​
/* 无头单向非循环链表指定位置删函数接口 */
void SingleListErase(SListNode** ppHead, SListNode* pPos);
​
/* 无头单向非循环链表查找函数接口 */
SListNode* SingleListFind(SListNode** ppHead, SListNode* pPos);
​
/* 无头单向非循环链表修改函数接口 */
void SingleListModification(SListNode** ppHead, SLTDataType ModifiSource, SLTDataType ModifiTarget);
​
/* 无头单向非循环链表大小函数接口 */
size_t SingleListSize(SListNode** ppHead);
​
/* 无头单向非循环链表判空函数接口 */
bool SingleListEmpty(SListNode** ppHead);
​
/* 无头单向非循环链表打印函数接口 */
void SingleListPrint(SListNode** ppHead);

【3.2】无头单向非循环链表初始化

/* 无头单向非循环链表初始化函数接口 */
void SingleListInit(SListNode** ppHead){
    // 断言保护指针传参.
    assert(ppHead);
​
    (*ppHead)->_data = 0;
    (*ppHead)->_next = NULL;
}

【3.3】无头单向非循环链表开辟节点空间

        开辟节点空间就是直接开辟一个节点空间,这里直接使用malloc这个函数就可以开辟了,开辟是主要返回的指针不能是空指针,所以要进行判断一下。

/* 无头单向非循环链表开辟新节点接口 */
SListNode* BuySingleListNode(SLTDataType val) {
    // 开辟新的节点空间. 
    SListNode* newNode = (SListNode*)malloc(sizeof(SListNode));
    // 检测节点是否开辟成功.
    if(newNode == NULL) {
        perror("BuySingleListNode malloc fail!");
        exit(-1);
    }
​
    // 节点开辟成功,初始化节点状态,并返回节点.
    newNode->_data = val;
    newNode->_next = NULL;
    return newNode;
}

【3.4】无头单向非循环链表销毁

// 无头非循环单链表 - 内存销毁
​void SingleListDestory(SListNode** ppHead)
{
    // 断言:保证指针变量是有效不为NULL的指针。
    assert(pSList);

    SListNode* pCur = *ppHead;
    while (pCur != NULL)
    {
        SListNode* pDel = pCur;
        pCur = pCur->next;

        free(pDel);
        pDel = NULL;
    }

    // 头指针指向NULL 
    *pSList->next = NULL;
    *pSList->data = 0;
    free(*pSList);
    *pSList = NULL;
}

【3.5】 无头单向非循环链表头插

        头插只需要将原来头结点中的指针域给新插入元素的指针域,然后再将新结点的地址给原来头结点的指针域即可。

/* 无头单向非循环链表头插函数接口 */
void SingleListPushFront(SListNode** ppHead, SLTDataType val){
    // 断言保护指针传参.
    assert(ppHead);
​
    // 开辟新的节点.
    SListNode* newNode = (SListNode*)malloc(sizeof(SListNode));
     // 检测节点是否开辟成功.
    if(newNode == NULL){
        perror("SingleListPushFront malloc fail!");
        exit(-1);
    }
​
    // 为新节点赋值:
    newNode->_data = val;
    // 头插节点,进行链接:
    newNode->_next = *ppHead;
    *ppHead = newNode; 
}

【3.6】 无头单向非循环链表尾插

尾插的话可能会分为两种情况:

  • 1、链表中有节点了
  • 2、链表中没有节点

 

/* 无头单向非循环链表尾插函数接口 */
void SingleListPushBack(SListNode** ppHead, SLTDataType val){
    // 断言保护指针传参.
    assert(ppHead);
    // 开辟新的节点准备尾插.
    SListNode* newNode = BuySingleListNode(val);
    // 尾插分为两种情况:
    // 1、如果链表为空链表,那么新开的节点就是第一个节点.
    // 2、如果链表不为空链表,那么需要找到最后一个节点尾插到后面.
    SListNode* pTail = *ppHead;
    if(*ppHead == NULL){ // 空链表.
        pTail = newNode;
        *ppHead = pTail;
        return;
    }
​
    // 程序走到这里说明不是一个空链表,找到最后一个节点,插入数据.
    while(pTail->_next != NULL){
        pTail = pTail->_next;
    }
    // 尾插.
    pTail->_next = newNode;
}
​

【3.7】 无头单向非循环链表在pos位置插

        对于单链表来说,在pos前插入数据是比较麻烦的,要找到pos之前的上一个数据,那就要从头进行便利了。

        当然还有一种情况就是pos的位置正好在第一个节点的位置上,这时候可以直接头插入

  • 第一种情况:

 

  • 第二种情况:

/* 无头单向非循环链表指定位置插函数接口 */
void SingleListInsert(SListNode** ppHead, SListNode* pPos ,SLTDataType val){
    // 断言保护指针传参.
    assert(ppHead);
    assert(pPos != NULL);
​
    // 指定位置插入有两种情况:
    // 1、pPos位置是头的位置,这时候只需要调用头插函数.
    // 2、pPos位置不是头的位置,这时候需要找到pPos的前一个节点,在pPos的前一个节点和pPos中间插入节点.
    if(pPos == *ppHead){
        SingleListPushFront(ppHead, val);
    }
    else { // 第二种情况.
        // 找到pPos的上一个节点.
        SListNode* pPrev = *ppHead;
        while(pPrev->_next != pPos){
            pPrev = pPrev->_next;
            // 防止外部给的指针是错的,找到NULL,说明没有要找的节点.
            assert(pPrev);
        }
​
        // 走到这里说明已经找到了pPos的上一个节点,新建节点,插入到中间. 
        SListNode* newNode = BuySingleListNode(val);
        pPrev->_next = newNode;
        newNode->_next = pPos;
        // newNode这个指针已经用完,把他指向空.
        newNode = NULL;   
    }
}

【3.8】 无头单向非循环链表头删

        链表在删除数据的时候要注意释放内存 先找一个指针保存一下pList的位置, pList指向下一个节点,free到之前pList指向的位置,还要注意的就是要注意如果链表空了就不能删除啦,否则就是引用NULL指针了。

/* 无头单向非循环链表头删函数接口 */
void SingleListPopFront(SListNode** ppHead){
    // 断言保护指针传参.
    assert(ppHead);
​
    // 检查链表是否是空链表.
    // 检查链表是否是空链表.
    if(SingleListEmpty(ppHead)) {
        printf("SingleList Empty!\n");
        return NULL;
    }
​
    // 保存头节点指针,对其进行删除,将ppHead指向下一个节点.
    SListNode* pDel = *ppHead;
    *ppHead = (*ppHead)->_next;
​
    // 释放节点.
    free(pDel);
    pDel = NULL;
}

【3.9】 无头单向非循环链表尾删

        尾删的时候要注意1个节点的时候需要直接将这个节点free掉,如果是多个节点需要采用图序的方式进行删除 有两种解法。

  • 第一种解法:

/* 无头单向非循环链表尾删函数接口 */
void SingleListPopBack(SListNode** ppHead){
    // 断言保护指针传参.
    assert(ppHead);
    assert(*ppHead);
​
    // 尾删分为两种情况考虑:
    // 1、链表中只有一个节点.
    // 2、链表中有多个节点.
    if((*ppHead)->_next == NULL){   // 删除链表中的最后一个节点.
        free(*ppHead);
        *ppHead = NULL;
        return;
    }
​
    // 程序走到这里说明链表中有多个节点.
    // 需要找到最后一个节点和最后一个节点的上一个节点.
    SListNode* pTail = *ppHead;
    SListNode* pPrev = NULL;
    while (pTail->_next != NULL){
        pPrev = pTail;
        pTail = pTail->_next;
    }
    // 找到了对应的节点释放掉pTail节点,将pPrev节点的_next指向NULL.
    free(pTail);
    pTail = NULL;
​
    pPrev->_next = NULL;
}

  • 第二种解法:

// 单链表尾删 - 函数声明
void SListPopBack(SListNode** ppHead)
{
    //  因为ppHead拿的是pList的地址,所以一定不为空。
    assert(ppHead);
    assert(*ppHead != NULL);
​
    // 尾删要分为:
    // 1个节点和多个节点
    if ((*ppHead)->next == NULL)
    {
        free(*ppHead);
        *ppHead = NULL;
    }
    else
    {
        // 寻找尾巴上的节点。
        SListNode* tail = *(ppHead);
        while (tail->next->next != NULL)
        {
            tail = tail->next;
        }
​
        // 程序走到这里说明找到了尾巴上的那个节点。
        free(tail->next);
        tail->next = NULL;
    }
}

【3.10】 无头单向非循环链表在pos位置删除

        在pos位置删除实际和上面的代码本质有点相似!

  • 第一种情况:

 

  • 第二种情况:

/* 无头单向非循环链表指定位置删函数接口 */
void SingleListErase(SListNode** ppHead, SListNode* pPos){
    //  因为ppHead拿的是pList的地址,所以一定不为空。
    assert(ppHead);
    assert(pPos);   // pos的位置不能为空。
​
    // 检查链表是否是空链表.
    if(SingleListEmpty(ppHead)) {
        printf("SingleList Empty!\n");
        return;
    }
​
    // 判断如果只有一个节点的时候,调用头删函数
    if (pPos == *ppHead)
        SingleListPopFront(ppHead);
    else
    {
        // 移动到pos的前一个位置。
        SListNode* prev = *ppHead;
        while (prev->_next != pPos)
        {
            prev = prev->_next;
​
            // 如果prev一直在往前走,走到空的位置,说明所有的节点中都没有要删除的数据。
            assert(prev);
        }
​
        // 程序跑到这里说明已经找到了要删除的数据。
        SListNode* pTemp = pPos;
        prev->_next = pPos->_next;
        free(pTemp);
        pTemp = NULL;
    }
}

【3.11】 无头单向非循环链表查找

        查找的函数比较简单不解释:

        当然查找其实还可以充当一些别的功能,比如:修改、某个位置插入、某个位置删除

/* 无头单向非循环链表查找函数接口 */
SListNode* SingleListFind(SListNode** ppHead, SLTDataType val){
    // 断言保护指针传参.
    assert(ppHead);
​
    // 检查链表是否是空链表.
    if(SingleListEmpty(ppHead)) {
        printf("SingleList Empty!\n");
        return NULL;
    }
    // 遍历查找和Val相同的值.
    SListNode* pCur = *ppHead;
    while(pCur != NULL){
        if(pCur->_data == val)
            return pCur;
        pCur = pCur->_next;
    }
​
    // 在上面循环中如果为找打val值的节点,说明没有该节点,返回NULL节点.
    return NULL;
}

【3.12】 无头单向非循环链表修改

/* 无头单向非循环链表修改函数接口 */
void SingleListModification(SListNode** ppHead, SLTDataType ModifiSource, SLTDataType ModifiTarget){
    // 断言保护指针传参.
    assert(ppHead);
    // 查找节点.
    SListNode* finNode = SingleListFind(ppHead, ModifiSource);
    if(finNode != NULL){
        finNode->_data = ModifiTarget;
    }
}

【3.13】 无头单向非循环链表打印

/* 无头单向非循环链表打印函数接口 */
void SingleListPrint(SListNode** ppHead){
    // 断言保护指针传参.
    assert(ppHead);
​
    // 检查链表是否是空链表.
    if(SingleListEmpty(ppHead)) {
        printf("SingleList Empty!\n");
        return;
    }
    // 这里不要动pHead这个指针,因为这个指针一动就找不到链表的头的位置了。
    SListNode* pCur = *ppHead;
    printf("Head->");
    // 循环到NULL指针.
    while(pCur != NULL) {
        printf("%d->", pCur->_data);
        pCur = pCur->_next;
    }
    printf("NULL\n");
}

【2.14】 无头单向非循环链表大小

/* 无头单向非循环链表大小函数接口 */
size_t SingleListSize(SListNode** ppHead){
    // 断言保护指针传参.
    assert(ppHead);
​
    // 遍历计数.
    SListNode* pCur = *ppHead;
    size_t count = 0;
    // 循环到NULL指针.
    while(pCur != NULL) {
        ++count;
        pCur = pCur->_next;
    }
    
    return count;
}

【3.15】 无头单向非循环链表判空

/* 无头单向非循环链表判空函数接口 */
bool SingleListEmpty(SListNode** ppHead){
     // 断言保护指针传参.
    assert(ppHead);
​
    // 如果对ppHead二级指针解引用,指向的是NULL,说明没有节点了.
    return (*ppHead) == NULL;
}
​

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

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

相关文章

【WinForm】C#实现商场收银软件,从面向过程到面向对象,设计模式的应用

文章目录 前言一、收银系统版本11、运行效果2、界面设计3、代码 二、收银系统版本21、运行效果2、界面设计3、代码&#xff1a; 三、收银系统版本31、运行效果2、界面设计3、代码 四、收银系统版本41、运行效果2、界面设计3、代码 总结面向对象23中设计模式总结设计模式关系图 …

【新版】系统架构设计师 - 数据库系统

个人总结&#xff0c;仅供参考&#xff0c;欢迎加好友一起讨论 文章目录 架构 - 数据库系统考点摘要数据库系统模式数据库视图数据模型&#xff08;基本数据模型&#xff09;数据库完整性约束关系模型关系代数规范化理论候选键、主键、外键、主属性&#xff0c;非主属性求候选键…

【MySQL】数据库的查询语言DQL

目录 前言&#xff1a; 一.基本查询 1.1查询多个字段 1.2设置别名 1.3去除字段中重复的值 二.条件查询 2.1条件的种类 2.1.1比较运算符 2.1.2逻辑运算符 三.结尾 前言&#xff1a; 在前面讲完了如何增删改数据表中的记录后&#xff0c;那么如何使用这些数据就成了另一…

自定义阿里云OSS上传文件的start依赖

说明&#xff1a;SpringBoot项目之所以开发起来很方便&#xff0c;是因为SpringBoot项目在启动时自动为我们装配了很多Bean对象&#xff08;参考&#xff1a;http://t.csdn.cn/MddMO&#xff09;&#xff0c;这取决于我们是否在pom.xml文件添加对应的依赖&#xff0c;称为起步依…

【Spring】循环依赖

一、什么情况下会出现循环依赖&#xff1f; 二、解决方案 &#xff08;一&#xff09;一级缓存&#xff1a;存放完整的Bean实例对象 缺点&#xff1a;一级缓存的方式无法保证多线程下的一级缓存Bean的完整性&#xff0c;可以用加锁的方式来解决此问题。 &#xff08;二&#…

springboot+vue项目之MOBA类游戏攻略分享平台(java项目源码+文档)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的MOBA类游戏攻略分享平台。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 &#x1f495;&#x1f495;作者&#xf…

Git操作方法

目录 Git是什么 Git特点 Git作用 Git原理 集中式 分布式 Git安装 修改语言 Git操作 1.初始化Git仓库 2.提交工作区的内容到版本库 3.查看版本记录 4.版本回退 5.版本前进 Git 命令 通用操作 工作状态 版本回退 版本前进 远程仓 1.GitHub 2.GitLab 3.码云…

2022年山东省职业院校技能大赛网络搭建与应用赛项网络搭建与安全部署服务器配置及应用

2022年山东省职业院校技能大赛 网络搭建与应用赛项 第二部分 网络搭建与安全部署&服务器配置及应用 竞赛说明&#xff1a; 一、竞赛内容分布 竞赛共分二个模块&#xff0c;其中&#xff1a; 第一模块&#xff1a;网络搭建及安全部署项目 第二模块&#xff1a;服务器…

后端(三):后端实战(表白墙的设计)

上一章结束了 Servlet 的学习&#xff0c;ok&#xff0c;现在我们已经学会了 1 1 了&#xff0c;现在开始我们要学会 百以内的加减乘除法。 本章就做一个最简单的 小小项目&#xff1a;表白墙。 在开始表白墙项目开始之间&#xff0c;我们先提前说好&#xff0c;这里主要跟关…

使用yolox训练自己的数据集并测试

1.首先给出yolox原模型的下载地址: ​​​​​​https://github.com/bubbliiiing/yolox-pytorch 百度网盘链接给出自己完整的模型&#xff08;包括数据集以及权重文件&#xff09;&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1JNjB42u9eGNhRjr1SfD_Tw 提取码&am…

线程的创建和使用(二)

1、线程的类和方法 Thread类是JVM用来管理线程的一个类&#xff0c;换句话说&#xff0c;每个线程都有唯一一个的Thread对象与之关联。 1.1、Thread的常见方法 方法说明Thread()创建线程对象Thread(Runnable target)使用Runnable对象创建线程对象Thread(String name)创建线程…

Python中对基本文件操作

1.文件的作用 保存数据放在磁盘中 2.打开文件 fopen(‘文件’,‘w’)或者fopen(‘文件’,‘r’) 3.文件操作 3.1 写数据(write) 如果文件不存在那么创建&#xff0c;如果存在那么就先清空&#xff0c;然后写入数据 对象open(“文件”,w) 对象.write&#xff08;“写入数…

【数据结构与算法】04 哈希表 / 散列表 (哈希函数、哈希冲突、链地址法、开放地址法、SHA256)

一种很好用&#xff0c;很高效&#xff0c;又一学就会的数据结构&#xff0c;你确定不看看&#xff1f; 一、哈希表 Hash Table1.1 核心概念1.2 哈希函数 Hash Function1.3 哈希冲突 Hash Collision1.4 哈希冲突解决1.41 方法概述1.42 链地址法 Separate Chaining1.43 开放寻址…

C语言学习笔记:指针

✨博文作者&#xff1a;烟雨孤舟 &#x1f496; 喜欢的可以 点赞 收藏 关注哦~~ ✍️ 作者简介: 一个热爱大数据的学习者 ✍️ 笔记简介&#xff1a;作为大数据爱好者&#xff0c;以下是个人总结的学习笔记&#xff0c;如有错误&#xff0c;请多多指教&#xff01; 目录 简介 …

企业会计软件必备!深入了解为何选择会计软件以及其带来的好处

随着科技的发展&#xff0c;企业需要更加智能化和数字化的财务管理方式&#xff0c;因此会计软件是现代社会的必然产物&#xff0c;会计软件可以帮助企业更有效地进行财务管理。 企业为什么需要会计软件&#xff1f; 提高准确度 通过传统的手工操作财务记录&#xff0c;会有很…

Linux常用命令——gcc命令

在线Linux命令查询工具 gcc 基于C/C的编译器 补充说明 gcc命令使用GNU推出的基于C/C的编译器&#xff0c;是开放源代码领域应用最广泛的编译器&#xff0c;具有功能强大&#xff0c;编译代码支持性能优化等特点。现在很多程序员都应用GCC&#xff0c;怎样才能更好的应用GCC…

【MySQL数据库】MySQL日志管理、备份与恢复

MySQL日志管理、备份与恢复 一、MySQL日志管理1.1日志存放位置 二、数据备份2.1物理备份与逻辑备份2.2完整备份、差异备份、增量备份2.3常见的备份方法 三、完整备份与恢复3.1物理冷备份与恢复3.2mysqldump 备份3.3mysqldump数据恢复3.4MySQL增量备份3.5MySQL增量恢复 一、MySQ…

【开发细节】SpringBoot配置文件的spring.profiles下的active和include属性的区别和作用

目录 问题作用spring.profiles.activespring.profiles.include 总结 问题 我们经常在项目的application.yml中看到这样的配置&#xff0c;如下&#xff1a; 在 Spring Boot 中&#xff0c;spring.profiles.active 和 spring.profiles.include 属性都是用来配置 profile 的。 …

【VB6|第18期】基于libxl导出Excel之导出失败的解决方案

日期&#xff1a;2023年6月12日 作者&#xff1a;Commas 签名&#xff1a;(ง •_•)ง 积跬步以致千里,积小流以成江海…… 注释&#xff1a;如果您觉得有所帮助&#xff0c;帮忙点个赞&#xff0c;也可以关注我&#xff0c;我们一起成长&#xff1b;如果有不对的地方&#xf…

安装Hive

安装Hive 准备 安装Java环境&#xff1a;Hive需要Java环境支持&#xff0c;所以需要先安装Java。安装文档&#xff1a;http://t.csdn.cn/deBJu 安装MySQL数据库。http://t.csdn.cn/d24pN 下载Hive 下载Hive的二进制文件。 链接&#xff1a;https://pan.baidu.com/s/1fdg7…