![5a02f4e66a430e98a42560867eb9c3d1.png](https://i-blog.csdnimg.cn/blog_migrate/1b1353687a7b9fd0f2f8b8922791e0be.jpeg)
题目链接
399. 除法求值
题目描述
给出方程式 A / B = k, 其中 A 和 B 均为代表字符串的变量, k 是一个浮点型数字。根据已知方程式求解问题,并返回计算结果。如果结果不存在,则返回 -1.0。
输入总是有效的。你可以假设除法运算中不会出现除数为0的情况,且不存在任何矛盾的结果。
样例
给定 a / b = 2.0, b / c = 3.0
问题: a / c = ?, b / a = ?, a / e = ?, a / a = ?, x / x = ?
返回 [6.0, 0.5, -1.0, 1.0, -1.0 ]
输入为: vector<pair<string, string>> equations, vector<double>& values, vector<pair<string, string>> queries(方程式,方程式结果,问题方程式), 其中 equations.size() == values.size(),即方程式的长度与方程式结果长度相等(程式与结果一一对应),并且结果值均为正数。以上为方程式的描述。 返回vector<double>类型。
基于上述例子,输入如下:
equations(方程式) = [ ["a", "b"], ["b", "c"] ],
values(方程式结果) = [2.0, 3.0],
queries(问题方程式) = [ ["a", "c"], ["b", "a"], ["a", "e"], ["a", "a"], ["x", "x"] ].
算法: 带权并查集
带权并查集的权主要有两种:
一种是集合级的权,在合并过程中会对集合级信息带来变化,例如集合元素个数,连通分量中的最值,这些信息可以放在代表元(树根)维护。并查集的查找过程对集合级信息没有影响,因为路径压缩影响的只是非根节点。
另一种是边级的权,与并查集的区别就是在并查集的树的边的意义不在只是相连,而又多了记录值(权) d[x]。这个权代表节点到其父节点这条有向边的某种指标,例如距离,d[x] := x -> father[x] 的距离。且这种指标要可以按某种规则传递,例如距离可以按加法传递。
本题的边权是表达式的值,按乘法传递。
在查找和合并过程中都会对边级的权带来变化。
查找:
int find(int x)
如果当前节点不是根节点,先递归地找到当前节点在路径压缩后的父节点即根节点,记录下来。然后在回溯阶段,将当前节点连接到路径压缩后的父节点(根)之前,先用旧的父节点更新当前节点的权值,这里更新好的权值在继续回溯后还要被上一个节点使用。
// 种类权值信息是乘法传递的,所以用 *=
_weight[x] *= _weight[_father[x]];
因为是从根开始回溯的,所有回溯完成后,所有 x -> 树根的路径上的节点 i ,它们记录的权值 _weight[i] 都改成了 x 到树根的距离。
int _find(int x)
{
if(_father[x] == x)
return x;
int new_fa = _find(_father[x]);; // 路径压缩后的父节点
_weight[x] *= _weight[_father[x]];
_father[x] = new_fa;
return _father[x];
}
合并:
void merge(int x, int y, int w)
先求 x和 y 的树根
int rootx = _find(x);
int rooty = _find(y);
x 到 rootx;y 到 rooty 之间路径上的点的权值在 _find() 里已经更新好了。之后要看 rootx 和 rooty 的树高,来决定是把 rootx 连接到 rooty 还是把 rooty 连接到 rootx。连接之后,要记录 rootx 或者 rooty 的信息。
比如将 rootx 连接到 rooty,那么需要更新 rootx 的权为 rootx 到 rooty 的距离。
_father[rootx] = rooty;
_weight[rootx] = _weight[y] * w / _weight[x];
更新的时候利用到了 x -> y 的距离 w。x, rootx, y, rooty 构成一个四边形,从 x 走到对角 rooty 的两条路长度相等,可以推出 rootx -> rooty 的距离
![5ee9cd3792fab2d90c53e2eb4cd8ace9.png](https://i-blog.csdnimg.cn/blog_migrate/e71b62875b393c597213b98a88bcf30e.jpeg)
算法流程:
1. 先遍历一遍已知等式,用哈希表记录已知等式中变量字符串对应的 id
2. 再遍历一遍已知等式,建立带权并查集。
3. 枚举每个询问,如果有变量不在并查集中(哈希表中查不到当前的变量字符串);或者两个变量不在一个连通分量中(并查集的same函数返回false),返回-1。
4. 否则,返回两个变量到根节点的权值,计算答案
代码(c++)
普通并查集的模板中,增加 weight[x], 维护 x -> father[x] 这条边的权值。
class WeightUnionFindSet
{
public:
WeightUnionFindSet(int N)
{
_father = vector<int>(N, -1);
_rank = vector<int>(N, 1);
_weight = vector<double>(N, 1.0); // a / a = 1.0
for(int i = 0; i < N; ++i)
_father[i] = i;
}
bool same(int x, int y)
{
return _find(x) == _find(y);
}
double get_weight(int x)
{
_find(x);
return _weight[x];
}
void merge(int x, int y, double w)
{
int rootx = _find(x);
int rooty = _find(y);
if(rootx == rooty) return;
if(_rank[rootx] < _rank[rooty])
{
_father[rootx] = rooty;
_weight[rootx] = _weight[y] * w / _weight[x];
}
else
{
_father[rooty] = rootx;
_weight[rooty] = _weight[x] * (1 / w) / _weight[y];
}
if(_rank[rootx] == _rank[rooty])
++_rank[rootx];
}
private:
vector<int> _father;
vector<int> _rank;
vector<double> _weight;
int _find(int x)
{
if(_father[x] == x)
return x;
int new_fa = _find(_father[x]);; // 路径压缩后的父节点
_weight[x] *= _weight[_father[x]];
_father[x] = new_fa;
return _father[x];
}
};
class Solution {
public:
vector<double> calcEquation(vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries) {
if(equations.empty()) return vector<double>((int)queries.size(), -1.0);
int n = equations.size();
unordered_map<string, int> item_id;
int N = 0;
for(const vector<string> &equation: equations)
{
for(const string &item: equation)
{
auto it = item_id.find(item);
if(it == item_id.end())
item_id[item] = N++;
}
}
WeightUnionFindSet weightunionfindset(N);
for(int i = 0; i < n; ++i)
{
int x = item_id[equations[i][0]];
int y = item_id[equations[i][1]];
double w = values[i];
if(!weightunionfindset.same(x, y))
weightunionfindset.merge(x, y, w);
}
vector<double> result;
for(const vector<string> &query: queries)
{
if(item_id.find(query[0]) == item_id.end() || item_id.find(query[1]) == item_id.end())
{
result.push_back(-1.0);
continue;
}
int x = item_id[query[0]], y = item_id[query[1]];
if(!weightunionfindset.same(x, y))
{
result.push_back(-1.0);
continue;
}
double tmp = weightunionfindset.get_weight(x) / weightunionfindset.get_weight(y);
result.push_back(tmp);
}
return result;
}
};