·
(本文仅用于记录在本学期实验课上涉及的数据结构与考点)
摆烂一个学期了,再不复习真要寄了()
cpp提供的数据结构
(不用记那么多具体实现,cpp万岁)
一、哈希表
1、unordered_map
unordered_map 是 C++ 标准库提供的哈希表实现,用于存储键值对。其定义在 头文件中。
插入元素
1 2
| umap[key] = value; umap.insert({key, value});
|
查找元素(不支持直接通过value来查询key,如果非要只能循环)
1 2
| umap.find(key); umap.count(key);
|
删除元素
1 2
| umap.erase(key); umap.erase(it);
|
访问元素
大小与清空
1 2 3
| umap.size(); umap.empty(); umap.clear();
|
迭代器
1 2 3
| for (auto it = umap.begin(); it != umap.end(); ++it) { cout << it->first << ": " << it->second << endl; }
|
2、unordered_set
unordered_set 是一个哈希集合,它存储唯一的键,没有对应的值。常用于查找、去重等场景。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| unordered_set<int> uset;
uset.insert(1); uset.insert(2); uset.insert(3);
if (uset.find(2) != uset.end()) { cout << "Found 2 in the set" << endl; }
uset.erase(1);
for (auto it = uset.begin(); it != uset.end(); ++it) { cout << *it << endl; }
uset.clear();
|
二、队列
先进先出
定义
入队
出队
访问队列头部元素
1
| int frontElement = q.front();
|
访问队列尾部元素
1
| int backElement = q.back();
|
检查队列是否为空
1 2 3
| if (q.empty()) { std::cout << "队列为空" << std::endl; }
|
获取队列大小
1
| std::cout << "队列大小: " << q.size() << std::endl;
|
三、堆
cpp使用priority_queue表示堆
push(const T& value):向优先队列中插入一个元素。插入时会保持堆的性质。
pop():删除堆顶元素,即优先队列中当前优先级最高的元素。删除操作后会自动重建堆。
top():返回堆顶元素,即优先队列中当前优先级最高的元素,但不删除它。
empty():检查优先队列是否为空,返回布尔值。
size():返回优先队列中的元素个数。
最大堆
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #include <iostream> #include <queue> #include <vector>
int main() { std::priority_queue<int> pq;
pq.push(10); pq.push(20); pq.push(5); pq.push(30);
std::cout << "Top element: " << pq.top() << std::endl;
pq.pop(); std::cout << "Top element after pop: " << pq.top() << std::endl;
std::cout << "Size of priority_queue: " << pq.size() << std::endl;
return 0; }
|
最小堆
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| #include <iostream> #include <queue> #include <vector> #include <functional>
int main() { std::priority_queue<int, std::vector<int>, std::greater<int>> pq;
pq.push(10); pq.push(20); pq.push(5); pq.push(30);
std::cout << "Top element: " << pq.top() << std::endl;
pq.pop(); std::cout << "Top element after pop: " << pq.top() << std::endl;
std::cout << "Size of priority_queue: " << pq.size() << std::endl;
return 0; }
|
自定义比较函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| #include <iostream> #include <queue> #include <vector>
struct Person { std::string name; int age;
bool operator<(const Person& other) const { return age < other.age; } };
int main() { std::priority_queue<Person> pq;
pq.push({"Alice", 30}); pq.push({"Bob", 25}); pq.push({"Charlie", 35});
std::cout << "Top person: " << pq.top().name << ", Age: " << pq.top().age << std::endl;
return 0; }
|
cpp未提供的数据结构
一、并查集
常用于处理一些不交集的合并和查询问题。它的核心操作是合并两个集合(union)和查询两个元素是否属于同一个集合(find)。并查集通常应用于图的连通性问题,比如判断图中是否存在环、最小生成树等问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| #include <iostream> #include <vector> using namespace std;
class DisjointSet { private: vector<int> parent; vector<int> rank;
public: DisjointSet(int n) { parent.resize(n); rank.resize(n, 0); for (int i = 0; i < n; i++) { parent[i] = i; } }
int find(int x) { if (parent[x] != x) { parent[x] = find(parent[x]); } return parent[x]; }
void unionSets(int x, int y) { int rootX = find(x); int rootY = find(y); if (rootX != rootY) { if (rank[rootX] > rank[rootY]) { parent[rootY] = rootX; } else if (rank[rootX] < rank[rootY]) { parent[rootX] = rootY; } else { parent[rootY] = rootX; rank[rootX]++; } } }
bool connected(int x, int y) { return find(x) == find(y); } };
int main() { int n = 10; DisjointSet ds(n);
ds.unionSets(1, 2); ds.unionSets(2, 3); ds.unionSets(4, 5); ds.unionSets(6, 7);
cout << "1 and 3 connected: " << ds.connected(1, 3) << endl; cout << "1 and 4 connected: " << ds.connected(1, 4) << endl;
return 0; }
|
二、Trie(字典树)
主要用于处理字符串集合,特别适合:
- 插入一个单词:
insert
- 判断某单词是否存在:
search
- 判断是否存在某个前缀:
startsWith
- 统计某个单词出现次数 / 某个前缀出现次数
- 删除单词:
erase / remove
1、Trie 类的基本实现(insert / search / startsWith)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
| class Trie { private: TrieNode* root;
public: Trie() { root = new TrieNode(); }
void insert(const std::string& word) { TrieNode* node = root; for (char ch : word) { int idx = ch - 'a'; if (node->children[idx] == nullptr) { node->children[idx] = new TrieNode(); } node = node->children[idx]; node->prefixCount++; } node->isEnd = true; node->wordCount++; }
bool search(const std::string& word) const { TrieNode* node = root; for (char ch : word) { int idx = ch - 'a'; if (node->children[idx] == nullptr) { return false; } node = node->children[idx]; } return node->isEnd; }
bool startsWith(const std::string& prefix) const { TrieNode* node = root; for (char ch : prefix) { int idx = ch - 'a'; if (node->children[idx] == nullptr) { return false; } node = node->children[idx]; } return true; }
int countWordsEqualTo(const std::string& word) const { TrieNode* node = root; for (char ch : word) { int idx = ch - 'a'; if (node->children[idx] == nullptr) return 0; node = node->children[idx]; } return node->wordCount; }
int countWordsStartingWith(const std::string& prefix) const { TrieNode* node = root; for (char ch : prefix) { int idx = ch - 'a'; if (node->children[idx] == nullptr) return 0; node = node->children[idx]; } return node->prefixCount; }
void erase(const std::string& word) { if (!search(word)) return; TrieNode* node = root; for (char ch : word) { int idx = ch - 'a'; node = node->children[idx]; node->prefixCount--; } node->wordCount--; if (node->wordCount == 0) { node->isEnd = false; } } }; int main() { Trie trie;
trie.insert("apple"); trie.insert("app"); trie.insert("apply"); trie.insert("apple");
bool f1 = trie.search("apple"); bool f2 = trie.search("app"); bool f3 = trie.search("appl");
bool p1 = trie.startsWith("app"); bool p2 = trie.startsWith("apx");
int c1 = trie.countWordsEqualTo("apple"); int c2 = trie.countWordsEqualTo("app");
int cp = trie.countWordsStartingWith("app");
trie.erase("apple"); bool f4 = trie.search("apple"); int c3 = trie.countWordsEqualTo("apple");
return 0; }
|
你可以通过这些调用大致理解每个接口的用途。
2、删除单词的更完整写法(带回收节点)
如果想让 Trie 在删除后,把“完全没有用的节点”释放掉,可以写成递归版 erase:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| class Trie2 { private: TrieNode* root;
bool eraseHelper(TrieNode* node, const std::string& word, int pos) { if (pos == (int)word.size()) { node->wordCount--; if (node->wordCount == 0) node->isEnd = false; } else { int idx = word[pos] - 'a'; TrieNode* child = node->children[idx]; if (child == nullptr) return false;
bool canDeleteChild = eraseHelper(child, word, pos + 1); if (canDeleteChild) { delete child; node->children[idx] = nullptr; } }
if (pos < (int)word.size()) { node->prefixCount--; }
if (node == root) return false; if (node->isEnd) return false; for (int i = 0; i < 26; ++i) { if (node->children[i] != nullptr) return false; } return true; }
public: Trie2() { root = new TrieNode(); }
void insert(const std::string& word) { TrieNode* node = root; for (char ch : word) { int idx = ch - 'a'; if (!node->children[idx]) node->children[idx] = new TrieNode(); node = node->children[idx]; node->prefixCount++; } node->isEnd = true; node->wordCount++; }
bool search(const std::string& word) const { TrieNode* node = root; for (char ch : word) { int idx = ch - 'a'; if (!node->children[idx]) return false; node = node->children[idx]; } return node->isEnd; }
void erase(const std::string& word) { if (!search(word)) return; eraseHelper(root, word, 0); } };
|
3、使用 unordered_map 的节点(字符集大时用)
如果要支持不限字符(如大小写字母、数字、甚至 Unicode),可以把 children[26] 换成 unordered_map<char, TrieNode*>:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| #include <unordered_map>
struct TrieNodeMap { std::unordered_map<char, TrieNodeMap*> children; bool isEnd; int wordCount; int prefixCount;
TrieNodeMap() : isEnd(false), wordCount(0), prefixCount(0) {} };
class TrieMap { private: TrieNodeMap* root;
public: TrieMap() { root = new TrieNodeMap(); }
void insert(const std::string& word) { TrieNodeMap* node = root; for (char ch : word) { if (!node->children.count(ch)) { node->children[ch] = new TrieNodeMap(); } node = node->children[ch]; node->prefixCount++; } node->isEnd = true; node->wordCount++; }
bool search(const std::string& word) const { TrieNodeMap* node = root; for (char ch : word) { auto it = node->children.find(ch); if (it == node->children.end()) return false; node = it->second; } return node->isEnd; }
bool startsWith(const std::string& prefix) const { TrieNodeMap* node = root; for (char ch : prefix) { auto it = node->children.find(ch); if (it == node->children.end()) return false; node = it->second; } return true; } };
|
函数调用方式和前面的 Trie 一样,只是内部实现不同。
- 优点:字符集无限制。
- 缺点:常数更大,速度相对慢一些。
4、支持通配字符的trie
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| #include <bits/stdc++.h> using namespace std;
struct TrieNode { TrieNode* children[26]; bool isEnd; TrieNode() : isEnd(false) { for (int i = 0; i < 26; ++i) children[i] = nullptr; } };
class WordDictionary { private: TrieNode* root;
bool dfs(const string& word, int pos, TrieNode* node) const { if (node == nullptr) return false; if (pos == (int)word.size()) { return node->isEnd; }
char ch = word[pos]; if (ch == '.') { for (int i = 0; i < 26; ++i) { if (node->children[i] != nullptr) { if (dfs(word, pos + 1, node->children[i])) { return true; } } } return false; } else { int idx = ch - 'a'; return dfs(word, pos + 1, node->children[idx]); } }
public: WordDictionary() { root = new TrieNode(); }
void addWord(const string& word) { TrieNode* node = root; for (char ch : word) { int idx = ch - 'a'; if (node->children[idx] == nullptr) { node->children[idx] = new TrieNode(); } node = node->children[idx]; } node->isEnd = true; }
bool search(const string& word) const { return dfs(word, 0, root); } };
|
5、Trie 和普通数据结构的对比(什么时候用它)
- 和
unordered_set<string> 比:
- 相同点:都可以存一堆字符串、支持插入/查找。
- Trie 优势:前缀查询、统计前缀数量、自动补全等操作更方便、复杂度更好(按字符串长度算)。
- 和
map<string, int> 比:
map 基于平衡树,字符串比较时最坏要 O(L)。
- Trie 的查询复杂度也是 O(L),但在前缀问题上更自然。
时间复杂度(假设字符串长度为 L):
insert:O(L)
search:O(L)
startsWith:O(L)
- 不依赖于当前有多少个单词 n,主要看字符串长度。