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

目录

一、整体认知

二、数据结构设计

面试要点

三、生命周期管理

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

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

相关推荐
狐璃同学2 小时前
数据结构(1)三要素
数据结构·算法
小明同学012 小时前
[C++入门]万字长文梳理 C++11 核心特性
c++
郝学胜-神的一滴2 小时前
Linux 高并发基石:epoll 核心原理 + LT/ET 触发模式深度剖析
linux·运维·服务器·开发语言·c++·网络协议
Hello!!!!!!2 小时前
C++基础(六)——数组与字符串
c++·算法
A_aspectJ2 小时前
Java开发的学习优势:稳定基石与多元可能并存的技术赛道
java·开发语言
qq_283720052 小时前
Python 模块精讲:collections —— 高级数据结构深度解析(defaultdict、Counter、deque)
java·开发语言
wicb91wJ62 小时前
分库分表实战:ShardingSphere落地
数据结构·postman·emacs
wjs20242 小时前
Chart.js 饼图指南
开发语言
YSF2017_32 小时前
C语言-12-静态库制作
c语言·开发语言