LOADING

加载过慢请开启缓存 浏览器默认开启

并查集初步学习

树的关系图欸

当我们面对许多数据量(且其中某一部分数据之间相互有联系的时候),如下六个元素,1和2之间连接,3、4和5相互连接,在处理某部分问题的时候十分难处理,因此聪明的人类引出了并查集这个概念方法来解决问题,可以将分别联系的数据整理成一颗颗分叉的树。(好耶~)

并查集是将所有元素分为几个集合,每个集合内的元素互相连接,用广泛的介绍就是,如果A和B是好朋友,B和C是好朋友,虽然A和C不认识,但是通过B的介绍,A和C也能成为好朋友,这样ABC都能在一个集合内,这如同树一般的分叉连接,并且不同的集合内不会有共同的元素

1、并查集的find函数

对于并查集的实用,主要有两个函数,一个是find函数,用于查找该元素的头元素,对于以下途中的元素可知,A,C,D的头元素都是B,此时B元素就是该集合的老大。

int pre[100];   //对数组f,f[i]=j中表示i元素的上一层元素是j
int find ( int n )
{
    int head=n;
    while(pre[head]!=head) //一直查找没有最上面一层的元素,相当于一个集合的boss吧
        head=pre[head];
 
    //part 2  
    //*路径压缩 
    int i=n,j;
    
    while(i!=head){    
        j=pre[i];      //j是i的前一元素
        pre[i]=head;  
        i=j;
    }
    //压缩结束
    *//
 
    return head;
    
}

如果我们不进行路径压缩那部分的话就会发现,每次要查找一个元素的老大的话,都要一直往上一层循环找,当层数很大的时候就很费时间复杂度,这时候我们就可以用路径压缩,使得在一个元素往上找老大的过程中遇到的元素都直接指向老大,如图

void join(int i,int j){
    int x=pre[i],y=pre[j];  //x和y分别是i和j的老大
    if(x!=y)  pre[x]=y ;  //如果i和j的老大不是是同一个人
          //让j组织的老大的成为i组织老大的老大
}


这时候合并的两组织层数用树来看还是比较长的,可以用find一次来压缩一下路径使得ABCEH属于同一层。

例题来自ACWing


这道题用主要用贪心的思想,同时加上并查集的优化,先求出最大的保质期时间,然后对于每一天的的头元素都是改天的日期,在从利润最大的商品往最小的商品遍历的时候,如果说对保质期使i的商品,若pre [ i ] == i,那么可以说明这一天的还没有卖出东西,所以可以把目前利润最大的卖了,同时使得目前该保质期的商品的 pre [ i ] = i - 1,即使得保质期为i的头元素为 i - 1, 这样当遇到第二大利润且保质期也是 i 的时候这个商品只能在该保质期的前的第 i - 1 天卖,从前往后的头元素表示着可以卖出商品的时间,如果该时间等于0了也说明天数卖完了

这题里用的并查集pre [ i ] = j 表示在第 i 天之前距 i 最近的还未出售过商品的日期是 j ,在每用完第j天后更新 j = j - 1,使得某天后面的pre [ i ]都与前面空闲的某天直接联系。

#include <bits/stdc++.h>
using namespace std;
#define N 10010
int n, pre[N];
pair<int, int> good[N];  //存储每一个货物的利润和保质期
void solve() {
    int day=0, num=0;
    for (int i=1;i<= n;i++) {
        cin>>good[i].first>>good[i].second;
        day=max(day,good[i].second);  //取得一个最晚过期时间
    }
    sort(good+1,good+n+1);  // 对利润从小到大排序
    for(int i=0;i<= day;i++)  pre[i] = i;//初始化每一天的头元素     
    //    利用路径压缩, 可以快速找出从过期时间往前数第一个空闲的天数
    for(int i=n;i>0;i--){
        int r=findd(good[i].second);  //获取利润最大的商品的过期日期
        if(r!=0){                   //如果这个"位置"还没有被用掉
            num+=good[i].first;     //更新答案        
            pre[r]=r-1;   //合并两个集合(r与r-1)-> 把这个"位置"指向他前一个"位置"
        }
    }
    cout<<num<<endl;
}
int findd(int x) {
    if (x==pre[x]) return x;   
    return pre[x]=findd(pre[x]);  //路径压缩
}
int main()
{
    while(cin>>n) {
        solve();
    }
    return 0;
}