草庐IT

并查集和哈希表的实现

小王学代码 2023-04-20 原文

并查集和哈希表的实现

文章目录


1.并查集的功能

1.将两个集合进行合并

2.询问两个元素是否在一个集合里面

2.并查集的基本原理

基本原理:每一个集合用一棵树来表示,树根的编号就是整个集合的编号,每一个点存储的都是其父节点,p[x]表示的就是x的父节点

要考虑的问题有三个
问题一:如何判断树根 if(p[x]==x)

问题二:如何求x的集合编号 while(p[x]!=x) x=p[x]

问题三:如何合并两个结合 p[x]是x的集合编号,p[y]是y的集合编号,p[x]=y;表示x的父节点是y

3.并查集的实现

并查集,查找两个元素,是否在一个集合里面,而且可以将两个集合合并

//首先要设置全员变量
using namespace std;

const int n = 100010;
int n,m;
int p[N];//p[N]来存储的是父节点

//寻找操作
int find (int x)
{
    if(p[x]!=x) p[x]=find (p[x]);
	return p[x];//如果x的父节点不是x的时候,就要将p[x]放在find函数里面去,用p[x]来接收,直到最后p[x]==x ,递归返回之后,这个结合的所有结点的父节点都是x
}
//相当于 返回x的祖宗结点,并且有路径压缩

//如果说我们输入”M“来表示将两个集合合并
int main()
{
    scanf("%d%d",&n,&m);
    //输入n个数字,进行m次操作
    for(int i = 1;i <= n; i++ ){
        p[i]=i;//首先让n个结点的父节点都是自己本身,因为初始的时候每一个结点就是一个集合
    }
    
    while(m--){
        char op[2];//输入操作的字符
        int a,b;//指定的两个结点
        scanf("%s%d%d",op,&a,&b);
        
		if(op[0]=='M'){
          p[find(a)]=find(b);//将两个集合合并,可以先找到a,b的祖宗结点,这样讲a祖宗结点的父节点设置为b的祖宗结点,这样两个集合合并
        }
        else {//op不是M的时候,就是要进行判定ab是否是在一个集合里面
            //如果是I 表示判断是否两个结点在一个集合里面
            if(find(a)==find(b)){
                printf("Yes\n");
            }else {
                printf("NO\n");
            }
        }
    }
    return 0;
}

哈希表(hash)

哈希表常用的两个方式来存储数据, 拉链法和开放寻址法

哈希表的原理就是将一个很大范围中的数字,放置在一个小范围的数组中存储起来.

可以实现的功能为1.存储 2.查找

比如-109 ~109 存放在一个范围为0~105 的数组中存储起来

操作流程

1.先进行mod 取模,得到一个数字,存放在一个数组中

2.因为对于这些数据,会有冲突的时候,所以对于多个数据取模得到一个数值,使用拉链法,数组的每一个槽都可以对应一个链表形式的结构,通过这个结构,将取模相同的元素,放在这个槽对应的链表中,然后在查找的过程中近似实现O(1)的操作

我们mod的时候使用的数字应该是个质数,如果处理在0-100000范围内的数据,那么就要找到大于100000的最小质数

查找mod数值的方法为:

int getInteger(int n){//n传递为100000
	while(n++){
        for(int j=2;j*j<=n;j++){
            if(n%j==0){
                break;
            }
        }
    }
    return n;
}

1.拉链法

操作的流程,在c/c++中需要自行用数组来表示链表,在Java中可以使用LinkedList来表示链表

我们使用c++来举例

using namespace std;

const int n=100003;//我们提前找到了这个大于最大范围的最小质数
int h[N],e[N],ne[N],idx;
//h[N]用来存储模的数值
//e[N]用来形成链表,存储的数值
//ne[N]用来得到当前N下标对应的下一个数值
//idx表示链表的下标

//我们要实现的是,输入一个n,表示有n个操作,如果是输入"I x"表示插入数值x,如果是"Q x"实现查找x,查找到x 输出Yes 没有输出No
#include<string.h>
#include<stdio.h>
#include<iostream>

void insert(int x){
    int k=(x % N + N ) % N;//取模,x%N可能为负值,所以+N,然后再mod N
    //得到k就是要存储在槽中的数值h[N],就是找到位置
    e[idx]=x;//用当前下标idx存储数值为x
    ne[idx]=h[k];//然后idx的下一个坐标为h[k]的位置也就是说,头插
    h[k]=idx;//然后h[k]=idx h[k]就相当于头节点
    idx++;//添加一个元素idx++;
    
}

bool find(int x){
    int k=( x % N + N ) % N ;
	for(int i=h[k];i!=-1;i=ne[i]){
        //得到x的存储在槽终点 位置h[k],然后查询链表
        if(x==e[i]){
            return true;
        }
    }
    return false;
}

int main()
{
    int n;
    scanf("%d",&n);
    //表示输入n个操作
    memset(h,-1,sizeof(h));//对于h[N]数组进行赋值为-1
    
    while(n--){
        char op[2];
        int x;
        scanf("%s%d",op,&x);
        if(op=='I'){//插入存储数值x
            insert(x);
        }else {
            //进行查找x
            if(find(x)){
                printf("Yes\N");
            }else{
                printf("No\n");
            }
        }
    }
    return 0;
}

拉链法的主要内容是

1.得到k的数值,然后在h[k]数组上加上链表,使用e[idx]数组来存储数值x,用ne[idx]=h[k],h[k]=idx来实现头插,最后idx++;

//插入的算法
void insert(int x){
    int k=(x % N + N) % N;
    e[idx]==x;
    ne[idx]=h[k];
    h[k]=idx;
    idx++;
}

2.我们对于h[N]数组初始化的时候赋值为-1,但是在插入数据的时候,会头插,将idx的数值赋值给了h[N]数组

//查询的时候的代码
bool find(int x){
    int k=(x % N + N) % N;    
    for(int i=h[k];i!=-1;i=ne[i]){
        if(e[i]==x){
            return true;
        }
    }
    return false;
}

拉链法主要是插入函数insert和find函数.

2.开放寻址法

开放寻址法的主要方式为: 开辟的数组的发小是输入数据范围的2,3倍

方法流程

1.先找到一个合适的质数

2.插入方法为,在存储数据的时候通过hash函数,得到k,然后存储

3.进行查找

代码演示

using namespace std;

const int N=100003;
int null=0x3f3f3f3f;//设定一个极大值
int h[N];//存储数值的数组

int find(int x){
    int k=(x % N + N) % N;
    while(h[k]!=null && h[k]!=x){
        //如果说是不为null不为x,表示没有存储过这个数值
        k++;//没找到就k++
        if(k==N)  k=0;//如果等于N,那就从头0开始
    }
    return k;//找到了就返回的k值
}

int main()
{
    int n;//表示n个操作数
    scanf("%d",&n);
    memset(h,0x3f,sizeof(h));
    //memset 作用是对于一个地址进行赋值,可以指定大小,指定数值
    //函数有三个参数
    //第一个是 输入地址,比如h表示数组地址,第二个参数是存放数值,这个是存放一个字节的数值,如果是int 数组,那就是4个0x3f
    //第三个参数是数组大小,也就是要赋值的范围
 
    while(n--){
        char op[2];
        int x;
        scanf("%s%d",op,&x);
     	int k= find(x);
        if(op[0]=='I'){
            h[k]=x;//进行插入数值x
        }else{
            //判断是否有x在数组里面
            if(h[k]!=null) printf("Yes\n");
            else printf("No\n");
        }
    }
    return 0;
}

3,字符串哈希

类似于kmp 但是可以实现查找字符串到O(1),基本上更好

字符串hash的用法步骤是:

1.使用p进制,类似于10进制这样,用h[0],h[1]….来存储前n个字符串的p计数

2.使得一串字符,得到一个数字 p进制,有个经验值为131或者是13331;如果是abcd 那就是(a *p[3]+b*p[2]+c*p[1]+d*p[0]) % Q Q也有经验值 为264 因为用h数组存储哈希值,264 可以用unsigned long long来表示,如果大于264 会超出存储范围 ,自动模值

那就是直接 unsigned long long h[n]=a*p[3]+b*p[2]+c*p[1]+d*p[0] 即可

3.然后如果是想要得到 l 到 r 之间的字符串哈希,前段为高位,后端为低位,如果是r>1 那么就是让 h [ l-1 ] *p[ r- l +1 ] 然后h[r]-h[l-1]*p[r-l+1]

代码如下

using namespace std;

typedef unsigned long long ULL;
const int N=100010,p=131;

int n,m;
char str[N];
ULL h[N],p[N];

//题目要求  第一行输入n m str[N]  表示为 n为字符串长度 m为比较次数 str为字符串数组
//接下来m行  输入l1 r1 l2 r2  比较 l1到r1 l2到r2  这两段字符串是否相等

ULL get(int l,int r){
    return h[r]-h[l-1]*p1[r-l+1];
}
int main()
{
	scanf("%d%d%s", &n, &m, str + 1);
	//str数组从1开始 
	//
	p[0] = 1;
	//得到数字
	for (int i = 1; i <= n; i++)
	{
		p[i] = p1[i - 1] * p;
		h[i] = h[i - 1] * p + str[i];
	}

	while (m--)
	{
		int l1, r1, l2, r2;
		scanf("%d%d%d%d", &l1, &r1, &l2, &r2);

		if (get(l1, r1) == get(l2, r2))  						printf("Yes\n");
		else printf("No\n");
	}
	return 0;
}

有关并查集和哈希表的实现的更多相关文章

  1. ruby - 如果指定键的值在数组中相同,如何合并哈希 - 2

    我有一个这样的哈希数组:[{:foo=>2,:date=>Sat,01Sep2014},{:foo2=>2,:date=>Sat,02Sep2014},{:foo3=>3,:date=>Sat,01Sep2014},{:foo4=>4,:date=>Sat,03Sep2014},{:foo5=>5,:date=>Sat,02Sep2014}]如果:date相同,我想合并哈希值。我对上面数组的期望是:[{:foo=>2,:foo3=>3,:date=>Sat,01Sep2014},{:foo2=>2,:foo5=>5:date=>Sat,02Sep2014},{:foo4=>4,:dat

  2. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  3. ruby - 如何在 Grape 中定义哈希数组? - 2

    我使用Ember作为我的前端和GrapeAPI来为我的API提供服务。前端发送类似:{"service"=>{"name"=>"Name","duration"=>"30","user"=>nil,"organization"=>"org","category"=>nil,"description"=>"description","disabled"=>true,"color"=>nil,"availabilities"=>[{"day"=>"Saturday","enabled"=>false,"timeSlots"=>[{"startAt"=>"09:00AM","endAt"=>

  4. ruby - 在哈希的键数组中追加元素 - 2

    查看我的Ruby代码:h=Hash.new([])h[0]=:word1h[1]=h[1]输出是:Hash={0=>:word1,1=>[:word2,:word3],2=>[:word2,:word3]}我希望有Hash={0=>:word1,1=>[:word2],2=>[:word3]}为什么要附加第二个哈希元素(数组)?如何将新数组元素附加到第三个哈希元素? 最佳答案 如果您提供单个值作为Hash.new的参数(例如Hash.new([]),完全相同的对象将用作每个缺失键的默认值。这就是您所拥有的,那是你不想要的。您可以改用

  5. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  6. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  7. MIMO-OFDM无线通信技术及MATLAB实现(1)无线信道:传播和衰落 - 2

     MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO

  8. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

  9. ruby - 在 Ruby 中创建按公共(public)键值分组的新哈希 - 2

    假设我有一个在Ruby中看起来像这样的哈希:{:ie0=>"Hi",:ex0=>"Hey",:eg0=>"Howdy",:ie1=>"Hello",:ex1=>"Greetings",:eg1=>"Goodday"}有什么好的方法可以将它变成如下内容:{"0"=>{"ie"=>"Hi","ex"=>"Hey","eg"=>"Howdy"},"1"=>{"ie"=>"Hello","ex"=>"Greetings","eg"=>"Goodday"}} 最佳答案 您要求一个好的方法来做到这一点,所以答案是:一种您或同事可以在六个月后理解

  10. ruby - Arrays Sets 和 SortedSets 在 Ruby 中是如何实现的 - 2

    通常,数组被实现为内存块,集合被实现为HashMap,有序集合被实现为跳跃列表。在Ruby中也是如此吗?我正在尝试从性能和内存占用方面评估Ruby中不同容器的使用情况 最佳答案 数组是Ruby核心库的一部分。每个Ruby实现都有自己的数组实现。Ruby语言规范只规定了Ruby数组的行为,并没有规定任何特定的实现策略。它甚至没有指定任何会强制或至少建议特定实现策略的性能约束。然而,大多数Rubyist对数组的性能特征有一些期望,这会迫使不符合它们的实现变得默默无闻,因为实际上没有人会使用它:插入、前置或追加以及删除元素的最坏情况步骤复

随机推荐