数据结构篇-顺序表及单项链表

news2024/12/27 14:04:18

目录

一、学习目标

二、顺序表

1. 线性表

1.1 概念

1.2 举例

2. 顺序表

2.1 基本概念

2.2 基本操作

2.3 顺序表优缺点总结

三、单项链表

1. 基本概念

2. 链表的分类

无头节点:

有头节点:

增添加节点

查找节点

删除节点

链表遍历

销毁链表

有头节点与无头节点的关系:

打怪实战:

四、总结


一、学习目标

  • 知识点:
    • 一文掌握数据结构的顺序表和单项链表
    • 通过打怪实战来提升自己的理解

二、顺序表

1. 线性表

        1.1 概念

        对于一组拥有n个数据元素的线性表,其严格数学定义是:其中任何一个数据元素ai,有且仅有一个直接前驱ai-1,有且仅有一个直接后继ai+1,首元素a0无直接前驱, 尾元素an-1无直接后继。

        满足这种数学关系的一组数据,当中的数据是一个挨着一个的,常被称为一对一关系。反之,如果数据之间的关系不是一对一的,就是非线性的。

        1.2 举例

        生活中的线性表例子非常多,比如一个班级中的以学号编排的学生,一座图书馆中的以序号编排的图书、一条正常排队等候的队列、一摞从上到下堆叠的餐盘,这些都是线性表。他们的特点都是:除了首尾两个元素,其余任何一个元素前后都对应相邻的另一个元素。

注意:线性表是一种数据内部的逻辑关系,与存储形式无关线性表既可以采用连续的顺序存储,也可以采用离散的链式存储

2. 顺序表

        2.1 基本概念

  • 顺序表:顺序存储线性表
  • 链式表:链式存储线性表,简称链表。

        顺序存储就是将数据存储到一片连续的内存中,在C语言环境下,可以是具名的栈数组,或者是匿名的堆数组

        存储方式不仅仅只是提供数据的存储空间,而是必须要能体现数据之间的逻辑关系。当采用顺序存储的方式来存放数据时,唯一能用来表达数据间本身的逻辑关系的就是存储位置。比如队列中的两个人,小明和小花,如果小明在逻辑上排在相邻的小花的前面,那么在存储位置上也必须把小明存放在相邻的小花的前面。

        2.2 基本操作

  • 顺序表设计(顺序表的管理结构体
    1. 顺序表总容量
    2. 顺序表当前最末元素下标位置
    3. 顺序表入口指针
typedef int DataType ;

// 设计一个顺序表的管理结构体
typedef struct list
{
    int Capacity; // 顺序表总容量
    int End; // 末尾数据下标
    DataType * Entrance ; // 顺序表的入口地址
} List , *P_List ;
  • 初始化

P_List ListInit( unsigned int Size )
{

    // 申请一个管理结构体的空间
    P_List Cntl = calloc( 1 , sizeof(List) );
    if (Cntl == NULL)
    {
        perror("calloc Cntl error");
        return NULL ;
    }
    

    // 申请数据的存储空间,并把申请到的内存入口地址填入到管理结构体中
    Cntl->Entrance = calloc( Size , sizeof(DataType) );
    if (Cntl->Entrance == NULL)
    {
        perror("calloc Data error");
        free(Cntl); // 释放管理结构体
        return NULL ;
    }
    

    Cntl->End = -1 ;  // 初始化管理结构体中的末尾元素下标
    Cntl->Capacity = Size; // 初始化管理结构体中总容量

    // 返回管理结构体地址
    return Cntl ;
}
  • 添加数据
bool  Add2List( P_List Cntl , DataType NewData )
{

    // 判断顺序表是否已满
    if (Cntl->End == Cntl->Capacity - 1 )
    {
        printf("当前顺序表已满...\n");
        return false ;
    }

    // 比较并找到合适的位置进行插入
    int i = 0 ;
    for ( i = 0; i <= Cntl->End; i++)
    {
        if ( Cntl->Entrance[i] > NewData )
        {
            // 从当前的i的位置开始把右边的所有的数据以此(从右往左)右移
            Move2Right( Cntl , i );
            break; 
        }
    }
    
    // 插入
    // Cntl->Entrance[i] = NewData ;
    memcpy( Cntl->Entrance+i , &NewData , sizeof(DataType) );

    // 更新末尾数据下标
    Cntl->End ++ ;

    return true ;
}
  • 遍历显示数据
void DisplayList( P_List Cntl )
{

    // 判断当前顺序表是否为空
    if (Cntl->End < 0)
    {
        printf("当前顺序表为空...\n");
        return ;
    }
    

    for (int i = 0; i <= Cntl->End ; i++)
    {
        printf("%d\n" , Cntl->Entrance[i]);
    }
    

    return ;
}
  • 查找数据
int FindData( P_List  Cntl ,  DataType Data )
{
    // 判断当前顺序表是否为空
    if (Cntl->End < 0)
    {
        printf("当前顺序表为空...\n");
        return -1 ;
    }


    for (int i = 0; i <= Cntl->End ; i++)
    {
        if (Cntl->Entrance[i] == Data)
        {
            printf("成功找到指定的数据,他的存储位置是:%d\n" , i );
            return i ;
        }
    }
    
    printf("无法找到指定的数据....\n");

    return -1 ;
}  
  • 删除数据
DataType  Del4List ( P_List  Cntl , unsigned int Index )
{

    if ( Cntl->End < Index)
    {
        printf("接收到错误的参数下标..\n");
        return -1 ;
    }

    DataType tmp = Cntl->Entrance[Index];

    // 从左往右依次左移操作
    MoveLeft( Cntl , Index );

    Cntl->End -- ;

    return tmp ;
}
  • 修改数据

void UpData(P_List Cntl , DataType Data )
{
    // 找到目数据
    int Index = FindData(Cntl , Data );
    if (Index < 0)
    {
        printf("修改数据失败...\n");
        return ;
    }
    
    // 剔除该数据
    Del4List( Cntl , Index );

    // 获取新的数据
    DataType NewData = GetNewData( );

    // 有序插入
    Add2List(Cntl , NewData );

}

2.3 顺序表优缺点总结

        顺序存储中,由于逻辑关系是用物理位置来表达的,因此从上述示例代码可以很清楚看到,增删数据都非常困难,需要成片地移动数据。顺序表对数据节点的增删操作是很不友好的

总结其特点如下

  • 优点
  1. 不需要多余的信息来记录数据间的关系,存储密度高
  2. 所有数据顺序存储在一片连续的内存中,支持立即访问任意一个随机数据(立即访问),比如上述顺序表中第 i 就可以直接 Cntl->Ent[i] .
  • 缺点
  1. 插入、删除时需要保持数据的物理位置反映其逻辑关系,一般需要成片移动数据
  2. 当数据节点数量较多时,需要一整片较大的连续内存空间
  3. 当数据节点数量变化剧烈时,内存的释放和分配不灵活

三、单项链表

1. 基本概念

  • 顺序表:顺序存储的线性表。
  • 链式表:链式存储线性表,简称链表

        既然顺序存储中的数据因为挤在一起而导致需要成片移动,那很容易想到的解决方案是将数据离散地存储在不同内存块中,然后在用来指针将它们根据数据的逻辑关系串起来。这种朴素的思路所形成的链式线性表,就是所谓的链表。

顺序表和链表在内存在的基本样态如下图所示:

2. 链表的分类

根据链表中各个节点之间使用指针的个数,以及首尾节点是否相连,可以将链表细分为如下种类:

  1. 单向链表 (每一个节点只有一个指针,指向下一个数据的地址)
  2. 单向循环链表 (尾部节点的下一个指针指向的是头部节点)
  3. 双向循环链表 (每一个节点都有两个指针, 它分别指向上一个以及下一个接下,并首尾节点互相指向)
  4. 内核链表 (本质上是一个双向循环链表,与其他链表不同之处就在于的数据与链表的逻辑是剥离的)

这些不同链表的操作都是差不多的,只是指针数目的异同。以最简单的单向链表为例,其基本示意图如下所示:

        上图中,所有的节点均保存一个指针,指向其逻辑上相邻的下一个节点(末尾节点指向空)。另外注意到,整条链表用一个所谓的头指针 head 来指向,由 head 开始可以找到链表中的任意一个节点。head 通常被称为头指针。

链表的基本操作,一般包括:

  1. 节点设计
  2. 初始化空链表
  3. 增删节点
  4. 链表遍历
  5. 销毁链表

下面着重针对这几项常见操作,讲解单向链表的处理。

  1. 节点设计

  1. 初始化空链表

无头节点:

概念: 链表的头部没有多余的一个节点,所有的节点都用于存储有效数据。

P_Node head = NULL ;

有头节点:

概念: 链表的头部有一个多余的节点,该节点不存储有效数据,作用是可以防止链表在操作的时候丢失的问题。

P_Node InitList( void )
{
    // 申请头节点
    P_Node head = calloc( 1, sizeof(Node) );
    if (head == NULL)
    {
        perror ( "calloc head error" );
        return NULL ;
    }

    // 初始化头节点的指针域指向空
    head->Next = NULL ;

    // 返回给头节点
    return head;
}
  • 增添加节点

头插法

void Add2Head( P_Node head , P_Node New )
{

    // 1. 让新新节点的后继指针指向第一个有效数据 head->Next
    New->Next = head->Next ;

    // 2. 让头节点的后继指针指向新节点
    head->Next = New ;

    return ;
}

尾补插入

void Add2Tail( P_Node head , P_Node New )
{
    P_Node tmp ;

    // 使用临时指针tmp 找到链表的最后一个有效的数据节点
    for ( tmp = head ; tmp->Next != NULL; tmp = tmp->Next);

    // 从for 循环出来后 tmp 就指向了最后一个节点

    // 1. 更新新节点的后继指针
    New->Next = tmp->Next ;

    // 2. 更新末尾节点的后继指针
    tmp->Next = New ;



    return ;
}

任意位置插入(插入到某一个节点的后方):

void Add2List ( P_Node Prev , P_Node New )
{
    New->Next = Prev->Next ;
    Prev->Next = New ;
    return ;
}

有序插入

代码:

void Add2ListOrder( P_Node head , P_Node New )
{

    P_Node tmp = head ;

    // 使用tmp来寻找一个合适的位置(tmp 的下一个节点的数据是大于或等于新节点的数据)
    for ( ; tmp->Next != NULL && tmp->Next->Data < New->Data ; tmp = tmp->Next );
    

    // 把新输入插入到 tmp 的下一个位置
    Add2List( tmp , New );

}

遍历显示

void DisplayList( P_Node head )
{
    for (P_Node tmp = head->Next ; tmp != NULL  ; tmp = tmp->Next)
    {
        printf("数据:%d\n" , tmp->Data);
    }
    return ;
}
  • 查找节点

        注意: 在单向链表的操作中一般需要得到目标操作节点的前驱节点,但是由于单向没有前驱指针,所以查找函数返回的是目标节点的前一个节点的指针。

P_Node FindNode ( P_Node head , DataType DelData )
{
    P_Node tmp = NULL ;

    // 使用for寻找需要剔除的节点
    for ( tmp = head ; tmp->Next != NULL && tmp->Next->Data != DelData; tmp = tmp->Next);

    // 如果没有找到指定数据
    if (tmp->Next==NULL)
    {
        return NULL ;
    }
    

    return tmp ;
}
  • 删除节点

P_Node Del4List( P_Node head , DataType DelData )
{

    // 链表是否为空
    if (head->Next == NULL)
    {
        printf("链表为空。。。\n" );
        return NULL ;
    }
    
    // 在链表中寻找需要删除的节点的前驱节点
    P_Node Prev = FindNode ( head , DelData );
    
    if (Prev == NULL)
    {
        printf("找不到需要剔除的节点...\n");
        return NULL ;
    }
    
    printf("找到目标节点:%d\n" , Prev->Next->Data );
    

    P_Node del = Prev->Next ;
    

    // 剔除操作
    Prev->Next = del->Next ;
    del->Next = NULL ;
    

    // 返回被剔除的节点
    return del ;
}
  • 链表遍历

void DisplayList( P_Node head )
{
    for (P_Node tmp = head->Next ; tmp != NULL  ; tmp = tmp->Next)
    {
        printf("数据:%d\n" , tmp->Data);
    }
    return ;
}
  • 销毁链表

void DestroyLisr(P_Node head )
{

    // 直接退出的条件
    if (head == NULL)
    {
        return ;
    }
    
    DestroyLisr(head->Next);
    free(head);

    return ;
}

有头节点与无头节点的关系

        无头节点的链表在操作时需要时刻关系中的 头指针 的指向,在插入删除第一个数据时会改变头指针的指向

        有头节点的链表在操作时不需要关注 头指针 的指向,因为在做任何操作不会改变头指针与头节点的关系,头指针永远指向头节点。也就是说栈中的头指针的值是永远不需要改变的。

拓展方向:

    • 理解以上所写的基础版本代码
    • 初始化函数和创建新节点函数如何融合。
    • 头插尾插
    • 有序插入
    • 通用性

单向循环链表

        概念: 该链表的末尾节点的后继指针指向头节点。

        与非循环链表的不同之处在于如何判断链表的末尾:

//非循环链表:
tmp->Next == NULL ; // tmp 则指向的末尾节点

//循环链表:
tmp->Next = head ; // tmp 指向的是末尾节点

打怪实战

      • 按照自己的能力手搓链表
        • 链表的基础增删改查销毁
        • 有序插入
        • 【拓展】循环链表
      • 【拓展】假设两个链表各自有序,请设计一个函数把这两个链表进行合并后返回

                                L1 : 1 4 5 7 9 L2 : 3 6 7 8 10 合并后: L2 : 1 3 4 5 6 7 7 8 10

      • 【拓展】假设有两个链表请设计一个函数把这两个链表进行合并

                                L1 : 1 4 5 7 9 L2 : 3 6 7 8 10 合并后: L2 : 3 6 7 8 10 1 4 5 7 9

四、总结

        本文介绍了数据结构的顺序表和单项链表的基础概念和操作,理解本文所有知识点后,便可打败其中的小怪,拿下经验值~

        本文参照 粤嵌文哥 部分课件经整理和修改后发布在C站,如有转载,请联系本人

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

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

相关文章

【ARM Trace32(劳特巴赫) 使用介绍 13 -- Trace32 断点 Break 命令篇】

文章目录 1. Break.Set1.1 TRACE32 Break1.1.1 Break命令控制CPU的暂停1.2 Break.Set 设置断点1.2.1 Trace32 程序断点1.2.2 读写断点1.2.2.1 变量被改写为特定值触发halt1.2.2.2 设定非值触发halt1.2.2.4 变量被特定函数改写触发halt1.2.3 使用C/C++语法设置断点条件1.2.4 使用…

折点计数 C语言xdoj46

问题描述 给定n个整数表示一个商店连续n天的销售量。如果某天之前销售量在增长&#xff0c;而后一天销售量减少&#xff0c;则称这一天为折点&#xff0c;反过来如果之前销售量减少而后一天销售量增长&#xff0c;也称这一天为折点&#xff0c;其他的天都不是折点。如图…

AI大模型行业2024年上半年投资策略:大模型多模态化趋势显著,AI应用侧加速繁华

今天分享的AI系列深度研究报告&#xff1a;《AI大模型行业2024年上半年投资策略&#xff1a;大模型多模态化趋势显著&#xff0c;AI应用侧加速繁华》。 &#xff08;报告出品方&#xff1a;东莞证券&#xff09; 报告共计&#xff1a;30页 1.传媒行业行情和业绩回顾 1.1行业…

数据可视化:解析跨行业普及之道

数据可视化作为一种强大的工具&#xff0c;在众多行业中得到了广泛的应用&#xff0c;其价值和优势不断被发掘和利用。今天就让我以这些年来可视化设计的经验&#xff0c;讨论一下数据可视化在各个行业中备受青睐的原因吧。 无论是商业、科学、医疗保健、金融还是教育领域&…

spring 笔记一 spring快速入门和配置文件详解

Spring简介 Spring是分层的 Java SE/EE应用full-stack 轻量级开源框架&#xff0c;以 IoC&#xff08;Inverse Of Control&#xff1a;反转控制&#xff09;和AOP&#xff08;Aspect Oriented Programming&#xff1a;面向切面编程&#xff09;为内核。 提供了展现层SpringMV…

如何FL Studio显示中文?切换语言教程

你是不是也在为fl studio的英文界面而苦恼&#xff1f;你是不是也想让你的fl studio 说中文&#xff0c;方便你制作音乐&#xff1f;你是不是也在网上找了很多教程&#xff0c;却发现都是复杂的&#xff0c;或者已经过时的&#xff1f;如果你的答案是肯定的&#xff0c;那么你来…

c++国际象棋有人机qt5.9.9启动chesss

项目简介&#xff1a; 利用C的知识和QT以及一些自行拓展的新知识&#xff0c;实现国际象棋的开发。 使自己更加深层的理解和掌握c并在程序中展现出来&#xff0c;同时开发编程的思想和能力&#xff0c;以及扩展知识面&#xff0c;学习一些课上没有涉及的内容。同时通过阅读一…

应用程序映射的 5 个安全优势

现代企业依靠无数的软件应用程序来执行日常运营。这些应用程序相互连接并协同工作以提供所需的服务。了解这些应用程序如何相互交互以及底层基础设施对于任何组织都至关重要。这就是应用程序映射概念的用武之地。 顾名思义&#xff0c;应用程序映射是创建应用程序架构&#xf…

Java键值对Pair的使用方式和操作流程

Java键值对Pair的使用方式和操作流程 什么是键值对 键值对是一种常见的数据结构&#xff0c;它由一个唯一的键&#xff08;key&#xff09;和与之关联的值&#xff08;value&#xff09;组成。键和值之间存在一种映射关系&#xff0c;通过键可以查找或访问对应的值。 在键值对…

设计模式——原型模式(创建型)

引言 原型模式是一种创建型设计模式&#xff0c; 使你能够复制已有对象&#xff0c; 而又无需使代码依赖它们所属的类。 问题 如果你有一个对象&#xff0c; 并希望生成与其完全相同的一个复制品&#xff0c; 你该如何实现呢&#xff1f; 首先&#xff0c; 你必须新建一个属于…

【vue实战项目】通用管理系统:信息列表,信息的编辑和删除

本文为博主的vue实战小项目系列中的第七篇&#xff0c;很适合后端或者才入门的小伙伴看&#xff0c;一个前端项目从0到1的保姆级教学。前面的内容&#xff1a; 【vue实战项目】通用管理系统&#xff1a;登录页-CSDN博客 【vue实战项目】通用管理系统&#xff1a;封装token操作…

【日积月累】Spring中的AOP与IOC相关问题详解

Spring中的AOP与IOC 1.前言2.Spring AOP&#xff08;面向切面编程&#xff09;2.1 AOP的实现过程2.2 AOP代理模式的类型2.2.1JDK的动态代理2.2.2CGLIB的动态代理 2.3AOP应用常见场景2.3.1日志记录 2.4对AOP的理解 3.Spring IOC&#xff08;Inversion of Control&#xff0c;控…

【JVM从入门到实战】(五)类加载器

一、什么是类加载器 类加载器&#xff08;ClassLoader&#xff09;是Java虚拟机提供给应用程序去实现获取类和接口字节码数据的技术。 类加载器只参与加载过程中的字节码获取并加载到内存这一部分。 二、jdk8及之前的版本 类加载器分为三类&#xff1a; 启动类加载器-加载Ja…

Linux驱动入门 —— LED点灯驱动程序

目录 IMX6ULL 的 GPIO 操作方法 GPIO 操作相关名词 IMX6ULL 的 GPIO 模块结构 GPIO 模块内部 读 GPIO​编辑 写 GPIO​编辑 LED 点灯驱动程序 字符设备驱动程序框架 编写驱动程序的步骤&#xff1a; 先编写驱动程序代码&#xff1a; 再编写测试程序代码&#xff1a;…

【华为数据之道学习笔记】4-2信息架构原则:建立企业层面的共同行为准则

信息架构承载了企业如何管理数据资产的方法&#xff0c;需要从整个企业 层面制订统一的原则&#xff0c;这些原则不仅是对数据专业人员的要求&#xff0c;也是对业务的要求&#xff0c;因为业务才是真正的数据Owner。所以&#xff0c;公司所有业务部门都应该共同遵从信息架构原…

【人工智能】人工智能中的Agent:法律虚拟助手简单示例

人工智能中的Agent&#xff1a;法律虚拟助手简单示例 随着人工智能技术的发展&#xff0c;Agent&#xff08;代理&#xff09;的概念在这个领域中变得愈发重要。在人工智能的应用中&#xff0c;Agent可以是一个系统、软件或机器人&#xff0c;能够执行特定的任务&#xff0c;理…

【JNA与C++基本使用示例】

JNA中java与C使用注意事项和代码示例 JNA关系映射表使用案列注意代码示例C代码java代码 JNA关系映射表 使用案列 注意 JNA只支持C方式的dll使用C的char* 作为返回值时&#xff0c;需要返回的变量为malloc分配的地址C的strlen函数只获得除/0以外的字符串长度 代码示例 C代码…

jumpserver web终端无法展示资产信息

前言 最近搭建了jumpServer 然后在web终端无法展示资产信息 错误展示 我的资产列表里面是有资产的 解决办法&#xff1a;

element Transfer 穿梭框 内容太长显示不全,鼠标移动上去显示全部

element Transfer中文字太长会造成显示不全&#xff0c;然后加了个提示 我这边是加了个插槽的形式&#xff0c;根据长度判断的&#xff0c;这个有个弊端就是如果是手机号的话&#xff0c;可能会没有省略号&#xff0c;然后也会有个黑色的提示框 <el-transferfilterable :d…

基于SSM实现的精品课程网站

一、系统架构 前端&#xff1a;jsp | js | css | jquery | bootstrap 后端&#xff1a;spring | springmvc | mybatis 环境&#xff1a;jdk1.7 | mysql | maven | tomcat 二、代码及数据库 三、功能介绍 01. 登录页 02. web端-首页 03. web端-视频教程 04. web端-资料…