散列表 | 哈希表(Hash Table)
通过散列函数将关键字(key)映射到数组中的特定位置,以便来快速查找数据。
哈希表就是c++内置散列表
散列 函数:将输入的关键字转换为整数,散列函数的质量决定了散列表的性能,好的散列函数应能均匀地分布关键字,避免冲突。
填充 | 负载因子:元素总数 / 位置总数,一旦填装因子大于0.7,就应该调整散列表的长度。
散列冲突:将无限的关键字空间映射到有限的数组索引上,不同的关键字可能映射到相同的索引位置,
冲突解决方法:
- 链地址法:当发生冲突时,将新元素插入到相应索引位置的链表中。
- 开放地址法:当发生冲突时,探测下一个空闲的位置。常见的探测方式有线性探测、二次探测和双重散列
优点:查找、插入和删除操作的平均时间复杂度很低。平均时间复杂度都是 O(1)
缺点:最坏情况下性能较差,可能退化到 O(n),尤其是在发生大量冲突时。高度依赖于良好的散列函数和较低的填充因子
空间:复杂度为 O(n)
适用场景:应用于需要快速查找的场景
图
图是一种数据结构,由节点和边组成,直接相连的节点,称为邻居
图可以分为以下几种类型:
-
无向图 (Undirected Graph):边没有方向,边连接的两个顶点之间是对称的关系。
-
有向图 (Directed Graph, Digraph):边有方向,表示从一个顶点指向另一个顶点的关系。
-
加权图 (Weighted Graph):边有权重,表示边之间的连接强度或距离等数值。
-
无权图 (Unweighted Graph):边没有权重,只表示顶点之间的关系。
-
连通图 (Connected Graph):任意两个顶点之间都有路径相连的无向图。
-
非连通图 (Disconnected Graph):存在至少一对顶点之间没有路径的无向图。
-
树 (Tree):一种特殊的连通无向图,没有环路,每两个顶点之间都有唯一的路径。
-
有向无环图 (Directed Acyclic Graph, DAG):一种没有环的有向图,常用于表示任务调度等依赖关系。
队列(Queue)
遵循先进先出(FIFO, First In First Out)的原则。
队列的实现:
- 数组实现:用固定大小的数组来实现队列,可能会遇到数组空间的浪费或溢出的问题。
- 链表实现:用链表来实现队列,可以动态分配内存,避免空间浪费。
队列的类型:
- 普通队列(Simple Queue):最基本的队列形式,支持入队和出队操作。
- 循环队列(Circular Queue):队列首尾相连,避免数组实现中的空间浪费问题。
- 双端队列(Deque, Double-ended Queue):允许在队列的两端进行入队和出队操作。
- 优先队列(Priority Queue):队列中的元素具有优先级,出队时优先级高的元素先被处理。
适用场景:按顺序处理任务的场景,如任务调度、广度优先搜索等。
BFS广度优先搜索(查找算法)
是一种图或树的遍历算法,它按照层次逐层访问节点,从起始节点开始,先访问与其直接相连的节点,再访问这些节点的邻居,依此类推,直到遍历完所有节点或找到目标节点。
广度优先搜索的应用:最短路径查找,图的连通性检查,拓扑排序,
运行时间:O(V + E)
空间复杂度为 O(V)
实例:首先将添加一个节点到队列,在将所有邻居添加到队列,每次处理队首的逻辑,知道队列为空,提前找到结果
void BFS(int start) {
vector<bool> visited(V, false); // 创建一个访问标记数组,初始化为未访问
queue<int> queue; // 创建一个队列
// 将起始节点标记为已访问并入队
visited[start] = true;
queue.push(start);
while (!queue.empty()) {
// 从队列中取出一个节点
int v = queue.front();
queue.pop();
// 访问该节点的所有邻居
for (auto i : adj[v]) {
if (!visited[i]) {
visited[i] = true; // 标记邻居节点为已访问
queue.push(i); // 将邻居节点入队
}
}
}
}