人工智能与大数据面试指南——算法与数据结构:大数据量算法

分类目录:《人工智能与大数据面试指南》总目录

《人工智能与大数据领域面试指南》系列下的内容会持续更新,有需要的读者可以收藏文章,以及时获取文章的最新内容。


大数据量算法面试一般考查海量数据上的存储、处理、操作,一般考点只有两个:

  • 数据量过大导致无法在较短时间内解决,需要设计一个时间复杂度较低的算法
  • 数据量过大导致无法一次性载入内存,需要设计合适的数据结构来实现在内存中存储,或者使用外存解决问题

综上所述,常用的大数据量算法考查的知识点如下:


各种数据类型占用的存储空间

1个字节(byte)等于8个比特(bit),其中:

  • 比特/位(bit):计算机中存储数据的最小单位,指二进制数中的一个位数,其值为0或1
  • 字节(byte, B):字节是计算机存储容量的基本单位,1个字节由8位二进制数组成(1Byte=1B=8bit)。在计算机内部,一个字节可以表示一个数据,也可以表示一个英文字母,两个字节可以表示一个汉字。

一般在常见的编程语言中有如下8中数据类型:

  • 整型byte:取值范围为 − 128 ∼ 127 ( − 2 7 ∼ 2 7 − 1 ) -128\sim127(-2^7\sim2^7-1) 128127(27271),占用1个字节
  • 整型short:取值范围为 − 32768 ∼ 32767 ( − 2 15 ∼ 2 15 − 1 ) -32768\sim32767(-2^{15}\sim2^{15}-1) 3276832767(2152151),占用2个字节
  • 整型int:取值范围为 − 2147483648 ∼ 2147483647 ( − 2 31 ∼ 2 31 − 1 ) -2147483648\sim2147483647(-2^{31}\sim2^{31}-1) 21474836482147483647(2312311),占用4个字节
  • 整型long:取值范围为 − 9223372036854774808 ∼ 9223372036854774807 ( − 2 63 ∼ 2 63 − 1 ) -9223372036854774808\sim9223372036854774807(-2^{63}\sim2^{63}-1) 92233720368547748089223372036854774807(2632631),占用4个字节
  • 单精度浮点型float:占用4个字节
  • 浮点型double:占用8个字节
  • 布尔型boolean:逻辑上boolean类型只占1bit,但是虚拟机底层对boolean值进行操作实际使用的是int类型,操作boolean数组则使用byte类型
  • 文本型char:存储范围为\u0000~\uFFFF,占用2个字节,若采用unicode编码,则其前128字节编码与ASCII兼容

可以看出byte类型和short类型的取值范围比较小,而long类型的取值范围又过大,占用的空间多,所以在编程实践中int类型的使用较多。在通常情况下,如果JAVA中出现了一个整数数字比如:35,那么这个数字就是int类型的,如果我们希望它是byte类型的,可以在数据后加上大写的B,如:35B,表示它是byte类型的,同样的35S表示short类型,35L表示long类型的,表示int类型可以什么都不加。同时,之所以是2的7、15、31次方是因为其包含了正负整数,byte类型、short类型、int类型、long类型包含的实际数量总数为 2 8 2^8 28 2 16 2^{16} 216 2 32 2^{32} 232 2 64 2^{64} 264

单精度浮点型float类型占用4个字节(32位)存储空间的单精度值。单精度在一些处理器上比双精度更快而且只占用双精度一半的空间,但是当值很大或很小的时候,它将变得不精确。当你需要小数部分并且对精度的要求不高时,单精度浮点型的变量是有用的。单精度浮点型float类型的单精度值占用4个字节的空间,包括1个符号位、一个8位二进制指数和一个23 位尾数,共32位。由于尾数的高顺序位始终为 1,因此它不是以数字形式存储的。而double类型比float类型存储范围更大,精度更高,所以通常的浮点型的数据在不声明的情况下都是double型的,如果要表示一个数据是float型的,可以在数据后面加上F。浮点型的数据是不能完全精确的,所以有的时候在计算的时候可能会在小数点最后几位出现浮动,这是正常的。

在Python中,整数是变长的,无法精确控制内存,若需精确控制内存需使用C/C++、JAVA等语言。对于Python中的字符串,相同长度的字符串占用的存储空间和编码方式也有关系。


两个大文件A和B,每一行存储的都是一个字符串,找出其中在两个文件中都出现过的字符串

对于随机不定长的字符串:哈希
  • 根据给定内存需求或其他限制,确定需要划分的小文件数量 m m m(代码里的num_split
  • 遍历文件 A A A,对文件 A A A的每一行做Hash运算,根据Hash值 h h h将该行数据映射到小文件 A 1 , A 2 , ⋯   , A m A_1,A_2,\cdots, A_m A1,A2,,Am中, A i A_i Ai i i i满足: i ≡ h ( m o d m ) i \equiv h \pmod m ih(modm),若划分结束后发现存在子文件 A i A_i Ai仍然超过内存限制,可以通过增大 m m m解决
  • 遍历文件 B B B,对文件 B B B的每一行做Hash运算,根据Hash值 h h h i ≡ h ( m o d m ) i \equiv h \pmod m ih(modm)读取文件 A i A_i Ai并判断 h h h是否在 A i A_i Ai中,由于不同字符串可能产生相同Hash值,故在Hash值相同的情况下还需要判断字符串是否相同(可将文件 A A A的每行字符串的Hash值作为key,字符串本身或字符串行索引作为value存入小文件 A 1 , A 2 , ⋯   , A m A_1,A_2,\cdots, A_m A1,A2,,Am中),若Hash值不同则可以断定字符串不在文件 A A A
import json

def contrast_file(fileA_path, fileB_path, num_split):
    result = []
    
    for i in range(num_split):
        with open('fileA_%s.json' % i,'w') as f:
            json.dump({}, f, ensure_ascii=False)     # ensure_ascii=False可以使得中文正常输出

    with open(fileA_path) as f:
        for line in f:
            line = line.replace('\n', '')
            hash_line = hash(line)
            ind = hash_line % num_split
            with open('fileA_%s.json' % ind,'r+') as fileA_ind:
                dict_fileA_ind = json.load(fileA_ind)
            with open('fileA_%s.json' % ind,'w+') as fileA_ind:
                if hash_line in dict_fileA_ind:
                    dict_fileA_ind[hash_line].append(line)
                else:
                    dict_fileA_ind[hash_line] = [line]
                json.dump(dict_fileA_ind, fileA_ind, ensure_ascii=False)
                
    with open(fileB_path) as f:
        for line in f:
            line = line.replace('\n', '')
            hash_line = hash(line)
            ind = hash_line % num_split
            with open('fileA_%s.json' % ind,'r+') as fileA_ind:
                dict_fileA_ind = json.load(fileA_ind)
                hash_line = str(hash_line)
                if hash_line in dict_fileA_ind:
                    list_hash_line = dict_fileA_ind[hash_line]
                    if line in list_hash_line:
                        result.append(line)
    
    return result
对于固定格式的字符串,如电话号码、QQ号等:Trie树
  • 遍历文件 A A A的字符串依次写入Trie树
  • 遍历文件 B B B判断字符串是否存在于Trie树

对于重复字符串较多的情况,可先用set()去重

参考文章:
· 《位图(BitMap)》
· 《布隆过滤器(Bloom Filter)》


10亿个未排序的正整数,其中只有1个数重复出现过,要在 O ( n ) O(n) O(n)的时间里面找出这个数,且内存要尽可能少

方法一:BitMap(适用于最大的正整数较小的情况)

当最大的正整数较小时,可以使用BitMap,即将10亿个正整数转换到一个BitMap中,并将其所有位设置为0,然后开始遍历这10亿个整数,每遍历一个,则对应到位图中相应的位置设置1。当内存为1G时,1G=1024MB=1048576KB=1073741824B=8589934592bits。换言之当10亿个正整数中的最大数小于8589934592时,使用BitMap理论上使用的内存小于1GB。在遍历整个数组插入BItMap的过程中,若出现任意一个正整数的位上已经为1,则该正整数为重复出现的数字,

方法二:字典树(前缀树、Trie树)

若最大的正整数不确定时,可以使用字典树。由于每个正整数的位数可能不同,所以在整数的末尾需要加上某个符号来表示结束,若在遍历数组构建字典树的时候,某个正整数的最后一位写入字典树后,节点上连接了一个结束符号,则说明改正整数重复出现过。

方法三:归并排序

若最大的正整数不确定且内存不足时,也可以考虑归并排序等外排序方法,但时间复杂度为 O ( n l o g n ) O(nlog n) O(nlogn),所以不推荐使用。

  • 建立为外排序所用的内存缓冲区,并根据它们的内存大小将输入文件划分为若干段,然后用某种有效的内排序方法对各段进行排序,这些经过排序的段叫做初始归并段或初始顺串,当它们生成后就被写到硬盘中
  • 仿照内排序中所介绍过的归并树模式,把第一个阶段生成的初始归并段加以归并,一趟趟地扩大归并段和减少归并段个数,直到最后归并成一个大归并段(有序文件)为止。

这种方法,需要1次读入输入文件,多次读入或输出中间文件。


猜你喜欢

转载自blog.csdn.net/hy592070616/article/details/127347393