看完书上的链表还不会实现?不进来看看?

news2024/10/3 0:24:39

1.1链表的概念

定义:

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

特点:

链表由一系列节点组成,节点在运行时动态生成 (malloc),每个节点包括两个部分:

一个是存储数据元素的数据域

另一个是存储下一个节点地址的指针域

如图:

1.2链表的概述

链表作为 C 语言中一种基础的数据结构,在平时写程序的时候用的并不多,但在操作系统里面使用的非常多。不管是RTOS还是Linux等使用非常广泛,所以必须要搞懂链表,链表分为单向链表和双向链表,单向链表很少用,使用最多的还是双向链表。单向链表懂了双向链表自然就会了。

1.3单向不带头不循环的链表的初始化

参数的传入:涉及改变链表的操作统统用指针传链表(其中特别注意的是需要改变传入的头结点时需要传入二级指针)不然函数调用完成之后,为传入的链表分配的的内存会自动释放,链表不会有任何改变。创建头结点,为头结点分配内存。

令头节点的指针为空指针(指针不初始化容易出现很多问题)

PS:这里为什么要动态分配内存呢?

因为线性表的顺序存储结构用数组实现,而数组占用的是一整块内存,数组元素分布很集中,需要提前预定数组的长度。而链表是一种动态结构,它所占用的大小和位置是不需要提前分配的,可以根据自身的需求即时生成。

第二步:创建第二个节点,将其放在第一个节点的后面(第一的节点的指针域保存第二个节点的地址)

第三步:再次创建节点,找到原本链表中的最后一个节点,接着讲最后一个节点的指针域保存新节点的地址,以此内推。

1.4单链表的一些操作

1.4.1链表的遍历

1.输出第一个节点的数据域,输出完毕后,让指针保存后一个节点的地址

2.输出移动地址对应的节点的数据域,输出完毕后,指针继续后移

3.以此类推,直到节点的指针域为NULL。

1.4.2链表的查找

// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x) {
        assert(plist);
        SListNode* cur = plist;
        while (cur)
        {
            if (cur->data == x)
            {
                return cur;
            }
            cur = cur->next;
        }
        return NULL;
}

1.4.3链表的头插头删,尾插尾删


// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x)
{
    if (*pplist == NULL)
    {
        SListNode* newnode = BuySListNode(x);
        *pplist = newnode;
 
    }
    else
    {
        SListNode *newnode = BuySListNode(x);
        newnode->next=*pplist;
        *pplist = newnode;
    }
}
// 单链表头删
void SListPopFront(SListNode** pplist)
{
    assert(*pplist);
    if ((*pplist)->next == NULL)//对结构体指针的地址传值时访问该结构体的成员先得对结构体地址解引用
    {
        SListNode* cur = *pplist;
        //直接free(*pplist),会使得pplist被置为随机值
        free(cur);
        *pplist = NULL;
    }
    else
    {
        SListNode *cur = *pplist;
        *pplist = (*pplist)->next;//左右两边的星号都不要漏。
        free(cur);
    }
}
 
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x)
{
    if (*pplist == NULL)
    {
        SListNode* newnode = BuySListNode(x);
        *pplist = newnode;
    }
    else
    {
        SListNode* tail = *pplist;
        SListNode* newnode = BuySListNode(x);
        while (tail->next)
        {
            tail = tail->next;
        }
        tail->next = newnode;
    }
}
// 单链表的尾删
void SListPopBack(SListNode** pplist) {
    assert(*pplist);
    SListNode* tail=*pplist,*prev=*pplist;
    if (tail->next == NULL)
    {
        free(tail);
        *pplist = NULL;
    }
    else 
    {
        while (tail->next)
        {
            prev = tail;
            tail = tail->next;
        }
        prev->next = NULL;
        free(tail);
        tail = NULL;
    }
}

1.4.4单链表的插入

// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?因为找pos之前的位置需要O(n)的时间复杂度
void SListInsertAfter(SListNode* pos, SLTDateType x) {
    assert(pos);
    SListNode* newnode = BuySListNode(x);
    newnode->next = pos->next;
    pos->next = newnode;
}
// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos) {
    assert(pos&&pos->next);
    SListNode* next = pos->next;
    pos->next = next->next;
    free(next);
    next = NULL;
}

// 单链表在pos位置之后插入x

// 分析思考为什么不在pos位置之前插入?

因为找pos之前的位置需要O(n)的时间复杂度进行查找到pos的前一个元素

1.4.5链表节点的删除

如果链表为空,不需要删除

如果删除的是第一个结点,则需要将保存链表首地址的指针保存第一个结点的下一个结点的 地址

如果删除的是中间结点,则找到中间结点的前一个结点,让前一个结点的指针域保存这个结 点的后一个结点的地址即可

// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos) {
    assert(pos&&pos->next);
    SListNode* next = pos->next;
    pos->next = next->next;
    free(next);
    next = NULL;
}

1.4.6单链表的销毁

重新定义一个指针q,保存p指向节点的地址,然后p后移保存下一个节点的地址,然后释放q对应的节点,以此类推,直到p为NULL为止。

// 单链表的销毁
void SListDestroy(SListNode* plist) {
    while (plist)
    {
        SListNode* next = plist;
        free(plist);
        plist = next;
    }
}

1.5双向带头不循环的链表

虽然听起来复杂,但其实只是结构复杂,但是遍历还是增删查改插入和删除等都是十分简单的

#include"List.h"
 
ListNode* BuyListNode(int x)
{
    ListNode* new = (ListNode*)malloc(sizeof(ListNode));
    new->data = x;
    new->next = NULL;
    new->prev = NULL;
    return new;
}
// 创建返回链表的头结点.
ListNode* ListCreate(void)
{
    ListNode* newhead = (ListNode*)malloc(sizeof(ListNode));
    newhead->next = newhead;
    newhead->prev = newhead;
    return newhead;
}
// 双向链表销毁
void ListDestory(ListNode* pHead)
{
    assert(pHead);
    ListNode* cur = pHead;
    while (cur)
    {
        ListNode* next = cur->next;
        free(cur);
        cur = next;
    }
}
// 双向链表打印
void ListPrint(ListNode* pHead)
{
    assert(pHead);
    ListNode* cur = pHead->next;
    while (cur != pHead)
    {
        printf("%d->", cur->data);
        cur = cur->next;
    }
    printf("NULL\n");
}
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
    assert(pHead);
    ListNode* tail = pHead->prev;
    ListNode* new = BuyListNode(x);
    new->next = tail->next;
    tail->next->prev = new;
    new->prev = tail;
    tail->next = new;
}
// 双向链表尾删
//需要注意此处如果链表初始化后不断删除会使得pHead指针指向的地方不确定,如果后续用户未重新创造双向链表的哨兵位而继续插入数据,会导致非法访问。
//或者加个条件判断判断是否只有一个哨兵位,是的话就不再进行删除。
void ListPopBack(ListNode* pHead)
{
    assert(pHead);
    ListNode* tail = pHead->prev;
    tail->prev->next= tail->next;//左边修改的是指针域,右边是具体的链表的位置。
    tail->next->prev = tail->prev;
    free(tail);
    //tail = NULL;
}
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
    ListInsert(pHead->next, x);
}
// 双向链表头删
void ListPopFront(ListNode* pHead)
{
    assert(pHead);
    ListNode* cur = pHead->next;
    if (cur != pHead)
    {
        pHead->next = cur->next;
        cur->next->prev = pHead;
        free(cur);
    }
    cur = NULL;
}
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x) {
    assert(pHead);
    ListNode* cur = pHead->next;
    while (cur != pHead)
    {
        if (cur->data == x)
        {
            return cur;
        }
        cur = cur->next;
    }
    if (cur == pHead)
    {
        return NULL;
    }
}
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x) {
    assert(pos);
    ListNode *new=BuyListNode(x);
    ListNode* prev = pos->prev;
    new->next = pos;
    pos->prev = new;
    new->prev = prev;
    prev->next = new;
}
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos) {
    ListNode* prev = pos->prev;
    prev->next = pos->next;
    pos->next->prev = prev;
    free(pos);

}

着重注意这段

// 双向链表尾删

//需要注意此处如果链表初始化后不断删除会使得pHead指针指向的地方不确定,如果后续用户未重新创造双向链表的哨兵位而继续插入数据,会导致非法访问。

//或者加个条件判断判断是否只有一个哨兵位,是的话就不再进行删除。

void ListPopBack(ListNode* pHead)

{

assert(pHead);

ListNode* tail = pHead->prev;

tail->prev->next= tail->next;//左边修改的是指针域,右边是具体的链表的位置。

tail->next->prev = tail->prev;

free(tail);

//tail = NULL;

}

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

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

相关文章

【react】类组件

React类创建组件&#xff0c;通过继承React内置的Component来实现的 class MyComponent extends React.Component{render() {console.log(this)// render是放在哪里的 —— 类(即&#xff1a;MyComponent)的原型对象上&#xff0c;供实例使用return <h2>我是用函数定义的…

python实现波士顿房价预测

波士顿房价预测 目标 这是一个经典的机器学习回归场景&#xff0c;我们利用Python和numpy来实现神经网络。该数据集统计了房价受到13个特征因素的影响&#xff0c;如图1所示。 对于预测问题&#xff0c;可以根据预测输出的类型是连续的实数值&#xff0c;还是离散值&#xff…

QGraphicsItem的简单自定义图形项

QGraphicsItem的继承重写序言重点函数QRectF boundingRect() constQPainterPath shape() constvoid paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget 0)序言 学习途中记录一下&#xff0c;可谓是精华点 重点函数 QRectF boundingRect()…

农产品销售系统/商城,可运行

文章目录项目介绍一、项目功能介绍1、用户模块主要功能包括&#xff1a;2、商家模块主要功能包括&#xff1a;3、管理员模块主要功能包括&#xff1a;二、部分页面展示1、用户模块部分功能页面展示2、商家模块部分功能页面展示3、管理员模块部分功能页面展示三、部分源码四、底…

蓝牙 - 设备类型设置: Class of Device

在电脑或手机上&#xff0c;搜寻和连接蓝牙设备时&#xff0c;不同的蓝牙设备显示的图标是不同的&#xff0c;比如搜到或连接上的设备是一个蓝牙键盘&#xff0c;显示的就会是键盘图标&#xff0c;如果搜索到的设备是一个手柄&#xff0c;显示的就是一个手柄图标。 显示的图标是…

进程(操作系统408)

进程的概念和特征 概念&#xff1a; 进程的多个定义&#xff1a; 进程是程序的一次执行过程 进程是一个程序及其数据在处理机上顺序执行时所发生的活动 进程时具有独立功能的程序在一个数据集合上运行的过程&#xff0c;它是系统进行资源分配和调度的一个独立单位 上面所说…

JVM的基本知识

JVM JVM是java的虚拟机,是一个十分复杂的东西,所以掌握的要求比较高.本文主要是研究JVM的三大话题 JVM内存划分JVM类加载JVM的垃圾回收 JVM内存划分 java程序要执行的时候,JVM会先申请一块空间,这里就涉及到JVM的内存划分 堆 : 放的是new 出来的对象栈: 放的是方法之间的调…

rabbitmq集群-镜像模式

上文参考&#xff1a; rabbitmq集群-普通模式 1. 什么是镜像模式 它和普通集群最大的区别在于 Queue 数据和原数据不再是单独存储在一台机器上&#xff0c;而是同时存储在多台机器上。也就是说每个 RabbitMQ 实例都有一份镜像数据&#xff08;副本数据&#xff09;。每次写入…

3月8号作业

题目&#xff1a;题目一&#xff1a;vmlinux可执行文件如何产生题目二&#xff1a;整理内核编译流程&#xff1a;uImage&#xff0c;zImage,Image,vmlinux之间的关系答案一&#xff1a;在内核源码目录下vi Makefile&#xff0c;搜索vmlinux目标&#xff0c;vmlinux: scripts/li…

MongoDB学习(java版)

MongoDB概述 结构化数据库 ​ 结构化数据库是一种使用结构化查询语言&#xff08;SQL&#xff09;进行管理和操作的数据库&#xff0c;它们的数据存储方式是基于表格和列的。结构化数据库要求数据预先定义数据模式和结构&#xff0c;然后才能存储和查询数据。结构化数据库通常…

Android Camera SDK NDK NDK_vendor介绍

Android Camera JNI NDK NDK_vendor介绍前言主要有哪几种interface&#xff1f;Android SDKCamera API 1Camera API 2小结Android NDKNDK InterfaceNDK Vendor Interface小结Camera VTS Testcase总结Reference前言 本篇博客是想介绍Android camera从application layer到camera…

谷歌插件Fetch在不同页面之间Cookie携带情况详解

content script 和 script inject 表现情况 在碰到content script 注入和用script标签注入一样&#xff0c;即使服务端有写入Cookie到域名下在该tab标签应用下也不会被保存&#xff0c;所以在发送时也无法自动携带&#xff0c;所以通过content script和<script>这种方式…

微信小程序第二节 —— 自定义组件。

&#x1f449;微信小程序第一节 —— 自定义顶部、底部导航栏以及获取胶囊体位置信息。 一、前言 &#x1f4d6;&#x1f4d6;&#x1f4d6;书接上回 &#xff0c;dai ga hou啊&#xff01;我是 &#x1f618;&#x1f618;&#x1f618;是江迪呀。在进行微信小程序开发中&am…

多维数组的地址,通过指针引用多维数组详解

通过指针引用一维数组可以参考这篇文章&#xff1a; 通过指针引用数组的几种方法的原理和差异&#xff1b;以及利用指针引用数组元素的技巧_juechen333的博客-CSDN博客一个数组包含若干元素&#xff0c;每个数组元素都占用存储单元&#xff0c;所以他们都有相应的地址&#xf…

《Ansible模块篇:debug模块详解》

一、简介 平时我们在使用ansible执行playbook时&#xff0c;经常可能会遇到一些错误&#xff0c;有的时候不知道问题在哪里 &#xff0c;这个时候可以使用-vvv参数打印出来详细信息&#xff0c;不过很多时候-vvv参数里很多东西并不是我们想要的&#xff0c;这时候就可以使用官方…

python第四天作业~函数练习

目录 作业4、判断以下哪些不能作为标识符 A、a B、&#xffe5;a C、_12 D、$a12 E、false F、False 作业5&#xff1a; 输入数&#xff0c;判断这个数是否是质数&#xff08;要求使用函数 for循环&#xff09; 作业6&#xff1a;求50~150之间的质数是…

ReentrantLock 源码解读

一、ReentrantLock ReentrantLock 是 java JUC 中的一个可重入锁&#xff0c;在上篇文章讲解 AQS 源码的时候提到 ReentrantLock 锁是基于 AQS 实现的&#xff0c;那是如何使用的 AQS 呢&#xff0c;本篇文章一起带大家看下 ReentrantLock 的源码。 在 AQS 中&#xff0c;如果…

linux安装influxdb-rpmyum方式

一、influxdb的安装InfluxDB简介时序数据库InfluxDB版是一款专门处理高写入和查询负载的时序数据库&#xff0c;用于存储大规模的时序数据并进行实时分析&#xff0c;包括来自DevOps监控、应用指标和IoT传感器上的数据主要特点&#xff1a;专为时间序列数据量身订造高性能数据存…

uniapp——ucharts的使用

ucharts是一个类似于echarts的图表框架 引入 在Hbuilder的插件下载仓库中搜索ucharts &#xff01;&#xff01;值得一提的是&#xff0c;Hbuilder的版本必须是大于3.1.0的&#xff01;&#xff01; 这样就导入到了项目的uni_modules里了 ucharts下载完成后&#xff0c;可以…

Java——异常机制

前言 随着对java的不断深入学习&#xff0c;在对语法以及编程思想有了一定的了解之后&#xff0c;在编程的过程中有可能会因为用户的输入不正确或者逻辑错误而出现异常或者错误&#xff0c;因此如何去捕捉与避免不应该出现的异常或者错误就变得十分重要。本文就介绍了java的异…