当前位置: 首页 > news >正文

嵌入式培训之数据结构学习(二)顺序表与单向链表

目录

一、顺序表

(一)顺序表的基本操作

1、创建顺序表

2、销毁顺序表

3、遍历顺序表

4、尾插,在顺序表的最后插入元素

5、判断表是否满

6、判断表是否空

7、按指定位置插入元素

8、查找元素,根据名字

9、根据名字修改指定元素

10、根据名字删除指定元素

11、清空表,清空表中已有的元素

12、获得表中有效元素的个数

13、获得指定下标的元素本身

(二)顺序表优缺点

二、链表

(一)链表基础概念

(二)单向链表(重点)

(三)单向链表的基本操作

1、创建链表

2、判断链表是否为空

3、获取链表长度

4、头插,在链表的前面插入一个结点

5、遍历打印链表

6、根据名字查找链表中的结点

7、根据名字删除链表中的结点


一、顺序表

(一)顺序表的基本操作

1、创建顺序表

SeqList *CreateSeqList(int len)

{
    SeqList *sl = malloc(sizeof(SeqList));  // 分配顺序表结构体空间
    if (NULL == sl) {
        fprintf(stderr, "CreateSeqList malloc error\n");  // 分配失败报错
        return NULL;
    }
    sl->head = malloc(sizeof(DATATYPE) * len);  // 分配数据存储数组空间
    if (NULL == sl->head) {
        fprintf(stderr, "CreateSeqList malloc2 error\n");  // 数组分配失败报错
        return NULL;
    }
    sl->tlen = len;  // 存储总长度(最大容量)
    sl->clen = 0;    // 初始元素个数为0
    return sl;       // 返回顺序表指针
}

2、销毁顺序表

int DestroySeqList(SeqList *list)

{
        if(NULL == list)

        {
                return 1; // 空指针返回错误码1
        }
         //先检查顺序表指针是否为空
 
        free(list->head); // 释放数据存储区(动态分配的数组)
        free(list); // 释放顺序表结构体本身
        return 0; // 销毁成功,返回0
        //先释放数据区内存,再释放顺序表结构体内存,避免内存泄漏

}

3、遍历顺序表

int ShowSeqList(SeqList *list)

{
    int len = GetSizeSeqList(list);  // 获取当前元素个数
    int i = 0;
    for (i = 0; i < len; ++i) {
        // 打印每个元素的成员(DATATYPE包含name、sex、age、score)
        printf("%s %c %d %d\n", 
               list->head[i].name, 
               list->head[i].sex, 
               list->head[i].age, 
               list->head[i].score);
    }
    return 0;
}

4、尾插,在顺序表的最后插入元素

int InsertTailSeqList(SeqList *list, DATATYPE *data)

{
    if (IsFullSeqList(list)) {  // 检查是否已满
        fprintf(stderr, "seqlist full\n");
        return 1;
    }
    // 方式1:直接赋值 *data 到当前末尾位置
    // list->head[list->clen] = *data;
    
    // 方式2:使用memcpy复制数据(适用于结构体中有指针成员的情况,避免浅拷贝)
    memcpy(&list->head[list->clen], data, sizeof(DATATYPE));
    
    list->clen++;  // 元素个数+1
    return 0;       // 成功返回0
}

5、判断表是否满

int IsFullSeqList(SeqList *list)

{
    if (NULL == list) {
        fprintf(stderr, "IsFullSeqList parameter error\n");  // 空指针检查
        return 1;  // 返回非0表示错误或已满(此处逻辑为:错误按"满"处理,可能需优化)
    }
    return list->clen == list->tlen;  // 元素个数等于总长度时返回1(满),否则0
}

6、判断表是否空

int IsEmptySeqList(SeqList *list)

{
    // 正确写法:判断元素个数是否为0
    return 0 == list->clen;
    // return list->clen = 0;  // 会将clen置0并返回0,逻辑错误
}

7、按指定位置插入元素

int InsertPosSeqList(SeqList *list, DATATYPE *data, int pos)

{
    if (IsFullSeqList(list)) return 1;  // 判满
    int len = GetSizeSeqList(list);
    if (pos < 0 || pos > len) {         // 位置合法性检查(允许pos==len,即尾插)
        return 1;
    }
    // 从末尾开始,将[pos, len-1]的元素后移一位
    int i = 0;
    for (i = list->clen; i > pos; i--) {
        // 方式1:直接赋值
        // list->head[i] = list->head[i-1];
        
        // 方式2:使用memcpy复制结构体
        memcpy(&list->head[i], &list->head[i-1], sizeof(DATATYPE));
    }
    // 在pos位置插入新元素
    memcpy(&list->head[pos], data, sizeof(DATATYPE));
    list->clen++;  // 元素个数+1
    return 0;
}

8、查找元素,根据名字

int FindSeqList(SeqList *list, char *name)

{
    int i = 0;
    int len = GetSizeSeqList(list);
    for (i = 0; i < len; ++i) {
        if (0 == strcmp(list->head[i].name, name)) {  // 字符串比较,相等时返回下标
            return i;
        }
    }
    return -1;  // 未找到返回-1
}

9、根据名字修改指定元素

int ModifySeqList(SeqList *list, char *old, DATATYPE *newdata)

{
//参数: old 为旧名称, newdata 为新数据指针
 
        if (IsEmptySeqList(list))

        {
            return 1; // 空表返回错误码1
        }
         //先检查顺序表是否为空
 
        int ret = FindSeqList(list, old);
        if (-1 == ret)

         {
            return 1; // 未找到旧元素,返回错误码1
          }
         //查找旧名称 old 的位置,未找到则返回错误
 
        memcpy(&list->head[ret], newdata, sizeof(DATATYPE));

        // 直接替换目标位置的数据


        return 0; // 修改成功,返回0
 }

10、根据名字删除指定元素

int DeleteSeqList(SeqList *list, char *name) //参数: list 为顺序表指针, name 为待删除元素的名称

{
        if (IsEmptySeqList(list)) {
        return 1; // 空表返回错误码1
}
 //先检查顺序表是否为空,空表无法删除元素,返回错误码


        int ret = FindSeqList(list, name);
        if (-1 == ret)

         {
            return 1; // 未找到目标元素,返回错误码1
         }
 //调用 FindSeqList 查找 name 的位置,返回下标 ret 
//若 ret=-1 ,说明元素不存在,返回错误码


         int i = 0;

         int len = GetSizeSeqList(list);

        // 获取当前元素总数
        //初始化循环变量 i ,获取顺序表当前长度
 
        for (i = ret; i < len - 1; ++i)

        {
            memcpy(&list->head[i], &list->head[i + 1], sizeof(DATATYPE));
        }
        //核心逻辑:从目标位置 ret 开始,将后续元素向前移动一位(覆盖删除)
        //memcpy 用于按数据类型大小批量复制内存,实现元素前移
 
        list->clen--; // 元素总数减1
        return 0; // 删除成功,返回0

}

11、清空表,清空表中已有的元素

int ClearSeqList(SeqList *list)


     list->clen = 0; // 直接将当前长度置0,不释放内存
     return 0; 
}
 //重置顺序表长度为0,逻辑清空(物理内存未释放)

12、获得表中有效元素的个数

int GetSizeSeqList(SeqList *list)


    return list->clen;  // 直接返回当前元素个数
}

13、获得指定下标的元素本身

DATATYPE *GetItemSeqList(SeqList *list, int ind)

{
    if (NULL == list) return NULL;  // 空指针检查
    int len = GetSizeSeqList(list);
    if (ind < 0 || ind > len) {     
        return NULL;
    }
    return &list->head[ind];  // 返回指定索引处的元素地址
}

(二)顺序表优缺点

1、优点:

(1)无需为表中的逻辑关系增加额外的存储空间;

(2)可以快速随机访问元素O(1)。

2、缺点:

(1)插入,删除元素需要移动元素o(n);

(2)无法动态存储

二、链表

(一)链表基础概念

1、作用:解决顺序存储的缺点,插入和删除,动态存储问题。

2、特点:线性表链式存储结构的特点是一组任意的存储单位存储线性表的数据元素

                存储单元可以是连续的,也可以不连续;

                可以被存储在任意内存未被占用的位置上

3、注:所以前面的顺序表只需要存储数据元素信息就可以了,但在链式结构中还需要

             一个元素存储下一个元素的地址

4、为了表示每个数据元素,ai与其直接后继数据元素ai+1之间的逻辑关系,对ai来说,

      除了存储其本身的信息外,还需要存一个指示器直接后续的信息;

        把存储元素信息的域叫数据域,把存储直接后继位置的域叫指针域

        这两部分信息组成数据元素ai的存储映像,叫结点(Node)

(二)单向链表(重点)

单向链表的链式结构

注:表头结构可要可不要

(三)单向链表的基本操作

1、创建链表

LinkList* CreateLinkList()

{
    LinkList* ll = malloc(sizeof(LinkList));  // 分配链表头结点内存
    if (NULL == ll) {                        // 检查内存分配失败
        fprintf(stderr, "CreateLinkList malloc");  // 错误输出
        return NULL;
    }
    ll->head = NULL;          // 初始化头指针为空
    ll->clen = 0;             // 初始化链表长度为0
    return ll;                // 返回链表头指针
}

2、判断链表是否为空

int IsEmptyLinkList(LinkList* ll)


    return 0 == ll->clen;  // 直接通过长度判断空链表
}

3、获取链表长度

int GetSizeLinkList(LinkList* ll)

{
    return ll->clen;  // 直接返回链表长度
}

4、头插,在链表的前面插入一个结点

(1)图解

第一种情况(第一个放进去)

第二种情况(放在第二个)

第一步:

第二步:

(2)算法实现

int InsertHeadLinkList(LinkList* ll, DATATYPE* data)

{
    LinkNode* newnode = malloc(sizeof(LinkNode));  // 分配新结点内存
    if (NULL == newnode) {                         // 检查内存分配失败
        fprintf(stderr, "InsertHeadLinkList malloc");
        return 1;
    }
    memcpy(&newnode->data, data, sizeof(DATATYPE));  // 复制数据到新结点
    newnode->next = NULL;                            // 新结点初始next为空

    if (IsEmptyLinkList(ll)) {                       // 若链表为空
        ll->head = newnode;                          // 新结点作为头结点
    } else {                                         // 若链表非空
        newnode->next = ll->head;                    // 新结点指向原头结点
        ll->head = newnode;                          // 新结点成为头结点
    }
    ll->clen++;                                      // 链表长度+1
    return 0;                                        // 成功返回0
}

5、遍历打印链表

(1)图解

定义临时指针tmp

循环tmp = tmp->next

(2)算法实现

int ShowLinkList(LinkList* ll)

{
    int len = GetSizeLinkList(ll);       // 获取链表长度
    LinkNode* tmp = ll->head;            // 临时指针指向头结点
    int i = 0;

    for (i = 0; i < len; ++i) {          // 循环遍历每个结点
        // 打印节点数据(假设DATATYPE包含name、sex、age、score字段)
        printf("%s %c %d %d\n", 
               tmp->data.name, 
               tmp->data.sex, 
               tmp->data.age, 
               tmp->data.score);
        tmp = tmp->next;                 // 移动临时指针到下一个结点
    }
    return 0;
}

6、根据名字查找链表中的结点

DATATYPE* FindLinkList(LinkList* ll, char* name)
 {
 //参数:-  ll :指向链表头节点的指针;
              -  name :待查找的目标姓名(字符串);
              - 返回值:若找到匹配节点,返回该节点的  DATATYPE  数据指针;否则     返回  NULL 。
 
        //初始化临时指针
        LinkNode* tmp = ll->head;
         //作用:创建临时指针  tmp ,指向链表的头节点,用于遍历链表。
 
        //循环遍历链表
         while (tmp) 
        {
        //条件: tmp  不为空时继续循环(即未遍历到链表末尾)。
        // 逻辑:从头部开始逐个访问节点,直到  tmp  为  NULL (遍历结束)。
 
        //字符串匹配检查
        if (0 == strcmp(tmp->data.name, name)) 
        {
            return &tmp->data;
        }
        //strcmp  函数:比较两个字符串( tmp->data.name  和  name )。
        //若相等,返回  0 ,进入条件分支。
        //若不等,返回非零值,跳过分支。
        //返回数据指针:若匹配成功,返回当前节点数据域的地址  &tmp->data (类型为  DATATYPE* )。
 
        //移动临时指针
         tmp = tmp->next;
         //作用:将  tmp  指向下一个结点,继续遍历。
 
        //遍历结束,返回空指针
         return NULL;

7、根据名字删除链表中的结点

(1)图解:

情况一:不是头删

定义tmp指向头结点

tmp指向待删结点的前一个

情况二头删:

(2)算法实现:

int DeleteLinkList(LinkList* ll, char* name)
{
         //参数:
         ll :链表头指针(指向  LinkList  结构体,包含头结点                  head  和                  长度  clen )。
         name :待删除结点的姓名(字符串)。
         返回值:成功删除返回  0 ,失败(链表为空或未找到结点)返回                                           1 。

        //初始化临时指针

        LinkNode* tmp = ll->head;

        //作用:创建临时指针  tmp ,指向链表头结点,用于遍历和定位结点。
 
        //检查链表是否为空

        if (IsEmptyLinkList(ll))

        {

                return 1;//空链表无法删除,直接返回错误

        }

        //处理头结点匹配的情况

        if (0 == strcmp(tmp->data.name, name))

        {

                    ll->head = ll->head->next; // 头结点后移一位
                    free(tmp);                // 释放原头结点内存
                    ll->clen--;               // 链表长度减1
                    return 0;                 // 返回成功

        }

        // 条件:若头结点的  name  与目标  name  相等( strcmp  返回  0 )。
         //操作:将头指针  ll->head  指向原头结点的下一个结点;
                         用  free  释放原头结点的内存(避免内存泄漏);
                         链表长度  clen  减  1 ;
                         返回  0  表示删除成功.

         //遍历查找后续结点

          while (tmp->next)

          {

        //循环条件: tmp->next  不为空(即当前结点后还有结点,未到链表末尾);
         目的:从第二个结点开始,逐个检查后续结点是否匹配目标姓名;
         检查当前结点的下一个结点是否匹配.

                if (0 == strcmp(tmp->next->data.name, name))

                {

                //当前结点的下一个结点( tmp->next )的 name  与目标 name  相等

                            LinkNode* tmp2 = tmp->next; // 记录待删除结点
                            tmp->next = tmp->next->next; // 跳过待删除结点,连接前后结点
                            free(tmp2);                  // 释放待删除结点内存
                            ll->clen--;                  // 链表长度减1
                            return 0;                    // 返回成功

                }

                tmp = tmp->next;//将 tmp  指向下一个结点,继续向后遍历链表

        }

        return 1;//未找到目标结点,返回删除失败

}

http://www.xdnf.cn/news/419671.html

相关文章:

  • 2025年的电脑能装win7吗_2025年组装电脑装win7详细图文教程
  • 是 OpenCV 的 CUDA 模块中用于在 GPU 上对图像或矩阵进行转置操作函数cv::cuda::transpose
  • LeetCode 热题 100_多数元素(97_169_简单_C++)(哈希表;排序)
  • 带格式的可配置文案展示
  • 基于单应性矩阵变换的图像拼接融合
  • 水滴Android面经及参考答案
  • React面试常问问题详解
  • AJAX 简介
  • 经典中的经典-比特币白皮书中文版
  • 【RabbitMQ】七种工作模式介绍
  • day19-线性表(顺序表)(链表)
  • 里氏替换原则:Java 面向对象设计的基石法则
  • langchain学习
  • nvidia驱动更新-先卸载再安装-ubuntu
  • Jsp技术入门指南【十三】基于 JSTL SQL 标签库实现 MySQL 数据库连接与数据分页展示
  • 解锁课程编辑器之独特风姿
  • pdf url 转 图片
  • loki grafana 页面查看 loki 日志偶发 too many outstanding requests
  • 基于大模型预测的视神经脊髓炎技术方案大纲
  • Flannel UDP 模式的优缺点
  • MySQL的Docker版本,部署在ubantu系统
  • Starrocks的主键表涉及到的MOR Delete+Insert更新策略
  • 2025年化学工程与材料物理国际会议(CEMP 2025)
  • [学习] RTKLib详解:qzslex.c、rcvraw.c与solution.c
  • 移动端前端开发调试工具/webkit调试工具/小程序调试工具WebDebugX使用教程
  • OpenCV的CUDA模块进行图像处理
  • 文件相关操作
  • 通过QPS和并发数定位问题
  • 网络体系结构(OSI,TCP/IP)
  • 3.4 数字特征