并查集路径压缩_[力扣399] 带权并查集

5a02f4e66a430e98a42560867eb9c3d1.png

题目链接

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

算法流程:

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;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值