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

【数据结构】 [特殊字符] 顺序表详解——数据结构的第一块基石

“所有复杂的数据结构,都是从最简单的线性表演化而来。”
——《大话数据结构》


一、什么是“顺序表”?

在学习数据结构的第一步,我们会接触一个最基本、最常用的概念:线性表(Linear List)

所谓线性表,就是“零个或多个数据元素的有限序列”,听起来有点抽象,我们换个说法👇
可以把它想象成一条火车,每一节车厢就是一个“元素”,每一节车厢有且只有一个前车厢(除第一节)和一个后车厢(除最后一节)。


📦 顺序表是什么?

顺序表(Sequential List)是一种线性表的顺序存储结构,即:

用一组连续的存储单元依次存放线性表中的各个元素。

通俗理解:

  • 它像是一排整齐排列的“格子”(内存空间连续),
  • 每个格子里存放一个数据元素,
  • 元素之间的逻辑顺序与它们在内存中的物理顺序一致。

🔍 举个例子:

假设我们要存储学生的学号 [1001, 1002, 1003, 1004]

在内存中,顺序表的存放情况大致是这样的:

内存地址内容(元素值)
0x00101001
0x00141002
0x00181003
0x001C1004

这些内存地址是连续的,这就是“顺序存储”的特点。


二、顺序表的基本结构

顺序表通常用数组来实现。
在 C/C++、Java、Python 等语言中都可以用数组(或列表)来代表。

🎯 定义结构(C语言示例)

#define MAXSIZE 100  // 最大容量
typedef int ElemType;  // 元素类型可自定义typedef struct {ElemType data[MAXSIZE];  // 存储元素的数组int length;              // 当前长度(有效元素个数)
} SqList;

这里的 SqList 就是一个顺序表结构体,包含两部分:

  1. data[MAXSIZE]:存放数据元素的数组;
  2. length:记录当前表中实际存放了多少个元素。

三、顺序表的主要操作

顺序表支持一系列常见操作:增、删、查、改、遍历
我们来一步步理解。


1️⃣ 插入(Insert)

在第 i 个位置插入一个新元素。

思路

  1. 检查插入位置是否合法(1 ≤ i ≤ length+1)
  2. 从最后一个元素开始,依次向后移动;
  3. 把新元素放到空出来的位置;
  4. 长度加1。

示例代码:

bool ListInsert(SqList *L, int i, ElemType e) {if (i < 1 || i > L->length + 1) return false;  // 位置非法if (L->length >= MAXSIZE) return false;        // 空间已满for (int j = L->length - 1; j >= i - 1; j--) {L->data[j + 1] = L->data[j];  // 元素后移}L->data[i - 1] = e;  // 插入新元素L->length++;return true;
}

时间复杂度:O(n)(因为可能要移动很多元素)


2️⃣ 删除(Delete)

删除第 i 个位置的元素。

思路

  1. 检查删除位置是否合法;
  2. 记下被删除元素(方便返回);
  3. i 位置后一个元素开始依次前移;
  4. 长度减1。

示例代码:

bool ListDelete(SqList *L, int i, ElemType *e) {if (i < 1 || i > L->length) return false;*e = L->data[i - 1];for (int j = i; j < L->length; j++) {L->data[j - 1] = L->data[j];}L->length--;return true;
}

时间复杂度:O(n)(同样因为移动元素)


3️⃣ 查找(Locate)

查找值为 e 的元素位置。

思路: 从头到尾逐个比较,直到找到目标元素。

int LocateElem(SqList L, ElemType e) {for (int i = 0; i < L.length; i++) {if (L.data[i] == e)return i + 1;  // 位置从1开始}return 0;  // 未找到
}

时间复杂度:O(n)
如果是有序顺序表,可以使用二分查找,效率更高(O(log n))。


4️⃣ 访问(GetElem)

直接访问第 i 个元素。

因为顺序表在内存中是连续存储的,所以:

L.data[i - 1]

即可直接访问。

时间复杂度:O(1)
这也是顺序表最大的优势之一!


5️⃣ 遍历(Traverse)

void TraverseList(SqList L) {for (int i = 0; i < L.length; i++) {printf("%d ", L.data[i]);}printf("\n");
}

四、顺序表的优缺点分析

优点 👍缺点 👎
随机访问速度快(O(1))插入、删除效率低(O(n))
内存连续,结构简单容量固定(除非动态扩展)
适合存储静态数据不适合频繁增删的场景

⚖️ 举个生活例子

想象你有一排整齐放好的书架(顺序表),
每本书都有固定位置编号,你可以瞬间找到第N本书(随机访问)。

但如果要在第2本和第3本之间插入一本新书
就得把后面的书都往后挪一格 —— 这就比较麻烦了。


🧠 五、动态顺序表(可扩展版)

上面的顺序表用的是固定数组(ElemType data[MAXSIZE]),
这在实际中有很大局限:一旦容量满了,就不能再存新元素

为了解决这个问题,我们需要一种能自动扩展容量的顺序表,也就是:

✅ “动态顺序表”(Dynamic Sequential List)


🌱 动态顺序表的基本思想

动态顺序表使用堆区动态分配的数组来存储数据,
当容量不够时,它会自动:

  1. 分配一块更大的新内存;
  2. 把旧数据复制过去;
  3. 释放旧的内存;
  4. 更新容量信息。

这就像一个“会自己长大的容器”🌾。


📦 结构定义(C++实现)

#include <iostream>
#include <cstring>  // memcpytemplate <typename T>
class SeqList {
private:T* data;         // 指向动态数组的指针int length;      // 当前元素个数int capacity;    // 当前容量(最大可存元素数)// 私有函数:扩容操作void expandCapacity() {int newCapacity = capacity * 2;  // 扩容为原来的2倍T* newData = new T[newCapacity]; // 分配新空间// 拷贝旧数据std::memcpy(newData, data, sizeof(T) * length);delete[] data;   // 释放旧空间data = newData;  // 更新指针capacity = newCapacity;std::cout << "[扩容成功] 新容量: " << capacity << std::endl;}public:// 构造函数SeqList(int initCap = 10) {capacity = initCap;data = new T[capacity];length = 0;}// 析构函数~SeqList() {delete[] data;}// 获取长度int size() const { return length; }// 判断是否为空bool empty() const { return length == 0; }// 按位置访问元素T& operator[](int index) {if (index < 0 || index >= length)throw std::out_of_range("索引越界");return data[index];}// 插入元素(在末尾)void push_back(const T& value) {if (length >= capacity) expandCapacity();data[length++] = value;}// 插入元素(指定位置)void insert(int pos, const T& value) {if (pos < 0 || pos > length)throw std::out_of_range("插入位置非法");if (length >= capacity) expandCapacity();for (int i = length; i > pos; i--) {data[i] = data[i - 1];}data[pos] = value;length++;}// 删除元素(指定位置)void erase(int pos) {if (pos < 0 || pos >= length)throw std::out_of_range("删除位置非法");for (int i = pos; i < length - 1; i++) {data[i] = data[i + 1];}length--;}// 遍历输出void print() const {for (int i = 0; i < length; i++) {std::cout << data[i] << " ";}std::cout << std::endl;}
};

⚙️ 使用示例

int main() {SeqList<int> list(3);  // 初始容量为3list.push_back(10);list.push_back(20);list.push_back(30);list.push_back(40);  // 触发自动扩容list.insert(2, 99);  // 在第2个位置插入元素list.print();  // 输出: 10 20 99 30 40list.erase(3); // 删除第3个位置的元素list.print();  // 输出: 10 20 99 40std::cout << "当前长度: " << list.size() << std::endl;return 0;
}

🧩 输出结果:

[扩容成功] 新容量: 6
10 20 99 30 40
10 20 99 40
当前长度: 4

可以看到:

  • 当容量不足时,自动打印出“扩容成功”提示;
  • 插入、删除、访问都和静态顺序表一样;
  • 但它能动态增长,更灵活!

📊 动态顺序表的性能分析

操作平均时间复杂度说明
随机访问O(1)支持下标访问
插入(尾部)O(1)*摊还复杂度(扩容时为O(n))
插入(中间)O(n)需要移动元素
删除O(n)需要移动元素
扩容O(n)需重新分配与复制数据

⚠️ “摊还复杂度”是指虽然单次扩容很耗时,但总体平均下来仍然高效。


💬 与 C++ STL vector 的关系

其实,你刚才实现的 SeqList
就是一个简化版的 std::vector

自定义 SeqListC++ STL vector
push_back()✅ 同名函数
insert()✅ 同功能
erase()✅ 同功能
自动扩容✅ 同逻辑(2倍扩容)
支持随机访问✅ 支持 [] 运算符

区别只是:

  • vector 有更多边界检查与异常安全;
  • 支持迭代器;
  • 扩容策略更智能(可能不是严格2倍)。

🪴 内存扩容原理图示

假设初始容量为 3:

容量 = 3
[10][20][30]

插入第4个元素 → 容量不足 → 自动扩容:

新容量 = 6
[10][20][30][40][_][_]

扩容完成后,所有旧数据被复制到了新内存空间中。


🧠 小结

特点描述
动态分配内存支持自动增长
连续存储支持快速随机访问
高效尾插适合栈、队列、动态数组实现
不适合频繁中间插入/删除因为要移动大量元素

六、应用场景

顺序表特别适合:

  • 数据量相对固定;
  • 不需要频繁插入删除;
  • 需要随机访问的场景。

例如:

  • 学生成绩表;
  • 商品价格列表;
  • 图形界面中控件位置表;
  • 或各种“批量处理”类数据。

七、小结:一句话记住顺序表

顺序表 = 一块连续的内存 + 有序的数据元素
访问快,插删慢,适合存静态数据。


八、延伸阅读

  • 《大话数据结构》第三章:线性表
  • STL 源码剖析:vector 实现原理
  • 数据结构可视化工具:https://visualgo.net

✏️ 总结表

操作平均时间复杂度最佳时间复杂度说明
访问O(1)O(1)随机访问速度最快
插入O(n)O(1)(尾插)需移动元素
删除O(n)O(1)(删尾)需移动元素
查找O(n)O(log n)(若有序)线性或二分查找

💡 结语

顺序表看似简单,却是理解数据结构的起点。
只有真正弄懂它的存储方式操作逻辑
你才能更轻松地理解链表、栈、队列、树乃至图的结构。

从顺序表开始,我们正式踏上数据结构的世界。


http://www.dtcms.com/a/519571.html

相关文章:

  • 企业级安全运营中心(SOC)建设实战:从威胁检测到自动化响应
  • 分布式存储Ceph与OpenStack、RAID的关系
  • “五金件自动化上下料”革新:人形机器人如何重塑柔性制造
  • 多线程六脉神剑第二剑:监视器锁 (Monitor)
  • 飞书多维表格自动化做音视频文案提取,打造素材库工作流,1分钟学会
  • 基于主题聚类的聊天数据压缩与智能检索系统
  • 结构健康自动化监测在云端看数据变化,比人工更及时精准,优缺点分析?
  • 做夹具需要知道的几个网站服装页面设计的网站
  • 分享影视资源的网站怎么做网站字头优化
  • 照明回路配线-批量测量超实用
  • Python 条件判断机制本质
  • 关于spiderdemo第二题的奇思妙想
  • Python处理指定目录下文件分析操作体系化总结
  • k8s部署自动化工具jenkins
  • YOLOv5 目标检测算法详解(一)
  • No040:陪伴的艺术——当DeepSeek学会在时光中温柔在场
  • 6-1〔O҉S҉C҉P҉ ◈ 研记〕❘ 客户端攻击▸侦查客户端指纹
  • 苏州企业网站设计企业phpstudy如何建设网站
  • 仿站网站域名网站建设数据库实验心得
  • 怎么看电脑的主板BIOS型号
  • 广东省高校质量工程建设网站管理登陆网站开发软件
  • 压缩与缓存调优实战指南:从0到1根治性能瓶颈(一)
  • LeetCode 381: O(1) 时间插入、删除和获取随机元素 - 允许重复
  • 一次RedisOOM 排查
  • MongoDB迁移到KES实战全纪录(下):性能优化与实践总结
  • 【Java 开发日记】我们来讲一讲阻塞队列及其应用
  • 免费网站统计代码农业电商平台有哪些
  • 在长沙做网站需要多少钱手机网页禁止访问解除
  • IEEE754是什么?
  • [lc-rs] 树|建桥贪心