您的位置:首页 > 娱乐 > 八卦 > 位图和布隆过滤器

位图和布隆过滤器

2024/12/23 7:16:34 来源:https://blog.csdn.net/m0_62975443/article/details/139888253  浏览:    关键词:位图和布隆过滤器

位图(bitset)

概念

所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在的。

给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中?

要判断一个数是否在某一堆数中,我们可能会想到如下方法:

  • 将这一堆数进行排序,然后通过二分查找的方法判断该数是否在这一堆数中。
  • 将这一堆数插入到unordered_set容器中,然后调用find函数判断该数是否在这一堆数中。

单从方法上来看,这两种方法都是可以,而且效率也不错,第一种方法的时间复杂度是O(NlogN),第二种方法的时间复杂度是O(N)。

但问题是这里有40亿个数,若是我们要将这些数全部加载到内存当中,那么将会占用16G的空间,空间消耗是很大的。因此从空间消耗来看,上面这两种方法实际都是不可行的。

实际在这个问题当中,我们只需要判断一个数在或是不在,即只有两种状态,那么我们可以用一个比特位来表示数据是否存在,如果比特位为1则表示存在,比特位为0则表示不存在。比如:

无符号整数总共有232个,因此记录这些数字就需要232个比特位,也就是512M的内存空间,内存消耗大大减少。

位图的应用

常见位图的应用如下:

  1. 快速查找某个数据是否在一个集合中。
  2. 排序。
  3. 求两个集合的交集、并集等。
  4. 操作系统中磁盘块标记。
  5. 内核中信号标志位(信号屏蔽字和未决信号集)。

位图(bitset)的使用

bitset的定义方式

方式一: 构造一个16位的位图,所有位都初始化为0。

bitset<16> bs1; //0000000000000000

方式二: 构造一个16位的位图,根据所给值初始化位图的前n位。

bitset<16> bs2(0xfa5); //0000111110100101

方式三: 构造一个16位的位图,根据字符串中的0/1序列初始化位图的前n位。

bitset<16> bs3(string("10111001")); //0000000010111001

bitset成员函数的使用

bitset中常用的成员函数如下:

代码示例:

#include <iostream>
#include <bitset>
using namespace std;

int main()
{
    bitset<8> bs;
    bs.set(2); //设置第2位
    bs.set(4); //设置第4位
    cout << bs << endl; //00010100
    
    bs.flip(); //反转所有位
    cout << bs << endl; //11101011
    cout << bs.count() << endl; //6

    cout << bs.test(3) << endl; //1

    bs.reset(0); //清空第0位
    cout << bs << endl; //11101010

    bs.flip(7); //反转第7位
    cout << bs << endl; //01101010

    cout << bs.size() << endl; //8

    cout << bs.any() << endl; //1

    bs.reset(); //清空所有位
    cout << bs.none() << endl; //1

    bs.set(); //设置所有位
    cout << bs.all() << endl; //1
    return 0;
}

注意: 使用成员函数set、reset、flip时,若指定了某一位则操作该位,若未指定位则操作所有位。

bitset运算符的使用

bitset中>>、<<运算符的使用

bitset容器对>>、<<运算符进行了重载,我们可以直接使用>>、<<运算符对biset容器定义出来的对象进行输入输出操作。

#include <iostream>
#include <bitset>
using namespace std;

int main()
{
    bitset<8> bs;
    cin >> bs; //10110
    cout << bs << endl; //00010110
    return 0;
}

bitset中赋值运算符、关系运算符、复合赋值运算符、单目运算符的使用

bitset容器中不仅对赋值运算符和一些关系运算符进行了重载,而且对一些复合赋值运算符和单目运算符也进行了重载,我们可以直接使用这些运算符对各个位图进行操作。

包括如下运算符:

  • 赋值运算符:=。
  • 关系运算符:==、!=。
  • 复合赋值运算符:&=、|=、^=、<<=、>>=。
  • 单目运算符:~。

代码示例:

#include <iostream>
#include <string>
#include <bitset>
using namespace std;

int main()
{
    bitset<8> bs1(string("10101010"));
    bitset<8> bs2(string("10101010"));
    bs1 >>= 1;
    cout << bs1 << endl; //01010101

    bs2 |= bs1;
    cout << bs2 << endl; //11111111
    return 0;
}

bitset中位运算符的使用

bitset容器中同时也对三个位运算符进行了重载,我们可以直接使用&、|、^对各个位图进行操作。

代码示例:

#include <iostream>
#include <string>
#include <bitset>
using namespace std;

int main()
{
    bitset<8> bs1(string("10101010"));
    bitset<8> bs2(string("01010101"));
    
    cout << (bs1&bs2) << endl; //00000000
    cout << (bs1|bs2) << endl; //11111111
    cout << (bs1^bs2) << endl; //11111111
    return 0;
}

bitset中[ ]运算符的使用

bitset容器中对[ ]运算符进行了重载,我们可以直接使用[ ]对指定位进行访问或修改。

代码示例:

#include <iostream>
#include <string>
#include <bitset>
using namespace std;

int main()
{
    bitset<8> bs(string("00110101"));
    cout << bs[0] << endl; //1
    bs[0] = 0;
    cout << bs << endl; //00110100
    return 0;
}

位图(bitset)的模拟实现

位图的所有接口定义:

namespace dt
{
    //模拟实现位图
    template<size_t N>
    class bitset
    {
    public:
        //构造函数
        bitset();
        //设置位
        void set(size_t pos);
        //清空位
        void reset(size_t pos);
        //反转位
        void flip(size_t pos);
        //获取位的状态
        bool test(size_t pos);
        //获取可以容纳的位的个数
        size_t size();
        //获取被设置位的个数
        size_t count();
        //判断位图中是否有位被设置
        bool any();
        //判断位图中是否全部位都没有被设置
        bool none();
        //判断位图中是否全部位都被设置
        bool all();
        //打印函数
        void Print();
    private:
        vector<int> _bits; //位图
    };
}

bitset类的实现

构造函数

在构造位图时,我们需要根据所给位数N,创建一个N位的位图,并且将该位图中的所有位都初始化为0。

一个整型有32个比特位,因此N个位的位图就需要用到N/32个整型,但是实际我们所需的整型个数是N/32+1,因为所给非类型模板参数N的值可能并不是32的整数倍。

例如,当N为40时,我们需要用到两个整型,即40/32+1=2。

代码示例:

//构造函数
bitset()
{
    _bits.resize(N / 32 + 1, 0);
}

set成员函数

set成员函数用于设置位。

设置位图中指定的位的方法如下:

  1. 计算出该位位于第 i 个整数的第 j 个比特位。
  2. 将1左移 j 位后与第 i 个整数进行或运算即可。

代码示例:

//设置位
void set(size_t pos)
{
    assert(pos < N);

    //算出pos映射的位在第i个整数的第j个位
    int i = pos / 32;
    int j = pos % 32;
    _bits[i] |= (1 << j); //将该位设置为1(不影响其他位)
}

reset成员函数

reset成员函数用于清空位。

清空位图中指定的位的方法如下:

  1. 计算出该位位于第 i 个整数的第 j 个比特位。
  2. 将1左移 j 位再整体反转后与第 i 个整数进行与运算即可。

代码示例:

//清空位
void reset(size_t pos)
{
    assert(pos < N);

    //算出pos映射的位在第i个整数的第j个位
    int i = pos / 32;
    int j = pos % 32;
    _bits[i] &= (~(1 << j)); //将该位设置为0(不影响其他位)
}

flip成员函数

flip成员函数用于反转位。

反转位图中指定的位的方法如下:

  1. 计算出该位位于第 i 个整数的第 j 个比特位。
  2. 将1左移 j 位后与第 i 个整数进行异或运算即可。

代码示例:

//反转位
void flip(size_t pos)
{
    assert(pos < N);

    //算出pos映射的位在第i个整数的第j个位
    int i = pos / 32;
    int j = pos % 32;
    _bits[i] ^= (1 << j); //将该进行反转(不影响其他位)
}

test成员函数

test成员函数用于获取位的状态。

获取位图中指定的位的状态的方法如下:

  1. 计算出该位位于第 i 个整数的第 j 个比特位。
  2. 将1左移 j 位后与第 i 个整数进行与运算得出结果。
  3. 若结果非0,则该位被设置,否则该位未被设置。

代码示例:

//获取位的状态
bool test(size_t pos)
{
    assert(pos < N);

    //算出pos映射的位在第i个整数的第j个位
    int i = pos / 32;
    int j = pos % 32;
    if (_bits[i] & (1 << j)) //该比特位被设置
        return true;
    else //该比特位未被设置
        return false;
}

size成员函数

size成员函数用于获取位图中可以容纳的位的个数。

将所给非类型模板参数进行返回即可。

//获取可以容纳的位的个数
size_t size()
{
    return N;
}

count成员函数

count成员函数用于获取位图中被设置的位的个数。

获取位图中被设置的位的个数,也就是统计位图中1的个数,我们只需要依次统计每个整数二进制中1的个数,然后将其相加即可得到位图中1的个数。

统计二进制中1的个数的方法如下:

  1. 将原数 n 与 n - 1 进行与运算得到新的 n 。
  2. 判断 n 是否为0,若 n 不为0则继续进行第一步。

如此进行下去,直到 n 最终为0,此时该操作进行了几次就说明二进制中有多少个1。

因为该操作每进行一次就会消去二进制中最右边的1,例图如下:

代码示例:

//获取被设置位的个数
size_t count()
{
    size_t count = 0;
    //将每个整数中1的个数累加起来
    for (auto e : _bits)
    {
        int num = e;
        //计算整数num中1的个数
        while (num)
        {
            num = num&(num - 1);
            count++;
        }
    }
    return count; //位图中1的个数,即被设置位的个数
}

any成员函数

any成员函数用于判断位图中是否有位被设置。

我们只需遍历每一个整数,若这些整数全部都为0,则说明位图中没有位被设置过。
虽然位图可能并没有包含最后一个整数的全部比特位,但由于我们构造位图时是将整数的全部比特位都初始化成了0,因此不会对此处判断造成影响。

代码示例:

//判断位图中是否有位被设置
bool any()
{
    //遍历每个整数
    for (auto e : _bits)
    {
        if (e != 0) //该整数中有位被设置
            return true;
    }
    return false; //全部整数都是0,则没有位被设置过
}

none成员函数

none成员函数用于判断位图中是否全部位都没有被设置。

位图中是否全部位都没有被设置,实际上就是位图中有位被设置的反面,因此none成员函数直接调用any成员函数,然后将返回值取反后再进行返回即可。

代码示例:

//判断位图中是否全部位都没有被设置
bool none()
{
    return !any();
}

all成员函数

all成员函数用于判断位图中是否全部位都被设置。

判断过程分为两步:

  1. 先检查前n-1个整数的二进制是否为全1。
  2. 再检查最后一个整数的前N%32个比特位是否为全1。

需要注意的是,如果位图没有包含最后一个整数的全部比特位,那么最后一个整数的二进制无论如何都不会为全1,所以在判断最后一个整数时应该只判断位图所包含的比特位。

代码示例:

//判断位图中是否全部位都被设置
bool all()
{
    size_t n = _bits.size();
    //先检查前n-1个整数
    for (size_t i = 0; i < n - 1; i++)
    {
        if (~_bits[i] != 0) //取反后不为全0,说明取反前不为全1
            return false;
    }
    //再检查最后一个整数的前N%32位
    for (size_t j = 0; j < N % 32; j++)
    {
        if ((_bits[n - 1] & (1 << j)) == 0) //该位未被设置
            return false;
    }
    return true;
}

打印函数

可以实现一个打印函数,便于检查我们上述代码的正确性,打印位图时遍历位图所包含的比特位进行打印即可,在打印位图的过程中可以顺便统计位图中位的个数count,将count与我们传入的非类型模板参数N进行比较,可以判断位图大小是否是符合我们的预期。

//打印函数
void Print()
{
    int count = 0;
    size_t n = _bits.size();
    //先打印前n-1个整数
    for (size_t i = 0; i < n - 1; i++)
    {
        for (size_t j = 0; j < 32; j++)
        {
            if (_bits[i] & (1 << j)) //该位被设置
                cout << "1";
            else //该位未被设置
                cout << "0";
            count++;
        }
    }
    //再打印最后一个整数的前N%32位
    for (size_t j = 0; j < N % 32; j++)
    {
        if (_bits[n - 1] & (1 << j)) //该位被设置
            cout << "1";
        else //该位未被设置
            cout << "0";
        count++;
    }
    cout << " " << count << endl; //打印总共打印的位的个数
}

布隆过滤器

在上面我们用位图很好的解决了多重整数高效查询的问题,那么我们在面对字符串时,该如何解决呢?

布隆过滤器的产生

布隆过滤器(Bloom Filter)是由布隆在1970年提出的,它是一种空间效率高、查询速度快的数据结构,主要用于判断一个元素是否属于一个集合。布隆过滤器的提出解决了在大规模数据集中进行高效查找的问题,特别是当内存或存储有限的情况下。

出现背景: 在计算机科学中,一些常见的问题包括查找元素是否在某个集合中,如单词拼写检查、垃圾邮件过滤、URL检测等。传统的数据结构如散列表或树结构可以解决这些问题,但它们在存储和查询效率上存在一些限制,特别是在面对大规模数据集时。因此,布隆过滤器的提出是为了解决这些限制。

原理: 布隆过滤器的核心思想是使用一个位数组(Bit Array)和多个哈希函数。其主要工作步骤如下:

1.初始化:创建一个位数组,初始所有位为0,以及选择多个不同的哈希函数。
2.插入元素:当需要插入一个元素时,将该元素经过多个哈希函数的映射,得到多个不同的位,然后将这些位设置为1。
3.查询元素:当需要查询一个元素是否在集合中时,将该元素经过多个哈希函数的映射,得到多个位的位置,然后检查这些位是否都为1。如果所有位都为1,则认为元素可能存在于集合中;如果任何一个位为0,则可以确定元素不存在于集合中。

特点

1.布隆过滤器具有高效的查询速度,因为它不需要实际存储元素本身,而只需要检查位数组中的位。
2.布隆过滤器可能会出现误判,即元素被判断为存在于集合中,但实际上并不在,但不会有漏判,即如果元素不在集合中,布隆过滤器不会将其判断为存在。
3.布隆过滤器的空间效率很高,因为它可以存储大量元素而占用很少的内存。

想了解更多原理和细节可以看一下这个大佬写的文章:

布隆过滤器的插入

布隆过滤器的插入过程是其核心操作之一,用于将元素添加到布隆过滤器中,以便后续查询该元素是否存在于集合中。下面是布隆过滤器的插入过程的详细解释:

1.初始化:首先,创建一个布隆过滤器,它包括一个位数组(Bit Array)和多个哈希函数。位数组中的每个位都初始化为0。
2.插入元素:要将一个元素插入布隆过滤器,执行以下步骤:
   哈希函数:使用预先选择的多个哈希函数,将要插入的元素映射为多个不同的位索引。每个哈希函数都会生成一个位索引,通常使用不同的种子值来确保生成不同的位索引。
   设置位:对于每个哈希函数生成的位索引,将相应的位数组中的位设置为1。这表示元素已经存在于布隆过滤器中。
3.完成插入:一旦对元素执行了上述步骤,插入过程就完成了。元素已被记录在位数组中,以后可以查询该元素是否存在于集合中。

注意事项

1.布隆过滤器不存储实际元素本身,只存储元素的哈希值或映射后的位索引。因此,插入操作是基于哈希值的,而不是实际元素。
2.哈希函数的数量和质量对布隆过滤器的性能至关重要。更多的哈希函数通常意味着更低的误判率,但也需要更多的计算资源。选择哈希函数时应考虑平衡性能和内存占用。
3.插入操作通常是快速的,因为它涉及位数组的直接设置,而不需要大量内存操作。这是布隆过滤器高效的一个原因之一。

插入过程使布隆过滤器记录了元素的存在,使后续的查询操作成为可能。查询操作会利用相同的哈希函数,检查位数组中的位来确定元素是否在集合中。这使得布隆过滤器在查找元素是否存在时非常高效,尤其适用于大规模数据集和资源有限的环境。

布隆过滤器的查找

布隆过滤器的思想是将一个元素用多个哈希函数映射到一个位图中,因此被映射到的位置的比特位一定为1。所以可以按照以下方式进行查找:分别计算每个哈希值对应的比特位置存储的是否为零,只要有一个为零,代表该元素一定不在哈希表中,否则可能在哈希表中。

注意:布隆过滤器如果说某个元素不存在时,该元素一定不存在,如果该元素存在时,该元素可能存在,因为有些哈希函数存在一定的误判

比如:在布隆过滤器中查找"alibaba"时,假设3个哈希函数计算的哈希值为:1、3、7,刚好和其他元素的比特位重叠,此时布隆过滤器告诉该元素存在,但实该元素是不存在的,存在可能存在误判,但是不存在不会存在误判。

布隆过滤器的删除

布隆过滤器不能直接支持删除工作,因为在删除一个元素时,可能会影响其他元素。

比如:删除其中一个元素,如果直接将该元素所对应的二进制比特位置0,另一元素也被删除了,因为这两个元素在多个哈希函数计算出的比特位上刚好有重叠。

一种支持删除的方法:将布隆过滤器中的每个比特位扩展成一个小的计数器,插入元素时给k个计数器(k个哈希函数计算出的哈希地址)加一,删除元素时,给k个计数器减一,通过多占用几倍存储空间的代价来增加删除操作。

布隆过滤器的实现

在这里我们先实现3个哈希函数:

struct HashBKDR
{
    // BKDR
    size_t operator()(const string& key)
    {
        size_t val = 0;
        for (auto ch : key)
        {
            val *= 131;
            val += ch;
        }

        return val;
    }
};

struct HashAP
{
    // BKDR
    size_t operator()(const string& key)
    {
        size_t hash = 0;
        for (size_t i = 0; i < key.size(); i++)
        {
            if ((i & 1) == 0)
            {
                hash ^= ((hash << 7) ^ key[i] ^ (hash >> 3));
            }
            else
            {
                hash ^= (~((hash << 11) ^ key[i] ^ (hash >> 5)));
            }
        }
        return hash;
    }
};

struct HashDJB
{
    // BKDR
    size_t operator()(const string& key)
    {
        size_t hash = 5381;
        for (auto ch : key)
        {
            hash += (hash << 5) + ch;
        }

        return hash;
    }
};

1.BKDR哈希函数 (Bentley and Sedgewick, 1997):
BKDR哈希函数使用一个常数因子131,以及遍历字符串中的字符。
对于每个字符,它将当前哈希值乘以131,然后加上字符的ASCII码值。
最终,它返回计算得到的哈希值。
2.AP哈希函数 (Arjen Lenstra and Endre Szemerédi’s hash function):
AP哈希函数使用一系列位运算和异或操作来处理字符串中的字符。
对于每个字符,它会根据字符的位置(奇数或偶数)应用不同的位运算操作。
最终,它返回计算得到的哈希值。
3.DJB哈希函数 (Daniel J. Bernstein, 1991):
DJB哈希函数使用一个初始哈希值5381,以及遍历字符串中的字符。
对于每个字符,它将当前哈希值左移5位,然后加上字符的ASCII码值。
最终,它返回计算得到的哈希值。

布隆过滤器代码示例:

// N表示准备要映射N个值
template<size_t N, 
class K = string, class Hash1 = HashBKDR, class Hash2 = HashAP, class Hash3 = HashDJB>
class BloomFilter
{
public:
    void Set(const K& key)
    {
        size_t hash1 = Hash1()(key) % (_ratio*N);
        //cout << hash1 << endl;

        _bits->set(hash1);

        size_t hash2 = Hash2()(key) % (_ratio*N);
        //cout << hash2 << endl;

        _bits->set(hash2);

        size_t hash3 = Hash3()(key) % (_ratio*N);
        //cout << hash3 << endl;

        _bits->set(hash3);
    }

    bool Test(const K& key)
    {
        size_t hash1 = Hash1()(key) % (_ratio*N);
        //cout << hash1 << endl;
        if (!_bits->test(hash1))
            return false; // 准确的

        size_t hash2 = Hash2()(key) % (_ratio*N);
        //cout << hash2 << endl;

        if (!_bits->test(hash2))
            return false; // 准确的

        size_t hash3 = Hash3()(key) % (_ratio*N);
        //cout << hash3 << endl;

        if (!_bits->test(hash3))
            return false;  // 准确的

        return true; // 可能存在误判
    }


private:
    const static size_t _ratio = 5;
    std::bitset<_ratio*N>* _bits = new std::bitset<_ratio*N>;
};

Set(const K& key):
Set 方法用于将元素插入布隆过滤器中。
它分别使用三个哈希函数将元素映射到位数组中的三个位置,然后将这三个位置的位设置为1。
这样,插入操作将三次设置位操作,将元素标记为存在于布隆过滤器中。
Test(const K& key):
Test 方法用于测试元素是否存在于布隆过滤器中。
与插入相似,它使用三个哈希函数将元素映射到三个位数组位置,并检查这三个位置的位是否都为1。
如果有一个位置的位为0,说明元素不在布隆过滤器中,返回 false。
如果三个位置的位都为1,可能存在误判,返回 true。

这个布隆过滤器可以用于在快速查找集合中的元素是否存在,但需要注意,它存在误判的可能性,即可能会将不在集合中的元素判断为存在。_ratio 在这个布隆过滤器实现中是一个倍数,它决定了位数组的大小。位数组的大小是 _ratio * N,其中 N 表示准备要映射的值的数量。具体来说,这个倍数 _ratio 用于控制位数组的大小以平衡误判率和内存使用。增加 _ratio 会增加位数组的大小,从而减小误判率,但也会增加内存占用。减小 _ratio 会减小位数组的大小,降低内存占用,但也会增加误判率。

布隆过滤器优点

1.增加和查询元素的时间复杂度为:O(K), (K为哈希函数的个数,一般比较小),与数据量大小无关。
2.哈希函数相互之间没有关系,方便硬件并行运算。
3.布隆过滤器

的误判时,布隆过滤器比其他数据结构有这很大的空间优势。
5.数据量很大时,布隆过滤器可以表示全集,其他数据结构不能。
6.使用同一组散列函数的布隆过滤器可以进行交、并、差运算。

布隆过滤器缺陷

1.有误判率,即存在假阳性(False Position),即不能准确判断元素是否在集合中(补救方法:再建立一个白名单,存储可能会误判的数据)。

2.不能获取元素本身。

3.一般情况下不能从布隆过滤器中删除元素。

4.如果采用计数方式删除,可能会存在计数回绕问题。

布隆过滤器的应用

给一个超过100G大小的log file, log中存着IP地址, 设计算法找到出现次数最多的IP地址?与上题条件相同,如何找到top K的IP?如何直接用Linux系统命令实现?

使用布隆过滤器和哈希切割

1.建立布隆过滤器:首先,创建一个合适大小的布隆过滤器。布隆过滤器的位数组大小需要足够大,以容纳所有可能的IP地址。
2.遍历日志文件:逐行遍历日志文件,提取每行中的IP地址。
3.哈希切割:对每个提取的IP地址应用一种哈希函数来将其映射到布隆过滤器的位数组上。这可以4.使用布隆过滤器的 Set 方法来实现。
5.统计次数:同时,维护一个哈希表,其中键是IP地址,值是出现的次数。在哈希切割过程中,检6.查IP是否已存在于表中,如果存在,则增加其出现次数。
7.查询次数最多的IP:在遍历完整个日志文件后,您可以遍历字典,找到出现次数最多的IP地址。
8.查询top K的IP:要找到top K的IP地址,可以对字典按照出现次数进行排序,然后选择前K个IP地址。

Linux系统提供了一些强大的命令行工具来处理文本文件,可以使用这些工具来解决问题:

  1. 使用awk命令或grep命令提取日志文件中的IP地址。
  2. 使用sort命令对提取的IP地址进行排序。
  3. 使用uniq -c命令统计IP地址的出现次数。
  4. 使用sort -nr命令按出现次数对IP地址进行逆序排序。
  5. 使用head命令来获取top K的IP地址。

示例操作命令行:

cat log_file.log | grep -oE "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b" | sort | uniq -c | sort -nr | head -n K

这将列出出现次数最多的top K个IP地址。

无论使用哪种方法,都需要根据实际需求和性能要求来选择。使用布隆过滤器和哈希切割的方法可能需要编写自定义代码,但可以处理非常大的数据集。使用Linux系统命令则更加简单,但可能受限于系统资源和性能。

给两个文件,分别有100亿个query,我们只有1G内存,如何找到两个文件交集?分别给出精确算法和近似算法

在这种情况下,由于内存有限,需要使用外部排序(external sorting)技术来处理这两个大型文件并找到它们的交集。外部排序是一种适用于大规模数据集的算法,其中数据无法完全加载到内存中。

精确算法:

1.分别对两个文件进行外部排序:首先,将两个文件分别划分为多个小文件块,每个小文件块可以适应内存的大小。然后,对每个小文件块进行内部排序,以确保每个块内的数据是有序的。
2.合并排序的块:对于每个文件,使用归并排序等合并算法,逐个合并排序后的小块,以创建一个完全有序的大文件。
3.查找交集:一旦两个文件都有序,可以使用合并算法来查找它们的交集。比较两个文件中的元素,将相同的元素输出到结果文件中。

这样可以找到确切的交集,但需要大量的磁盘I/O和计算时间,因为数据需要多次读取和写入磁盘。

近似算法: 在内存有限的情况下,使用布隆过滤器可以实现近似的交集查找。以下是近似算法的步骤:

1.构建两个布隆过滤器:对于每个文件,构建一个布隆过滤器。这需要一小部分内存。在构建布隆过滤器时,需要选择合适的哈希函数和位数组大小,以平衡内存使用和误判率。
2.查询交集:对于第一个文件的每个查询,检查是否在第二个布隆过滤器中。如果布隆过滤器中存在该查询,将其添加到结果集中。
3.结果集:结果集中包含两个文件的近似交集。

近似算法的主要优点是节省了内存,但它会引入误判。如果某个查询在第一个布隆过滤器中存在,但实际上不存在于第二个文件中,它仍然会出现在结果集中。误判率取决于布隆过滤器的参数选择。

无论使用哪种算法,都需要在性能和准确性之间做权衡。精确算法提供准确的结果,但需要更多的时间和资源。近似算法可以在有限内存下快速处理,但可能会引入一定程度的误判。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com