C++ 数据结构

在 C++ 中,常见的数据结构既包括标准库(STL)提供的容器,也包括你可以自己基于指针/模板等手写的经典结构。下面从 STL 容器到手写结构依次介绍。


1. 原生数组 (C‑style Array)

int a[5];         // 静态分配,长度固定
int b[] = {1,2,3};// 编译期推断长度为 3
  • 优点:内存连续,随机访问 O(1),开销极小。
  • 缺点:大小固定,不可越界检查,不支持动态扩容。

2. 动态数组:std::vector

见上节【C++ vector 容器】。

  • 动态扩容、随机访问 O(1)、尾部插入/删除均摊 O(1)。
  • 推荐使用:大部分需要数组语义且元素数量动态变化的场景。

3. 双端队列:std::deque

#include <deque>
std::deque<int> dq;
dq.push_back(1);
dq.push_front(0);
dq.pop_back();
dq.pop_front();
  • 支持两端插入/删除 O(1),中间插入/删除 O(n),随机访问 O(1)。
  • 底层由一组固定大小的缓冲区组成,适合需要头尾都频繁操作的场景。

4. 链表系列

4.1 单向链表:std::forward_list (C++11)

#include <forward_list>
std::forward_list<int> fl = {1,2,3};
fl.push_front(0);      // O(1)
fl.insert_after(fl.begin(), 42);
fl.remove(2);          // O(n)
  • 只支持单向遍历,插入/删除节点 O(1)(已知前驱时),随机访问 O(n)。
  • 内存开销小于双向链表。

4.2 双向链表: std::list

#include <list>
std::list<int> lst = {1,2,3};
lst.push_back(4);
lst.insert(std::next(lst.begin()), 42);
lst.erase(lst.begin());
  • 双向遍历,插入/删除节点 O(1)(已知位置时),随机访问 O(n)。
  • 适合在容器中间频繁插入/删除的场景。

5. 适配器:栈、队列、优先队列

#include <stack>
#include <queue>

// 栈(LIFO)
std::stack<int> st;
st.push(1);
st.top();         // 1
st.pop();

// 队列(FIFO)
std::queue<int> qu;
qu.push(1);
qu.front();       // 1
qu.pop();

// 优先队列(默认大顶堆)
std::priority_queue<int> pq;
pq.push(3);
pq.push(1);
pq.top();         // 3
  • 底层默认用 deque,也可指定 vector 或 list 作为底层容器。

6. 关联容器:有序集合和映射

6.1 有序集合 std::set / std::multiset

#include <set>
std::set<int> s = {3,1,4};
s.insert(2);         // 保持元素有序
auto it = s.find(3);
  • 底层通常是红黑树(平衡 BST),插入/查找/删除均摊 O(log n)。
  • multiset 允许重复元素。

6.2 有序映射 std::map / std::multimap

#include <map>
std::map<std::string,int> mp;
mp["apple"] = 3;
mp.insert({"banana",2});
  • 键值对按键有序存储,操作均摊 O(log n)。
  • multimap 允许相同键出现多次。

7. 无序关联容器(哈希表)

7.1 std::unordered_set / std::unordered_multiset

#include <unordered_set>
std::unordered_set<int> us = {1,2,3};
us.insert(4);
  • 基于哈希表,平均插入/查找/删除 O(1),最坏 O(n)。
  • unordered_multiset 允许重复。

7.2 std::unordered_map / std::unordered_multimap

#include <unordered_map>
std::unordered_map<std::string,int> ump;
ump["key"] = 42;
  • 哈希映射,平均 O(1),最坏 O(n)。
  • unordered_multimap 允许相同键多次。

8. 其他常用容器

  • std::array<T,N>(C++11):固定大小的数组封装,支持 STL 接口,随机访问 O(1)。
  • std::bitset<N>:位数组,支持按位操作;空间高效,大小在编译期固定。
  • std::string / std::wstring:字符序列容器,参见之前章节。
  • std::tuple / std::pair:简易的异构数据打包结构。

9. 自定义经典数据结构示例

9.1 单链表 (手写)

template<typename T>
struct Node {
    T data;
    Node* next = nullptr;
    Node(const T& v): data(v) {}
};

template<typename T>
class LinkedList {
    Node<T>* head = nullptr;
  public:
    void push_front(const T& v) {
        Node<T>* p = new Node<T>(v);
        p->next = head;
        head = p;
    }
    // … 析构、遍历、删除等
};

9.2 二叉树节点

template<typename T>
struct TreeNode {
    T val;
    TreeNode* left  = nullptr;
    TreeNode* right = nullptr;
    TreeNode(const T& v): val(v) {}
};

9.3 图的邻接表

#include <vector>
#include <list>

int V; // 顶点数
std::vector<std::list<int>> adj(V);
void addEdge(int u, int v) {
    adj[u].push_back(v);
    adj[v].push_back(u); // 若为无向图
}

10. 时间复杂度一览

容器/操作随机访问头部插入尾部插入中间插入查找/删除
vectorO(1)O(n)O(1)*O(n)O(n)
dequeO(1)O(1)O(1)O(n)O(n)
listO(n)O(1)O(1)O(1)O(n)
forward_listO(n)O(1)O(n)O(n)
set/mapO(log n)
unordered_set/mapO(1) avg.

* 尾部插入对于 vector 在容量足够时为 O(1),否则需扩容 O(n)。


小结

  • 优先使用 STL 容器:它们实现成熟、性能可预测,并与算法库无缝配合。
  • 选择合适的容器:根据访问模式(随机访问 vs. 顺序遍历)、插入/删除位置、是否需要排序/哈希等,权衡时间与空间。
  • 必要时自定义:当 STL 容器无法满足特殊需求(比如内存池、紧凑存储、复杂指针关系)时,可手写结构。

熟练掌握以上数据结构,能让你的 C++ 程序既高效又易维护。

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注