zoukankan      html  css  js  c++  java
  • 散列函数之单散列算法解决冲突问题

    1. 问题

    问题同《简单散列函数算法

    设有10个非负整数,用不多于20个的储存单元来存放,如何存放这10个数,使得搜索其中的某一个数时,在储存单元中查找的次数最少?

    问题类似于,有10个带号码的球,放到编号为{0, 1, 2, …, 19}共20个盒子中,每个盒子最多放一个,问如何放,使能够用最少的次数打开盒子,知道任一个球所在的盒子编号?

    2. 分析

    简单散列函数算法》中,已经分析得出,只要能解决冲突问题,就能将查找时间降为常量范围内。

    思路:当一个数发生冲突时,再找一个没有被占用的空盒子来放这个球

    哈哈,思路相当简单,好像也很有道理的样子,关键问题是:如何知道这个没占用的空盒子和球号的对应关系?

    这里使用《初等数论及其应用》中第5章所介绍的方法,该书中对方法的描述有的地方进行了简略,也没讲如何查找,我这里进行补齐,并写了份python的代码,便于理解和应用

    3. 单散列函数解决冲突问题

    3.1 方法的思路:

    设盒子数量是m, 球的总数是n

    当一个数发生冲突时,则看这个冲突的盒子(k)的下一个盒子(k+1)是否是空的,如果是,则放入,如果不是,则继续看下下个(k+2),一直加到m,大于m还没找到,则到{0, 1, …, k}即k前面的盒子中去找空盒子:

    如假设有球{0, 1, 30},仍设m = 10,当0和1分别放入对应的0, 1号盒子,当要放入30时,f(30) = 30 % 10 = 0,0号盒子被占用,冲突,再看下一个盒子1号,发现1号也被占用,再看下一个2号盒子,发现是空的,放入即可

    当又有一个球为40时呢,类似,会发现0, 1, 2号盒子都被占用,这时需要放入3号盒子

    那么这时又来一个真正的3号球呢,我们会发现3号盒子已经被占用,所以只能放到4号盒子中去了

    最后{0, 1, 30, 40, 3}放入的情况如下:

    盒子1编号 0 1 2 3 4 5 6 7 8 9
    球号 0 1 30 40 3          

    注意:这里由于球号的排列顺序不同,放的位置并不一致,如球号如此排列{0, 1, 3, 30, 40},则3号球就会在3号位置了

    ∵ 而m >= n

    ∴ 对于任一个x,总可以找到一个空的盒子给其放球

    3.2 数学表达式

    设 h0(k) ≡ k (mod m), k = 球号, m = 盒子数量,这里”≡ ”表示同余,不是相等,h(k)即为除m的余数

    hj(k) ≡ h0(k) + j,0<= j < m,  hj(k) 表示发生 j 次冲突后,球所放入的盒子编号

    ∴ hj+1(k) ≡ h0(k) + (j + 1) ≡ hj(k) + 1

    即表示,当在hj(k)的位置发生冲突后,即再查看其下一个盒子是否为空

    ∵当k = m - 1时, k ≡ 0 (mod m),根据模的算法

    ∴其下一个位置 k + 1 = 0,即表示回到0号盒子开始查找空盒子

    3.3 如何查找球k所在的盒子

    方法和放球时一样的,先查找h0(k) ≡ k (mod m), 如果相等,则ok

    如果不相等,则说明可能在下一个盒子中,按3.2的公式依次递归,最终会找到对应的盒子

    3.4 最坏复杂度

    假设有9个球已经占据了{0, 1, 2, …, 8}前8个盒子,最后一个球k9 ≡ k0 (mod m),则需要从第0个位置依次 +1 加到9位置,才能找到不冲突的盒子,也就是说最坏要打开10个盒子才能找到,最坏复杂度 = n,n为球的数量,

    哈哈,看起来费了半天劲还不如《简单散列函数算法》中的方法2.2。

    但好处也很明显,《简单散列函数算法》中的方法2.2需要先对在第2组盒子中的球号进行排序,如果新增加一个球,就要再排一次

    3.5 Python code及测试结果

    #mod = m, h(k) = n % m, hj(k) = (h(k) + j) % m
    def SingleHash(numList):
        if len(numList) > m:
            print "num list len is :", len(numList), "is big than mod :", m;
            return None;
    
        hashList = [None] * m;
        for k in numList:
            for j in range(m):
                hj_k = (k + j) % m;
                if None == hashList[hj_k]:
                    hashList[hj_k] = k;
                    break;
            else:
                print "num list is too big, hash list is overflow";
                return None;
    
        return hashList;
    
    def CheckSingleHash(numList, hashList):
        if None == hashList:
            return;
    
        for k in numList:
            for j in range(m):
                hj_k = (k + j) % m;
                #check the key is equal with the one in hash list, if not check the next one
                if hashList[hj_k] == k:
                    if j > 0:
                        print k, "find", j+1, "times";
                    break;
                else:
                    print k, "conflict with", hashList[hj_k]; 
            else:
                print k, "is not find...";
                return False;
    
        return True;

    测试时,设置了m = 19

    测试数列为: numList = [0, 1, 2, 7, 9, 15, 19, 20, 77, 38],为了测试冲突,故意设置了一些冲突数,为了减少无用的输出,对于没有冲突的就不打出了,测试结果如下:

    image

    可以看出,38由于多次冲突,需要查找7次才能找到

    4. 散列算法冲突问题

    不论是《简单散列函数算法》中的散列算法还是单散列算法,如果没有冲突的情况下,都只要一次就能找到球所在的盒子,所以如果算法冲突的概率低,那么平均的时间复杂度也是很是越来越接近常量的。

    4.1 简单散列算法冲突的概率

    简单散列函数算法》中的散列算法只要一个球k模m(k%m)已经在盒子中了,就一定会产生冲突,设k已在第一组盒子中,则对所有的f(x) = k + im, (i ∈ {0, 1, 2, ...}),都会产生冲突,冲突的概率是很高的。

    设最大的球号为s, 则共有(s/m)个满足f(x) = k + im的球号(当s很大时,可以忽略除不尽的部分)

    则简单散列函数算法中第一个为满足f(x) = k + im的球的概率 :  image

    设共有n个球,恰有2个球满足f(x)时,第2个球的概率:image,当s很大时,1可以忽略,则约为 (n-1)/m

    故刚好取2个球满足f(x)时的概率 = (n2-1)/m2

    类似的,恰有3个球满足f(x)时的概率 = n(n-1)(n-2) / m3

    可以看出,当n和m接近时,冲突的概率越来越接近100%,所以一定要使m > n,且大得越多越好

    回到我们问题中的设定,n = 10, m = 20, 则刚好2个球满足时,冲突的概率 = 25%

    刚好有3个球满足时,冲突的概率 = 9%

    总的冲突概率 > 34%,冲突概率是非常高的

    4.2 单散列算法冲突概率

    单散列算法中,由于第一次产生冲突时,设为hj(k),第2次产生冲突时则必须有一个hj(k) + 1 的球已经在盒子中,所以发生2次以上冲突的概率会有所降低,当然,这种情况下是需要查看2个盒子的。

    由4.1知,同时取出2个都满足f(x)时的概率 = (n2-1)/m2,则第3个球必须是f(x)或f(x)+1才会有冲突

    第3个球是f(x)的概率 = (n-2)/m

    第3个球是f(x) + 1的概率 = (n-2)/m,

    故发生冲突的概率 = 2n(n-1)(n-2) / m3

    其他大于3个球冲突的概率就不予计算比较了,单用此和简单散列算法恰有2个球的冲突比较:

    当n = 10, m = 20, 单散列算法冲突的概率 = 18%

    可以看出,单散列算法在此情况下冲突概率上是优于简单散列算法的。

  • 相关阅读:
    消息队列接口API(posix 接口和 system v接口)
    Ubuntu 安装 Eclipse C/C++开发环境
    Ubuntu下Eclipse搭建ARM开发环境
    Linux进程间通信——使用流套接字
    Linux进程间通信——使用数据报套接字
    Linux进程间通信——信号集函数
    Linux进程间通信——使用信号
    Linux进程间通信——使用匿名管道
    mappedBy的作用
    VS Code 配置 C/C++ 环境
  • 原文地址:https://www.cnblogs.com/organic/p/6262546.html
Copyright © 2011-2022 走看看