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

news2024/10/6 2:32:10

目录

无头非循环单向链表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;
}
​

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Qt中以qRegister开头的几个函数的用法说明

目录 1. 前言 2. qRegisterMetaTypeStreamOperators 2.1. 函数功能简述 2.2.用法举例1 3. qRegisterMetaType 1. 前言 Qt通过qRegister开头的函数和Q_DECLARE开头的几个宏向Qt元系统注册、声明一些非基本类型。一旦声明、注册后&#xff0c;在Qt元系统中就可以很方便的利用这…

神秘龙卷风

那道提示 打开后是一个加密压缩包&#xff0c;根据题目提示&#xff0c;这应该是一道暴力破解的题目 暴力破解后得到密码位5463 结果拿到是一串不止到啥的字符&#xff0c;根据提示应该是还要进行解码 经过查询&#xff0c;得知这个编码叫Brainfuck&#xff1a;&#xff08;下面…

【Java高级语法】(七)Object类:同志,关于Object类的情况你了解多少嘞?~

Java高级语法详解之Object类 :one: 概念:two: 使用2.1 equals()方法2.2 hashCode()方法2.3 toString()方法2.4 finalize()方法2.5 getClass()方法2.6 clone()方法2.7 wait()、notify()和 notifyAll()方法 :three: 使用场景:ear_of_rice: 总结:bookmark_tabs: 本文源码下载地址 …

【前端知识】React 基础巩固(十五)——书籍购物车简单案例

React 基础巩固(十五)——书籍购物车简单案例 案例代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta http-equiv"X-UA-Compatible" content"IEedge" /><meta name"v…

性能测试基础知识(二)性能测试流程

性能测试流程 一、性能测试需求分析二、性能测试计划和方案三、性能测试用例设计四、性能测试执行五、性能测试分析和调优六、性能测试报告总结 一、性能测试需求分析 1、熟悉被测系统&#xff08;业务功能、技术架构&#xff09; 2、明确测试内容 一般有以下几种类型&#xf…

chatgpt赋能python:Python中如何实现图形移动:探索基本概念和代码示例

Python中如何实现图形移动&#xff1a;探索基本概念和代码示例 介绍 图形移动是计算机科学中的基本概念&#xff0c;它与广泛应用的游戏、动画和用户界面设计有密切关系。 在Python中&#xff0c;我们可以使用各种库和框架来实现图形移动和动画效果&#xff0c;包括Tkinter&a…

chatgpt赋能python:Python截取指定字符操作:让你的SEO优化变得更简单

Python截取指定字符操作&#xff1a;让你的SEO优化变得更简单 在SEO优化中&#xff0c;截取指定字符是一个非常常见的操作。Python作为一款开源的高级编程语言&#xff0c;提供了许多方便的函数和方法来处理文本操作&#xff0c;包括截取指定字符。在本文中&#xff0c;我们将…

第三章 选择与循环

程序员必备技能(思想)&#xff1a;增量编写法。每写一部分代码要及时运行看结果是否正确&#xff0c;对于复杂程序很重要。 常用的运算符优先级&#xff1a; 逻辑非 &#xff01;> 算术运算符 > 关系运算符 > && > || > 赋值运算符 单目运算符 逻辑非…

chatgpt赋能python:Python如何随机生成26个字母

Python如何随机生成26个字母 Python是一种流行的编程语言&#xff0c;被广泛用于数据分析、人工智能和Web开发等领域。 Python中有很多库可以用于随机生成字符&#xff0c;包括Python内置的random库和第三方库string。 在本文中&#xff0c;我们将介绍如何使用这些库在Python中…

《Java黑皮书基础篇第10版》 第16章【笔记】

第十六章 JavaFX UI 组件和多媒体 16.1 引言 16.2 Labeled和Label在这里插入图片描述 标签(label)是一个显示小段文字&#xff0c;一个节点或同时显示两者的区域。它经常用来给其他组件(通常为文本域)做标签&#xff0c;Label类继承自Labeled抽象类 16.3 - 16.4 按钮和复选框…

目标检测YOLO实战应用案例100讲-基于深度学习的遥感目标检测算法FPGA部署实现研究

基于深度学习的目标检测网络剪枝及FPGA部署 随着科技的发展,人工智能的发展正在促进计算机视觉的智能化广泛应用。如手 机上的语音识别可以将声音转化成文字、门禁识别人脸通行、美颜相机对人像加上跟 踪特效等,这些都是人工智能在我们生活中的应用。 人工智能对图像领域…

【Docker】Linux安装步骤

目录 下载关于Docker的依赖环境安装Docker启动&#xff0c;并设置为开机自动启动&#xff0c;测试开启远程API访问端口登录harbor仓库 下载关于Docker的依赖环境 输入以下命令安装依赖环境 回车 yum -y install yum-utils device-mapper-persistent-datalvm2 安装Docker 下…

河道水面垃圾识别检测算法 yolov5

河道水面垃圾识别检测系统采用yolov5忘了模型计算机视觉技术&#xff0c;河道水面垃圾识别检测算法通过在河道上安装摄像头&#xff0c;对水面垃圾进行实时监测自动识别并记录水面垃圾&#xff0c;及时通知环保部门进行处理。近几年来&#xff0c;目标检测算法取得了很大的突破…

树莓派 4B 多串口配置

0. 实验准备以及原理 0.1 实验准备 安装树莓派官方系统的树莓派 4B&#xff0c;有 python 环境&#xff0c;安装了 serial 库 杜邦线若干 屏幕或者可以使用 VNC 进入到树莓派的图形界面 0.2 原理 树莓派 4B 有 UART0&#xff08;PL011&#xff09;、UART1&#xff08;mini …

ldsc python程序安装以及测试

教程参考&#xff1a; https://zhuanlan.zhihu.com/p/379628546https://github.com/bulik/ldsc 1. 软件安装 1.1 windows安装教程 首先配置&#xff1a; anaconda&#xff0c;为了需要conda环境git&#xff0c;为了下载github中的ldsc程序 打开windows电脑中的promote&am…

chatgpt赋能python:Python如何隐藏请求IP地址提高SEO效果

Python如何隐藏请求IP地址提高SEO效果 引言 在进行网站优化的过程当中&#xff0c;隐藏请求的IP地址是一个重要的环节。这个技巧不仅能够提高SEO的效果&#xff0c;还能够保护我们的网络安全&#xff0c;防止遭受黑客攻击。本文将介绍Python如何隐藏请求IP地址&#xff0c;以…

Spring应用启动分析优化

最近在搞应用的启动优化&#xff0c;参考一些可以显著提高 Java 启动速度方法和spring-boot-startup-report实现了此项目&#xff0c;Spring Startup Ananlyzer 采集Spring应用启动过程数据&#xff0c;生成交互式分析报告(HTML)&#xff0c;用于分析Spring应用启动卡点&#x…

让数据不再裸奔:学习使用AES加解密算法

目录 1. application.yml文件配置 2. AES加解密工具类 3. AES消息秘钥、AES秘钥初始向量、转字节数组工具类 4. AES加解密测试 我们为什么要用AES算法来进行加解密&#xff1f; AES&#xff08;Advanced Encryption Standard&#xff09;&#xff0c;又称高级加密标准&am…

C语言之生成随机数方法(C代码实现猜数字游戏)

C语言之生成随机数方法&#xff08;C代码实现猜数字游戏&#xff09; 首先先把猜数字游戏的代码给大家暂时出来&#xff0c;然后我们在根据代码的步骤一步一步的推导 #define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> #include <stdlib.h> #include <tim…

【ChatGPT】一个凭借两百多年历史的公式崛起的巨星

&#x1f482;作者简介&#xff1a; THUNDER王&#xff0c;一名热爱财税和SAP ABAP编程以及热爱分享的博主。目前于江西师范大学本科在读&#xff0c;同时任汉硕云&#xff08;广东&#xff09;科技有限公司ABAP开发顾问。在学习工作中&#xff0c;我通常使用偏后端的开发语言A…