栈(Stack)
栈是一种线性数据结构,遵循后进先出(LIFO)原则。数据只能从栈顶插入(压栈)和删除(弹栈)。
优点
- 操作简单高效,压栈和弹栈的时间复杂度均为 O(1)。
- 适合处理具有嵌套性质的问题,如函数调用、括号匹配、表达式求值等。
缺点
- 只能从一端操作数据,灵活性较差。
- 无法直接访问栈中间的元素,需要弹出栈顶元素才能访问底层数据。
分析
栈的空间复杂度通常为 O(n),n 为栈中元素数量。递归调用可能因栈深度过大导致栈溢出,需谨慎使用。
队列(Queue)
队列是一种线性数据结构,遵循先进先出(FIFO)原则。数据从队尾插入(入队),从队头删除(出队)。
优点
- 操作高效,入队和出队的时间复杂度均为 O(1)。
- 适合处理需要顺序执行的场景,如任务调度、缓冲区管理等。
缺点
- 与栈类似,无法直接访问中间元素。
- 普通队列在空间利用率上可能存在浪费(如数组实现的循环队列需预留空间)。
分析
队列的实现方式包括数组队列和链表队列。循环队列可以优化数组队列的空间利用率,避免频繁扩容。
数组(Array)
数组是一种连续存储的线性数据结构,通过索引直接访问元素。
优点
- 随机访问效率高,时间复杂度为 O(1)。
- 内存连续,缓存友好,访问速度快。
缺点
- 大小固定,动态扩容成本高(需拷贝数据)。
- 插入和删除操作效率低,平均时间复杂度为 O(n)。
分析
数组适合查询频繁、增删较少的场景。在需要动态调整大小时,可考虑使用动态数组(如 C++ 的 vector
或 Java 的 ArrayList
)。
链表(LinkedList)
链表是一种非连续存储的线性数据结构,通过指针连接节点。
链表分类
单链表(Singly Linked List)
- 每个节点包含数据和指向下一个节点的指针。
- 插入和删除操作的时间复杂度为 O(1),但需先找到操作位置(查找 O(n))。
双链表(Doubly Linked List)
- 每个节点包含数据及两个指针,分别指向前驱和后继节点。
- 支持双向遍历,但占用更多内存空间。
循环单链表(Circular Singly Linked List)
- 尾节点指向头节点,形成环状结构。
- 适合环形缓冲区等场景。
循环双链表(Circular Doubly Linked List)
- 尾节点指向头节点,且每个节点有前驱和后继指针。
- 双向遍历且支持环形操作。
优点
- 动态分配内存,无需预先指定大小。
- 插入和删除操作效率高(尤其双链表)。
缺点
- 随机访问效率低,时间复杂度为 O(n)。
- 指针占用额外内存空间,缓存不友好。
分析
链表适合频繁增删的场景,但查询性能较差。双链表在需要双向操作时更灵活,但空间开销更大。
综合比较
数据结构 | 访问时间 | 插入/删除时间 | 适用场景 |
---|---|---|---|
栈 | O(n) | O(1) | LIFO 场景 |
队列 | O(n) | O(1) | FIFO 场景 |
数组 | O(1) | O(n) | 查询为主 |
链表 | O(n) | O(1) | 增删为主 |
根据具体需求选择数据结构:若需高效查询,优先数组;若需频繁增删,链表更优;栈和队列适合特定逻辑场景。