【数据结构】动态顺序表实现详解:从原理到接口设计(面试视角)

目录

一、整体认知

二、数据结构设计

面试要点

三、生命周期管理

[1. 初始化](#1. 初始化)

[2. 销毁](#2. 销毁)

四、扩容机制(核心)

深度理解(面试高频)

[1. 为什么用 realloc?](#1. 为什么用 realloc?)

[2. 为什么按 2 倍扩容?](#2. 为什么按 2 倍扩容?)

[3. 为什么用 tmp?](#3. 为什么用 tmp?)

五、插入操作

[1. 尾插(最优操作)](#1. 尾插(最优操作))

[2. 头插](#2. 头插)

[3. 指定位置插入](#3. 指定位置插入)

代码问题(面试加分点)

六、删除操作

[1. 尾删](#1. 尾删)

[2. 头删](#2. 头删)

[3. 指定位置删除](#3. 指定位置删除)

七、时间复杂度总结

八、常见面试问题

[1. 顺序表 vs 链表](#1. 顺序表 vs 链表)

[2. 为什么顺序表要扩容?](#2. 为什么顺序表要扩容?)

[3. 扩容为什么不用 +1?](#3. 扩容为什么不用 +1?)

九、可以继续优化的点(进阶)

十、一句话总结(面试结尾)


一、整体认知

顺序表可以说是高级数组,通过接口设计可对数组实现增删查改等操作。

是重要的数据结构只是,现在让我们一起感受顺序表的魅力吧!

核心特征:

  • 底层是一段连续内存(数组)

  • 支持随机访问(O(1))

  • 插入/删除需要挪动数据(O(n))

  • 通过 realloc 实现动态扩容


二、数据结构设计

cpp 复制代码
typedef int SLDateType;

typedef struct SqeList
{
    SLDateType* arr; // 指向动态数组
    int size;        // 当前有效元素个数
    int capacity;    // 当前容量
} SL;

面试要点

  • sizecapacity

  • size:已使用空间

  • capacity:总可用空间

  • arr == NULL 是初始化态


三、生命周期管理

1. 初始化

cpp 复制代码
void SLInit(SL* ps)
{
    ps->arr = NULL;
    ps->size = ps->capacity = 0;
}

关键点:

  • 不直接分配空间(懒加载思想)

  • 第一次插入时再扩容


2. 销毁

cpp 复制代码
void SLDestroy(SL* ps)
{
    if (ps->arr)
    {
        free(ps->arr);
    }
    ps->arr = NULL;
    ps->size = ps->capacity = 0;
}

面试关注点:

  • 释放堆空间(避免内存泄漏)

  • 指针置空(防止野指针)

  • 状态重置(可复用)


四、扩容机制(核心)

cpp 复制代码
void SLCheckCapacity(SL* ps)
{
    if (ps->capacity == ps->size)
    {
        int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
        SLDateType* tmp = (SLDateType*)realloc(ps->arr, newcapacity * sizeof(SLDateType));
        if (tmp == NULL)
        {
            perror("realloc fail!");
            exit(1);
        }
        ps->arr = tmp;
        ps->capacity = newcapacity;
    }
}

深度理解(面试高频)

1. 为什么用 realloc
  • 自动处理:

    • 原地扩容(可能)

    • 或搬迁到新地址

  • 避免手动 malloc + memcpy

2. 为什么按 2 倍扩容?
  • 保证均摊时间复杂度 O(1)

  • 避免频繁扩容

3. 为什么用 tmp
复制代码
SLDateType* tmp = realloc(...)
  • 防止 realloc 失败导致原指针丢失(内存泄漏)

五、插入操作

1. 尾插(最优操作)

cpp 复制代码
void SLPushBack(SL* ps, SLDateType x)
{
    assert(ps);
    SLCheckCapacity(ps);
    ps->arr[ps->size++] = x;
}

复杂度:

  • 均摊 O(1)

2. 头插

cpp 复制代码
void SLPushHead(SL* ps, SLDateType x)
{
    assert(ps);
    SLCheckCapacity(ps);

    for (int i = ps->size; i > 0; i--)
    {
        ps->arr[i] = ps->arr[i - 1];
    }

    ps->arr[0] = x;
    ps->size++;
}

关键点:

  • 必须从后往前移动(避免覆盖)

  • 时间复杂度 O(n)


3. 指定位置插入

cpp 复制代码
void SLInsert(SL* ps, int pos, SLDateType x)
{
    assert(ps->arr);
    assert(pos <= ps->size - 1 && pos >= 0);

    SLCheckCapacity(ps);

    for (int i = ps->size; i > pos; i--)
    {
        ps->arr[i] = ps->arr[i - 1];
    }

    ps->arr[pos] = x;
    ps->size++;
}

代码问题(面试加分点)

复制代码
assert(pos <= ps->size - 1 && ps >= 0);

❌ 错误:

  • ps >= 0 没意义(指针比较)

  • 插入位置应该允许 pos == size

✅ 正确写法:

复制代码
assert(pos >= 0 && pos <= ps->size);

六、删除操作

1. 尾删

cpp 复制代码
void SLPopBack(SL* ps)
{
    assert(ps);
    assert(ps->size);
    ps->size--;
}

本质:

  • 逻辑删除(不释放空间)

2. 头删

cpp 复制代码
void SLPopHead(SL* ps)
{
    assert(ps);
    assert(ps->size);

    for (int i = 1; i < ps->size; i++)
    {
        ps->arr[i - 1] = ps->arr[i];
    }

    --ps->size;
}

3. 指定位置删除

cpp 复制代码
void SLErase(SL* ps, int pos)
{
    assert(ps->arr);
    assert(pos <= ps->size - 1 && pos >= 0);

    for (int i = pos; i < ps->size - 1; i++)
    {
        ps->arr[i] = ps->arr[i + 1];
    }

    --ps->size;
}

七、时间复杂度总结

操作 时间复杂度
尾插 O(1)(均摊)
头插 O(n)
任意插入 O(n)
尾删 O(1)
头删 O(n)
任意删除 O(n)
随机访问 O(1)

八、常见面试问题

1. 顺序表 vs 链表

维度 顺序表 链表
内存 连续 离散
访问 O(1) O(n)
插入删除 O(n) O(1)(已知位置)
cache友好

2. 为什么顺序表要扩容?

  • 数组大小固定

  • 插入时可能溢出

  • 必须动态扩展


3. 扩容为什么不用 +1?

如果每次 +1:

  • 插入 n 次 → realloc n 次

  • 时间复杂度退化为 O(n²)


九、可以继续优化的点(进阶)

  • 支持缩容(shrink)

  • 增加查找接口

  • 封装为泛型(void*)

  • 增加迭代器设计(C++方向)


十、一句话总结(面试结尾)

顺序表本质是一个基于动态扩容策略的连续内存结构,通过"空间换时间"实现高效访问,但在插入删除上存在整体搬移的性能代价。


如果你下一步想进阶,我建议你做两件事:

  1. 把这个结构改写成 C++ 的 vector(带构造/析构/拷贝)

  2. 对比实现一个 单链表,把两者的适用场景彻底打透

这两步做完,你的数据结构基础就已经达到面试可用级别了。

相关推荐
ZC跨境爬虫5 小时前
跟着 MDN 学JavaScript day_6:JavaScript 中的基础数学——数字与运算符
开发语言·前端·javascript·学习·ecmascript
Lucis__5 小时前
图的高阶算法:从构造最小生成树到求解最短路径问题
数据结构·c++·算法·图论
小小测试开发12 小时前
安装 Python 3.10+
开发语言·人工智能·python
好评12413 小时前
【C++】智能指针全解
c++·智能指针
AAA大运重卡何师傅(专跑国道)13 小时前
【无标题】
开发语言·c#
是阿建吖!14 小时前
【Linux】信号
android·linux·c语言·c++
城北徐宫14 小时前
Linux信号深度解剖:5种产生、3张表、4次切换
linux·c++·学习
sugar__salt14 小时前
从栈队列数据结构到JS原型面向对象全解
前端·javascript·数据结构
liulilittle14 小时前
论 Linux 内核态全局稳态带宽的卡尔曼估计与工程实现
linux·服务器·网络·c++·计算机网络·tcp·通信
XBodhi.14 小时前
Visual Studio C++ 语法错误: 缺少“;”(在“return”的前面)
开发语言·c++·visual studio