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

目录

一、整体认知

二、数据结构设计

面试要点

三、生命周期管理

[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;

面试要点

  • size ≠ capacity

  • 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. 对比实现一个 单链表,把两者的适用场景彻底打透

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

相关推荐
keke.shengfengpolang1 分钟前
2027届统计学专业求职政策研究与产业研究:从岗位要求到材料准备的技术路径
职场和发展
鬼手点金5 分钟前
opencode-crawl4ai使用建议
java·开发语言·人工智能·python·机器学习
谢亮_vipxieliang24 分钟前
Go select 多路复用:从语法到实战的完整指南
开发语言·后端·golang
写后端的胖头鱼8 小时前
一文详解B+树原理(附场景+示例)
数据结构·b树
怕浪猫9 小时前
RAG 面试 6 连问,从原理到优化全部覆盖
python·算法·面试
yyy(十一月限定版)9 小时前
224redfg
c语言
2601_9628857210 小时前
如何用 Python 自动识别股票的支撑位与压力位?
开发语言·python
爱吃奥利奥_wen11 小时前
面向对象三板斧:封装、继承、多态,到底在“装“什么、“承“什么、“变“什么?
开发语言·经验分享·c#
All for pursuit.11 小时前
【位运算-1】338.比特位计数
数据结构·c++·算法·leetcode
我可能是个假开发11 小时前
FTP Unicode 文件名导致 `550` 的问题分析与解决方案
java·开发语言·spring