数据结构入门:详解顺序表的实现与操作

目录

1.线性表

2.顺序表

2.1概念与结构

2.2分类

2.2.1静态顺序表

2.2.2动态顺序表 

3.动态顺序表的实现

3.1.SeqList.h

3.2.SeqList.c

3.2.1初始化

 3.2.2销毁

3.2.3打印

3.2.4顺序表扩容 

3.2.5尾部插入及尾部删除 

3.2.6头部插入及头部删除 

3.2.7特定位置插入及删除 

3.2.8查找元素,返回下标 

 4.顺序表算法题

1.力扣:移除元素

核心思想

算法步骤

 2.力扣:删除有序数组的重复项

核心思想

算法步骤

 3.力扣:合并两个有序数组

核心思想

算法步骤


1.线性表

在讲顺序表之前,我们首先要介绍一个概念,线性表。

线性表(Linear List)是数据结构中最基础、最常用的一种结构,它的特点是数据元素之间按线性顺序排列,每个元素最多有一个前驱和一个后继。

线性表有两种实现方式,其一是顺序表,其二是链表。以下我们来为他们做一个简单区分:

顺序表(顺序存储)链表(链式存储)
实现方式数组存储元素,内存连续分配结点存储元素和指针,内存非连续分配
特点
  • 支持随机访问(通过下标直接访问元素,时间复杂度 O(1))。

  • 插入/删除可能需要移动大量元素(时间复杂度 O(n))。

  • 存储空间固定(需预先分配,可能浪费或不足)。

  • 插入/删除高效(时间复杂度 O(1),仅需修改指针)。

  • 不支持随机访问(查找需从头遍历,时间复杂度 O(n))。

  • 存储空间动态分配,更灵活。

适用场景元素数量固定、频繁查询、少增删。频繁增删、元素数量变化大。

表格内容暂时看不明白没有关系,这里只是做一个简介,相信等你看完博客之后会有一个新的体会。

今天我们着重介绍的是顺序表,链表会放在下一篇博客中精讲。


2.顺序表

2.1概念与结构

概念:顺序表是⽤⼀段物理地址连续的存储单元依次存储数据元素的线性结构,⼀般情况下采⽤数组存储。

顺序表和数组的区别? 

顺序表是数据结构中线性表的一种实现方式,本质是基于数组的封装,通过数组实现元素的连续存储,并添加了对数据操作的逻辑(如插入、删除、扩容等)。

2.2分类

2.2.1静态顺序表

定义与特点

  • 固定容量:在创建时分配固定大小的内存空间,无法动态扩展或收缩

  • 内存分配:通常在编译阶段确定(如全局数组)或在栈上分配(如局部数组),内存管理由系统自动完成。

  • 操作限制:仅支持基本的读写操作,插入和删除需手动移动元素,效率较低。

#define SLDataType int   // 定义顺序表元素类型为int(便于后期类型修改)
#define N 7              // 定义顺序表容量为7(固定大小)// 静态顺序表结构体定义
typedef struct Seqlist { // 类型重命名为SLSLDataType arr[N];   // 固定大小的数组存储元素int size;            // 记录当前有效元素个数(核心状态标识)
} SL;                    // 类型重命名简化

2.2.2动态顺序表 

 定义与特点

  • 可变容量:支持运行时动态调整容量(扩展或收缩),通过重新分配内存实现。

  • 内存分配:通常在堆上动态分配,由程序逻辑管理(如 malloc 或 new)。

  • 高级操作:封装了插入、删除、扩容等复杂逻辑,用户无需手动处理内存。

// 定义顺序表存储的元素类型为int
// 使用宏定义方便后续修改元素类型(例如改为float或自定义结构体)
#define SLDataType int  // 动态顺序表结构体定义
typedef struct Seqlist {SLDataType* arr;   // 指向动态分配数组的指针(存储元素)int size;          // 当前顺序表中有效元素的数量int capacity;      // 当前顺序表的总容量
} SL;  // 使用typedef将结构体重命名为SL,简化代码

将静态顺序表和动态数据表做一个对比:

特性静态顺序表动态顺序表
存储方式固定大小数组(栈内存/全局区)动态分配数组(堆内存)
容量编译时确定(#define N 7),不可变运行时动态调整(通过malloc/realloc
内存分配自动分配和释放(无需手动管理)需手动管理(malloc/free
扩容/缩容不支持,大小固定支持,可动态调整(如capacity不足时扩容)

经过比对,我们发现 

  • 静态顺序表简单但僵化,适合确定性的小规模数据。

  • 动态顺序表复杂但强大,是现代编程语言中动态数组的基础实现。

本文将以实现动态顺序表为主,详细讲述动态顺序表的扩容,头插,尾插,删除等操作。


3.动态顺序表的实现

3.1.SeqList.h

#pragma once#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int SLDataType;// 动态顺序表 -- 按需申请
typedef struct SeqList
{SLDataType* arr;int size; // 有效数据个数int capacity; // 空间容量
}SL;//初始化和销毁
void SLInit(SL* ps);
void SLDestroy(SL* ps);
void SLPrint(SL* ps);//扩容
void SLCheckCapacity(SL* ps);//头部插⼊删除 / 尾部插⼊删除
void SLPushBack(SL* ps, SLDataType x);
void SLPopBack(SL* ps);
void SLPushFront(SL* ps, SLDataType x);
void SLPopFront(SL* ps);//指定位置之前插⼊/删除数据
void SLInsert(SL* ps, int pos, SLDataType x);
void SLErase(SL* ps, int pos);int SLFind(SL * ps, SLDataType x);

一个动态顺序表至少应该能够实现以上功能,下面我们来逐一实现。

3.2.SeqList.c

3.2.1初始化
void SLInit(SL* ps)
{ps->arr = NULL;ps->capacity = 0;ps->size = 0;
}
 3.2.2销毁
void SLDestroy(SL* ps)
{if (ps->arr){free(ps->arr);}ps->arr = NULL;ps->capacity = 0;ps->size = 0;
}

注意:这里释放空间需要判断指针是否为空,如果是空指针直接释放会报错,同时我们要检查的是ps->arr,并不是ps,需要额外注意,不要释放ps的空间和将ps置空指针

3.2.3打印
void SLPrint(SL* ps)
{for (int i = 0; i < ps->size; i++){printf("%d ", ps->arr[i]);}printf("\n");
}
3.2.4顺序表扩容 
void SLCheckCapacity(SL* ps)
{// 检查是否需要扩容(当前元素数 == 当前容量)if (ps->capacity == ps->size){// 计算新容量:如果当前容量为0(初始状态)则设为4,否则扩容2倍int new_capacity = ps->capacity == 0 ? 4 : 2 * (ps->capacity);// 尝试重新分配内存SLDataType* tem = realloc(ps->arr, new_capacity * sizeof(SLDataType));// 处理内存分配失败的情况if (tem == NULL){perror("realloc");  // 打印错误信息exit(EXIT_FAILURE); // 终止程序(EXIT_FAILURE是标准错误退出码)}// 更新顺序表结构ps->arr = tem;           // 指向新分配的内存ps->capacity = new_capacity; // 更新容量值}
}

关键点说明:

  1. 扩容时机

    • size == capacity时触发扩容,这是为了防止下一次插入操作导致溢出

  2. 扩容策略

    • 初始容量设为4(常见设计,避免初期频繁扩容)

    • 之后每次按2倍扩容(标准库常用策略,均摊时间复杂度为O(1))

  3. 内存管理

    • 使用realloc而不是malloc,可以保留原有数据

    • 必须检查返回值是否为NULL(内存分配可能失败)

  4. 错误处理

    • 使用perror输出错误信息(会显示系统级的错误原因)

    • EXIT_FAILURE是标准库定义的错误退出码(通常值为1)

  5. 安全性

    • 先将realloc结果赋给临时变量tem,确认成功后再赋给ps->arr

    • 避免直接ps->arr = realloc(...)导致内存泄漏

3.2.5尾部插入及尾部删除 
/*** @brief 在顺序表尾部插入一个元素* @param ps 指向顺序表结构的指针(必须非空)* @param x 要插入的元素值*/
void SLPushBack(SL* ps, SLDataType x) {assert(ps);              // 确保顺序表指针有效(若ps为NULL则终止程序)SLCheckCapacity(ps);     // 检查并扩容(若容量不足)ps->arr[ps->size++] = x; // 在尾部插入元素,并更新size
}/*** @brief 删除顺序表尾部元素(逻辑删除)* @param ps 指向顺序表结构的指针(必须非空且arr有效)*/
void SLPopBack(SL* ps) {assert(ps && ps->arr);  // 确保顺序表指针和内部数组指针均有效ps->size--;             // 减少size,忽略最后一个元素(逻辑删除)
}
3.2.6头部插入及头部删除 
/*** @brief 在顺序表头部插入一个元素* @param ps 指向顺序表结构的指针(必须非空且内部数组已初始化)* @param x 要插入的元素值* @note 时间复杂度 O(n),需要移动所有元素*/
void SLPushFront(SL* ps, SLDataType x) {// 1. 参数校验assert(ps && ps->arr);  // 确保顺序表指针和内部数组指针有效// 2. 容量检查(必要时扩容)SLCheckCapacity(ps);    // 检查并处理容量不足的情况// 3. 移动元素:从后往前逐个后移for (int i = ps->size; i > 0; i--) {ps->arr[i] = ps->arr[i - 1];  // 将元素向后移动一位}// 4. 插入新元素到头部ps->arr[0] = x;  // 在数组头部放入新元素// 5. 更新元素计数ps->size++;      // 顺序表长度+1
}/*** @brief 删除顺序表头部元素* @param ps 指向顺序表结构的指针(必须非空且顺序表不为空)* @note 时间复杂度 O(n),需要移动剩余所有元素*/
void SLPopFront(SL* ps) {// 1. 参数校验assert(ps && ps->size > 0);  // 确保顺序表有效且不为空// 2. 移动元素:从前往后逐个前移for (int i = 0; i < ps->size - 1; i++) {ps->arr[i] = ps->arr[i + 1];  // 将元素向前移动一位}// 3. 更新元素计数ps->size--;  // 顺序表长度-1/* 可选:清零最后一个元素位置的值ps->arr[ps->size] = 0;  // 防止脏数据*/
}
3.2.7特定位置插入及删除 
/*** @brief 在顺序表指定位置前插入元素* @param ps 指向顺序表结构的指针(必须非空)* @param pos 要插入的位置索引(0 ≤ pos ≤ ps->size)* @param x 要插入的元素值* @note 时间复杂度 O(n),需要移动元素* @note 边界情况:*       - pos=0:相当于头部插入*       - pos=size:相当于尾部追加*/
void SLInsert1(SL* ps, int pos, SLDataType x) {assert(ps);  // 确保顺序表指针有效assert(pos >= 0 && pos <= ps->size);  // 检查位置合法性SLCheckCapacity(ps);  // 检查并扩容(若容量不足)// 从后往前移动元素,腾出pos位置for (int i = ps->size; i > pos; i--) {ps->arr[i] = ps->arr[i - 1];  // 元素后移}ps->arr[pos] = x;  // 在pos位置插入新元素ps->size++;        // 更新元素数量
}/*** @brief 在顺序表指定位置后插入元素* @param ps 指向顺序表结构的指针(必须非空)* @param pos 参考位置索引(0 ≤ pos < ps->size)* @param x 要插入的元素值* @note 时间复杂度 O(n),需要移动元素* @note 边界情况:*       - pos=0:在第一个元素后插入*       - pos=size-1:相当于尾部追加*/
void SLInsert2(SL* ps, int pos, SLDataType x) {assert(ps);  // 确保顺序表指针有效assert(pos >= 0 && pos < ps->size);  // 检查位置合法性SLCheckCapacity(ps);  // 检查并扩容// 从后往前移动元素,腾出pos+1位置for (int i = ps->size; i > pos + 1; i--) {ps->arr[i] = ps->arr[i - 1];  // 元素后移}ps->arr[pos + 1] = x;  // 在pos+1位置插入新元素ps->size++;            // 更新元素数量
}/*** @brief 删除顺序表指定位置的元素* @param ps 指向顺序表结构的指针(必须非空且顺序表非空)* @param pos 要删除的位置索引(0 ≤ pos < ps->size)* @note 时间复杂度 O(n),需要移动元素* @note 边界情况:*       - pos=0:删除头部元素*       - pos=size-1:删除尾部元素*/
void SLErase(SL* ps, SLDataType pos) {assert(ps && ps->size > 0);  // 确保顺序表有效且非空assert(pos >= 0 && pos < ps->size);  // 检查位置合法性// 从pos位置开始,用后续元素覆盖当前元素for (int i = pos; i < ps->size - 1; i++) {ps->arr[i] = ps->arr[i + 1];  // 元素前移}ps->size--;  // 更新元素数量/* 可选:清除残留数据(防止脏数据)ps->arr[ps->size] = 0;*/
}
3.2.8查找元素,返回下标 
/*** @brief 在顺序表中查找指定元素的位置* @param ps 指向顺序表结构的指针(必须非空)* @param x 要查找的元素值* @return 如果找到,返回元素的索引(0 ≤ index < size);*         如果未找到,返回 -1* @note 时间复杂度 O(n),需要遍历数组*/
int SLFind(SL* ps, SLDataType x) {assert(ps);  // 确保顺序表指针有效// 遍历顺序表查找元素for (int i = 0; i < ps->size; i++) {if (ps->arr[i] == x) {return i;  // 找到元素,返回索引}}return -1;  // 未找到元素
}

 4.顺序表算法题

1.力扣:移除元素

核心思想

使用 双指针技巧

  • src(源指针):遍历原始数组,检查每个元素。

  • dst(目标指针):记录非 val 元素的存储位置。

算法步骤

  1. 初始化 src 和 dst 为 0。

  2. 遍历数组:

    • 如果 nums[src] == val:跳过该元素(src++)。

    • 如果 nums[src] != val:将其复制到 nums[dst],然后 src++ 和 dst++

  3. 最终 dst 的值即为新数组的长度。

代码:

int removeElement(int* nums, int numsSize, int val) {int src = 0,dst = 0;while(src<numsSize){if(nums[src]==val)src++;else{nums[dst] = nums[src];dst++;src++;}}return dst;
}

 2.力扣:删除有序数组的重复项

核心思想

使用 双指针技巧

  • dst(目标指针):记录唯一元素的存储位置。

  • src(源指针):遍历数组,寻找与 dst 不同的元素。

算法步骤

  1. 初始化 dst = 0(第一个元素必定唯一),src = 1

  2. 遍历数组:

    • 如果 nums[dst] == nums[src]:跳过重复元素(src++)。

    • 如果 nums[dst] != nums[src]:将 nums[src] 复制到 nums[++dst],然后 src++

  3. 最终 dst + 1 即为新数组的长度(因为索引从 0 开始)。

 代码:

int removeDuplicates(int* nums, int numsSize) {int dst = 0,src = 1;while(src<numsSize){if(nums[dst]==nums[src]){src++;}else{nums[++dst] = nums[src++];}}return dst+1;
}

 3.力扣:合并两个有序数组

核心思想

使用 逆向双指针法 从后向前合并,避免覆盖 nums1 中的未处理元素。

算法步骤

  1. 初始化指针

    • l1 = m - 1:指向 nums1 的最后一个有效元素。

    • l2 = n - 1:指向 nums2 的最后一个元素。

    • l3 = m + n - 1:指向 nums1 的末尾(合并后的位置)。

  2. 逆向遍历合并

    • 比较 nums1[l1] 和 nums2[l2],将较大的值放入 nums1[l3],并移动对应指针。

    • 重复直到 nums1 或 nums2 遍历完毕。

  3. 处理剩余元素

    • 如果 nums2 有剩余元素(l2 >= 0),直接复制到 nums1 的前端。

代码:

void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n) {int l1 = m-1,l2 = n-1,l3 = m+n-1;while(l1>=0 && l2>=0){if(nums1[l1]>nums2[l2]){nums1[l3] = nums1[l1];l3--;l1--;}else{nums1[l3] = nums2[l2];l2--;l3--;}}while(l2>=0){nums1[l3] = nums2[l2];l2--;l3--;}
}

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

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

相关文章

LeetCode热题100--53.最大子数组和--中等

1. 题目 给你一个整数数组 nums &#xff0c;请你找出一个具有最大和的连续子数组&#xff08;子数组最少包含一个元素&#xff09;&#xff0c;返回其最大和。 子数组是数组中的一个连续部分。 示例 1&#xff1a; 输入&#xff1a;nums [-2,1,-3,4,-1,2,1,-5,4] 输出&…

python:练习:2

1.题目&#xff1a;统计一篇英文文章中每个单词出现的次数&#xff0c;并按照出现次数排序输出。 示例输入&#xff1a; text "Python is an interpreted, high-level, general-purpose programming language. Created by Guido van Rossum and first released in 1991…

AI Agent 孵化器?开源框架CAMEL

简介 CAMEL&#xff08;Communicative Agents for Mind Exploration of Large Scale Language Model Society&#xff09;是一个开源框架&#xff0c;大语言模型多智能体框架的先驱者。旨在通过角色扮演和自主协作&#xff0c;探索大语言模型&#xff08;LLM&#xff09;在多智…

关于插值和拟合(数学建模实验课)

文章目录 1.总体评价2.具体的课堂题目 1.总体评价 学校可以开设这个数学建模实验课程&#xff0c;我本来是非常的激动地&#xff0c;但是这个最后的上课方式却让我高兴不起哦来&#xff0c;因为老师讲的这个内容非常的简单&#xff0c;而且一个上午的数学实验&#xff0c;基本…

LayerSkip: Enabling Early Exit Inference and Self-Speculative Decoding

TL;DR 2024 年 Meta FAIR 提出了 LayerSkip&#xff0c;这是一种端到端的解决方案&#xff0c;用于加速大语言模型&#xff08;LLMs&#xff09;的推理过程 Paper name LayerSkip: Enabling Early Exit Inference and Self-Speculative Decoding Paper Reading Note Paper…

解决ktransformers v0.3 docker镜像中 operator torchvision::nms does not exist 问题

问题背景 更新ktransformers docker镜像到v0.3版本后&#xff08;之前为v0.2.4post1&#xff09;&#xff0c;使用更新前启动命令无法正确启动服务&#xff0c;提示以下错误&#xff1a; Traceback (most recent call last):File "/workspace/ktransformers/ktransforme…

如何系统学习音视频

学习音视频技术涉及多个领域&#xff0c;包括音频处理、视频处理、编码解码、流媒体传输等。 第一阶段&#xff1a;基础知识准备 目标&#xff1a;掌握音视频学习所需的计算机科学和数学基础。 计算机基础 学习计算机网络基础&#xff08;TCP/IP、UDP、HTTP、RTSP等协议&#…

TiDB 可观测性最佳实践

TiDB 介绍 TiDB&#xff0c;由 PingCAP 公司自主研发的开源分布式关系型数据库&#xff0c;是一款创新的 HTAP 数据库产品&#xff0c;它融合了在线事务处理&#xff08;OLTP&#xff09;和在线分析处理&#xff08;OLAP&#xff09;的能力&#xff0c;支持水平扩容和缩容&…

使用FreeRTOS解决单片机串口异步打印

单片机串口异步打印 文章目录 单片机串口异步打印前言设计思路准备队列创建完整代码 总结 前言 &#x1f30a;在单片机开发中串口的异步打印异步打印允许单片机在执行其他任务的同时进行打印操作&#xff0c;无需等待打印完成后再继续执行后续代码&#xff0c;避免了在多处调用…

代码颜色模式python

1. CMYK&#xff08;印刷场景&#xff09; 例子&#xff1a;某出版社设计书籍封面时&#xff0c;使用 Adobe Illustrator 绘制图案。 红色封面的 CMYK 值可能为&#xff1a;C0, M100, Y100, K0&#xff08;通过洋红和黄色油墨混合呈现红色&#xff09;。印刷前需将设计文件转…

HarmonyOS NEXT 诗词元服务项目开发上架全流程实战(二、元服务与应用APP签名打包步骤详解)

在HarmonyOS应用开发过程中&#xff0c;发布应用到应用市场是一个重要的环节。没经历过的童鞋&#xff0c;首次对HarmonyOS的应用签名打包上架可能感觉繁琐。需要各种秘钥证书生成和申请&#xff0c;混在一起分不清。其实搞清楚后也就那会事&#xff0c;各个文件都有它存在的作…

【BotSharp框架示例 ——实现聊天机器人,并通过 DeepSeek V3实现 function calling】

BotSharp框架示例 ——实现聊天机器人&#xff0c;并通过 DeepSeek V3实现 function calling 一、一点点感悟二、创建项目1、创建项目2、添加引用3、MyWeatherPlugin项目代码编写4、WeatherApiDefaultService项目代码编写5、WebAPI MyWeatherAPI 的项目代码编写6、data文件夹中…

百度CarLife实现手机车机无缝互联

百度CarLife是百度推出的智能车联网解决方案&#xff0c;通过手机与车机互联技术&#xff0c;为用户提供安全便捷的车载互联网服务体验。 CarLife 实现手机与车机屏幕的无缝互联&#xff0c;让应用内容同步至车载系统&#xff0c;减少驾驶过程中操作手机的频率&#xff0c;提升…

基于STM32的虚线绘制函数改造

改造前&#xff1a; uint16_t DrawLine(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) { // GUI_DrawLine( x1, y1, x2, y2); // return 1;int16_t deltaX, deltaY;int16_t error, stepErrorLT, stepErrorGE;int16_t stepX, stepY;int16_t steep;int16_t…

Java高频面试之并发编程-10

hello啊&#xff0c;各位观众姥爷们&#xff01;&#xff01;&#xff01;本baby今天来报道了&#xff01;哈哈哈哈哈嗝&#x1f436; 面试官&#xff1a;ThreadLocalMap 怎么解决 Hash 冲突的&#xff1f; ThreadLocalMap 是 ThreadLocal 的核心实现&#xff0c;它采用 开放…

AI应用实战:Excel表的操作工具

有个小需求是这样的&#xff0c;需要在一份数据表里&#xff0c;将1000多个客户的月报数据分别单独截图存档&#xff0c;有客户需要的时候就要发给客户&#xff0c;截图下来的也是以客户为命名&#xff0c;这样查找时也比较容易匹配上。 在没有写工具之前&#xff0c;以往财务…

使用 DoH 查询域名 —— 以 core.tantanapp.com 为例的实战分析

前言 在现代 iOS 应用中&#xff0c;为了确保 DNS 查询的隐私和完整性&#xff0c;我们可以使用 DoH&#xff08;DNS over HTTPS&#xff09; 来查询域名信息。 本文将以 https://cloudflare-dns.com/dns-query?namecore.tantanapp.com&typeA 为例&#xff0c;通过 Postm…

Python----卷积神经网络(卷积为什么能识别图像)

一、卷积的概念 卷积是一种数学运算&#xff0c;通常用于信号处理和图像分析。在卷积神经网络中&#xff0c;卷积操作用于提取输入数据&#xff08;如图像&#xff09;中的特征。通过将输入数据与卷积核&#xff08;滤波器&#xff09;进行卷积运算&#xff0c;CNN能够识别图像…

linux FTP服务器搭建

FTP服务器搭建 系统环境&#xff1a;ubuntu 搭建方式&#xff1a;win系统下通过ssh连接ubuntu&#xff0c;搭建FTP服务 一、ssh连接 ssh -p 端口 用户名IP ssh -p 22 ubuntu192.168.1.109 密码&#xff1a;ubuntu123456 二、安装配置FTP服务器 1、安装 sudo apt install v…

语音合成之十韵律之美:TTS如何模拟语音的节奏和语调

韵律之美&#xff1a;TTS如何模拟语音的节奏和语调 1. 引言&#xff1a;韵律在语音合成中的重要性1.1 追求自然的TTS&#xff1a;超越可懂度1.2 定义韵律&#xff1a;语音的音乐1.3 韵律为何重要&#xff1a;传递意义、情感与自然度 2. TTS韵律建模的基础技术2.1 利用文本&…