单源最短路径
题目大意
给定一个有向图,包含\(n\)个点、\(m\)条有向边以及一个出发点\(s\)。图中每条边从点\(u\)指向点\(v\),长度为\(w\) 。要求计算并输出从出发点\(s\)到图中其余\(n
- 1\)个点的最短路径长度,若无法从\(s\)到达某个点,则输出\(2^{31}-1\)(即 INT_MAX
)。
解法描述
Dijkstra 算法
算法思想
采用贪心策略。从源点\(s\)出发,维护一个距离源点最近的顶点集合。每次从集合外选取距离源点最近的顶点加入集合,利用该顶点更新其邻接顶点到源点的距离。由于边权非负,一旦某个顶点加入集合,其到源点的最短距离就确定下来,后续不会再改变。
实现方式
利用邻接表来存储图结构,通过
add函数添加边。定义dist数组记录源点到各顶点的最短距离,初始时将所有距离设为INT_MAX,dist[s]设为 0;st数组用于标记顶点是否已确定最短路径。借助优先队列(小根堆)heap,按距离源点的距离从小到大存储顶点。在dijkstra函数中,持续从优先队列取出距离最小的顶点u,若u已确定最短路径则跳过。遍历u的邻接顶点v,若通过u到v的距离小于当前dist[v],则更新dist[v],并将v加入优先队列。最后输出dist数组中的结果。复杂度分析
- 时间复杂度:使用邻接表和二叉堆实现时,每次从优先队列取出顶点的时间复杂度为\(O(\log n)\),总共处理\(n\)个顶点,遍历边的时间复杂度为\(O(m)\),所以时间复杂度为\(O((n + m)\log n)\)。若使用斐波那契堆,可优化到\(O(n\log n + m)\)。
- 空间复杂度:邻接表存储图需要\(O(n +
m)\)的空间;优先队列(二叉堆)存储顶点需要\(O(n)\)空间;
dist和st数组各需\(O(n)\)空间。因此,总空间复杂度为\(O(n + m)\)。
1 | //堆优化dijkstra,邻接表存 |
SPFA 算法
算法思想
是 Bellman - Ford 算法的队列优化版本。借助队列优化松弛操作,不断取出队首顶点,更新其邻接顶点的距离。若邻接顶点距离更新且未在队列中,则将其加入队列,直至队列为空。理论上边权可为负,但本题边权非负时也可使用。
实现方式
同样用邻接表存储图,
addedge函数用于建图。dis数组记录源点到各顶点的距离,初始化为极大值,dis[s]设为 0;vis数组标记顶点是否在队列中。在spfa函数里,将源点入队,在队列不为空时,取出队首顶点u,标记其出队,遍历u的邻接顶点v,若通过u到v的距离小于当前dis[v],则更新dis[v],若v不在队列中则将其入队并标记。最后输出dis数组中的结果。 3. 复杂度分析:复杂度分析
- 时间复杂度:最坏情况下,每个顶点入队\(n\)次,每次入队遍历其出边,时间复杂度为\(O(nm)\)。在随机图中,平均时间复杂度接近\(O(km)\)(\(k\)为常数且\(k \lt n\) )。本题边权为正,其时间复杂度比 Dijkstra 算法高。
- 空间复杂度:邻接表存储图需要\(O(n + m)\)空间;队列存储顶点最多需要\(O(n)\)空间;
dis和vis数组各需要\(O(n)\)空间。所以总空间复杂度为\(O(n + m)\)。
1 |
|
最小生成树
题目大意
给定一个图,图中包含顶点集合和边集合,每条边都有一个权重代表连接两个顶点的成本或距离。要求使用合适的算法找到该图的最小生成树,并计算最小生成树的总权重。
最小生成树(Minimum Spanning Tree,MST)是在一个连通无向图中,由所有顶点和连接这些顶点的部分边构成的一棵树,这棵树满足以下两个关键特性:
- 包含所有顶点:最小生成树包含图中的全部顶点。以城市交通图为例,每个城市是一个顶点,最小生成树必须涵盖所有城市,确保每个城市都能通过树中的边与其他城市相连通。
- 边权总和最小:在所有能够连接图中所有顶点的树中,最小生成树的边的权重之和是最小的。继续以城市交通图来说,边的权重可代表城市间建设道路的成本,最小生成树就表示用最低的总成本在所有城市间建立道路连接的方案,能避免不必要的高成本连接,达到资源的最优利用。
解法描述
Kruskal
算法思想
采用贪心策略,先将图中所有边按权重从小到大排序。然后依次选取最短的边,若选取的边不会使已选边形成环,则将其加入最小生成树中,直到所有顶点都被包含在生成树中。
实现方式
首先读取图的顶点数 \(n\) 和边的权重信息,将有效边(权重不为-1)存储到数组
e中。使用并查集数据结构(find函数和数组p)来检测加入某条边是否会产生环。对边数组e进行排序后,遍历每条边,判断边的两个端点是否在不同的集合(即不会形成环),若是,则合并这两个集合,并将边的权重累加到结果ans中。复杂度分析
- 时间复杂度:主要取决于边的排序,时间复杂度为 \(O(E log E)\),其中 \(E\) 是边的数量。
- 空间复杂度:使用邻接表存储图,空间复杂度是 \(O(V + E)\),\(V\) 是顶点的数量。
1 |
|
Prim
算法思想
从任意一个顶点开始,逐步扩展生成树。每次选择连接已在生成树中的顶点和不在生成树中的顶点的最短边,直到所有顶点都被包含在生成树中。
实现方式
初始化距离数组
dist为无穷大,标记数组st为未访问。通过循环n次,每次在未访问的顶点中选择距离当前生成树最近的顶点t,将其距离累加到结果ans中,然后用该顶点更新其他顶点到生成树的距离,标记该顶点已访问。复杂度分析
- 时间复杂度:时间复杂度为 \(O(E + V log V)\),其中 \(V\) 是顶点的数量,这通常发生在使用优先队列(如二叉堆)选择最小边时。
- 空间复杂度:使用邻接矩阵存储图,空间复杂度是 \(O(V^2)\)。
1 |
|
哈夫曼树
题目大意
题目给定 26 个小写字母 a - z 的非归一化概率以及一个由 26
个小写字母组成的序列。需要根据这些字母的概率构建霍夫曼树,再使用该霍夫曼树对给定的字母序列进行压缩,最后计算压缩后该序列占用的总比特数。
解法描述
霍夫曼编码
算法思想
霍夫曼编码是一种用于数据压缩的贪心算法。其核心思想是根据字符出现的频率构建一棵二叉树,频率较低的字符离根节点较远,频率较高的字符离根节点较近。在构建树的过程中,每次选取频率最小的两个节点合并成一个新节点,新节点的频率为这两个节点频率之和,重复这个过程直到只剩下一个根节点。这样,每个字符的编码长度就由其在霍夫曼树中的深度决定,出现频率高的字符编码短,出现频率低的字符编码长,从而实现数据的压缩。
实现方式
- 输入处理:使用
for循环读取 26 个小写字母a - z的非归一化概率,并将每个概率及其对应的索引(idx从 0 开始递增)以pair<int, int>的形式存入优先队列huf中,优先队列按概率从小到大排序。 - 构建霍夫曼树:当优先队列
huf中的元素数量大于 1 时,取出队首的两个元素t1和t2,将它们合并成一个新节点,新节点的概率为t1.first + t2.first,索引为idx并递增。同时记录新节点的两个子节点为t1.second和t2.second到son数组中,再将新节点重新插入优先队列huf中。 - 计算字符编码长度:定义深度优先搜索函数
dfs,对于每个非叶子节点(索引大于等于 26),递归地对其左右子节点调用dfs函数;对于叶子节点(索引小于 26),对应字符的编码长度计数器cnt[p]加 1。通过遍历所有节点调用dfs函数,计算出每个字母的编码长度。
- 输入处理:使用
复杂度分析
时间复杂度:
构建优先队列的时间复杂度为 \(O(n log n)\),其中 \(n = 26\) 是字母的数量。构建霍夫曼树的过程中,每次从优先队列中取出两个元素并插入一个新元素,共进行 \(n - 1\) 次操作,每次操作的时间复杂度为 \(O(log n)\),所以构建树的总时间复杂度为 \(O(n log n)\)。计算字符编码长度的深度优先搜索过程需要遍历所有节点,时间复杂度为 \(O(n)\)。计算总比特数需要遍历输入的字符串,设字符串长度为 \(m\),时间复杂度为 \(O(m)\)。综合起来,总的时间复杂度为 \(O(n log n + m)\)。
空间复杂度:
优先队列
huf最多存储 \(n\) 个元素,空间复杂度为 \(O(n)\)。son数组用于存储霍夫曼树的节点关系,空间复杂度为 \(O(n)\)。cnt数组用于存储每个字母的编码长度,空间复杂度为 \(O(n)\)。因此,总的空间复杂度为 \(O(n)\)。
1 |
|