面试题 _并查集

本文深入探讨了并查集这一数据结构,详细介绍了其基础知识、基本结构和应用场景,包括地铁迷打卡活动的最小交通花费计算问题和婴儿名字频率统计问题。通过实际案例,展示了并查集在解决不交集合并及查询问题上的高效性和灵活性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

基础知识

  并查集是一种用来管理元素分组情况的数据结构,用于处理一些不交集(Disjoint Sets)的合并及查询问题。有一个联合-查找算法(Union-find Algorithm)定义了两个用于此数据结构的操作:

  • Find:确定元素属于哪一个子集。它可以被用来确定两个元素是否属于同一子集。
  • Union:将两个子集合并成同一个集合。

为了更加精确的定义这些方法,需要定义如何表示集合。一种常用的策略是为每个集合选定一个固定的元素,称为代表,以表示整个集合。接着,Find(x) 返回 x 所属集合的代表,而 Union 使用两个集合的代表作为参数。

基本结构

面试题 1

题目:
  地铁迷在某个城市组织了地铁打卡活动。活动要求前往该城市中的所有地铁站进行打卡。打卡可以在站外或者站内进行。地铁的计价规则如下:只要不出站,就不计费;出站时,只计算进站和出站站点的距离。如在同一个站点进出站,按照最低票价 a 元计算。假设地铁票不会超时。大部分站点都是通过地铁线连通的,而且地铁站的连通是双向的(若 A,B 连通,则 B,A连通),且具有传递性的(若 A,B 连通,且 B,C 连通,则 A,C连通)。但并不是所有的地铁站都相互连通,在不能通过坐地铁达到的两个地点间,交通的花费则提高到 b 元。地铁迷从酒店起点出发,再回到酒店。假设从酒店到达任意地铁站的交通花费为 b 元。请计算地铁迷完成打卡最小交通花费。
输入描述:
每组输入包括m + 1行。
第1行包含4个整数n,m,a,b,其中n表示地铁站点数,m表示连通的地铁站点对数,a代表地铁最低票价,b代表非地铁方式票价,其中0 < a < b。
第2到m + 1行,每行2个整数Hi,Ti代表站点Hi和Ti站点是连通的(0 <= i< m)。

输出描述:
输出只有一行,包含一个整数,表示打卡的最小交通花费。
在这里插入图片描述
代码:

#include<iostream>
#include<vector>
using namespace std;
vector<int> f;
 
int find(int x) {   //查找父类操作
    return x == f[x] ? x : f[x] = find(f[x]);
}
 
void union1(int u,int v) {    //合并操作
    f[find(v)] = find(u);
}
 
int main() {
    int n,m,a,b;
    cin >> n >> m >> a >> b;
    vector<int> row(2);
 
    for(int i = 0;i < n;i++)
        f.push_back(i);
    for(int i = 0;i < m;i++) {
        cin >> row[0] >> row[1];        
        union1(row[0],row[1]);
    }
      
    int num = 0;
    for(int i = 0;i < n;i++)
       if(f[i] == i)
           num++;
    int res = 2 * b + num * a + (num - 1) * b;
    cout << res << endl;
    return 0;
}

面试题 _婴儿名字

  每年,政府都会公布一万个最常见的婴儿名字和它们出现的频率,也就是同名婴儿的数量。有些名字有多种拼法,例如,John 和 Jon 本质上是相同的名字,但被当成了两个名字公布出来。给定两个列表,一个是名字及对应的频率,另一个是本质相同的名字对。设计一个算法打印出每个真实名字的实际频率。注意,如果 John 和 Jon 是相同的,并且 Jon 和 Johnny 相同,则 John 与 Johnny 也相同,即它们有传递和对称性。
注:在结果列表中,选择字典序最小的名字作为真实名字。
在这里插入图片描述代码:

//并查集
class Solution {
public:
    vector<int>fa;
    map<string,int>m1;//以str代表序列
    map<int,int>m2;//以str表示个数
    map<int,string>m3;//以序列寻找idx
    int getfa(int x){
        if(x!=fa[x]){                           //x的父亲不是它自身
            fa[x]=getfa(fa[x]);                 //找到x父亲的父亲,并赋值给x的父亲
        }
        return fa[x];                           //返回x的父亲
    }
    void unionXY(string s1,string s2){
        int x=getfa(m1[s1]);                    //获得s1的父亲结点  索引
        int y=getfa(m1[s2]);                    
        if(m3[x]>m3[y]){                        //比较两个父亲,哪个更小
            fa[x]=y;
        }
        else{
            fa[y]=x;
        }
    }

    pair<string,int>process(string str){
        int pos1,pos2;
        string name1,name2;
        pos1 = str.find('(');
		pos2 = str.find(')');
		name1 = str.substr(0, pos1);
		name2 = str.substr(pos1 + 1, pos2 - pos1 - 1);
        return {name1,atoi(name2.c_str())};
    }
    pair<string,string>processSyn(string str){
        string first,second;
		int pos1 = str.find(',');
		first = str.substr(1, pos1 - 1);
		second = str.substr(pos1 + 1, str.size() - pos1 - 2);

        return {first,second};
    }
    vector<string> trulyMostPopular(vector<string>& names, vector<string>& synonyms) {
        int n=names.size();
        int idx=0;
        for(auto str:names){
            pair<string,int>t=process(str);                 //拆分 名字 频率
            m1[t.first]=idx;                                //map<名字,索引>       m1
            m2[idx]=t.second;                               //map<索引,频率>       m2
            m3[idx]=t.first;                                //map<索引,名字>
            idx++;
        }
        fa.resize(n);                                       //父结点的索引
        for(int i=0;i<n;i++){
            fa[i]=i;
        }
        for(auto str:synonyms){
            pair<string,string>p=processSyn(str);           //将相同的名字进行合并
            unionXY(p.first,p.second);
        }
        vector<string>res;
        map<int,int>nums;//表示个数
        for(int i=0;i<n;i++){                               //找到i的父亲,并更新其中的值
            nums[getfa(i)]+=m2[i];
        }
        for(auto p:nums){
            string tmp=m3[p.first];
            tmp+='(';
            tmp+=to_string(p.second);
            tmp+=')';
            res.push_back(tmp);
        }
        return res;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值