散列及碰撞处理

散列是一种常用的数据存储技术。散列使用的数据结构叫做散列表。在散列表上插入、删除、取用数据都非常快,但是对于查找来说效率很低。

散列表的长度是预先设定的,存储数据时,通过一个散列函数将键映射为一个数字,这个数字的长度为0到散列表的长度。编写散列函数前需要先确定散列表数组的长度,我们对数大小常见的限制是:数组长度应该是一个质数。数组长度的确定策略都是基于处理碰撞问题的。

1、散列函数

我们采取的散列方式为除留余数法,基于这个方法的散列函数可以更加均匀的分布随机的整数键。对于字符串类型的键,散列值可以设为每个字符的ASCII码值的和除以数组长度的余数。散列函数定义如下:

 function simpleHash(data) {
        var total = 0;
        for ( var i = 0; i < data.length; ++i) {
            total += data.charCodeAt(i);
        }
        return total % this.table.length;
    }

于是,HashTable类的构造函数如下:

    function HashTable() {
        this.table = new Array(137);this.simpleHash = simpleHash;
        this.showDistro = showDistro;
        this.put = put;
        //this.get=get;
    }
    function simpleHash(data) {
        var total = 0;
        for ( var i = 0; i < data.length; ++i) {
            total += data.charCodeAt(i);
        }
        return total % this.table.length;
    }
    function put(data) {
        var pos = this.simpleHash(data);
        this.table[pos] = data;
    }
    function showDistro() {
        for ( var i = 0; i < this.table.length; ++i) {
            if (this.table[i] != undefined) {
                document.write(i + ": " + this.table[i]);
                document.write("<br />");
            }
        }
    }

simpleHash()散列函数通过JavaScript的charCodeAt()函数,返回每个字符的ASCII码值,相加得到散列值,put()方法通过调用simpleHash()得到数组的索引,并将数据存储到该索引对应的位置。通过实验可以发现,这个散列函数散列的数据并不是均匀分布的,向数组的两端集中,并且更严重的问题是,容易引发碰撞!所以需要优化一下散列函数来避免碰撞。

为了避免碰撞:

1、首先要保证散列表中用来存储数据的数组大小是个质数。这个计算散列值时采取的取余运算有关。

2、有了合适的散列表大小后,接下来就需要有一个更好的散列函数。霍纳算法很好的解决了这个问题。新的散列函数在每次求值前都先乘以一个质数,这里建议使用一个较小的质数。

使用霍纳算法的散列函数:

function betterHash(data) {
        const H = 37;
        var total = 0;
        for ( var i = 0; i < data.length; ++i) {
            total += total*H + data.charCodeAt(i);
        }
        total = total % this.table.length;
     return parseInt(total);
    }

2、从散列表中存取数据

使用散列表来存储数据,我们需要重新定义put() 方法,使其可以同时接受键和数据作为参数,对键值散列后,将数据存储到散列表中:

function put(key, data) {
        var pos = this.betterHash(key);
        this.table[pos] = data;
    }

get() 方法同样需要对键值进行散列化,得到数据在散列表中存储的位置:

function get(key) {
        return this.table[this.betterHash(key)];
    }

3、碰撞处理

1)开链法

开链法是指,实现散列表的底层数组中,每个数组元素又是一个新的数组结构。

实现开链法的方法:在创建存储散列过的键值的数组时,通过调用一个函数创建一个空的新数组,将该数组赋值给散列表里的每个数组元素,创建一个二维数组。我们称这个数组为链。函数buildChains() 定义如下:

function buildChains() {
        for ( var i = 0; i < this.table.length; ++i) {
            this.table[i] = new Array();
        }
    }

 使用了开链法后,我们需要重新定义put()和get()方法。

put()方法先将键值散列,散列后的值对应数组中的一个位置,先查看该位置上数组的第一个单元格是否有值,如果有值则会搜索下一个位置,直到找到可以存放数据的单元格,并将数组存储进去,新的put()方法实现如下:

function put(key, data) {
        var pos = this.betterHash(key);
        var index = 0;
        if (this.table[pos][index] == undefined) {
            this.table[pos][index] = key;
            this.table[pos][index + 1] = data;
        } else {
            while (this.table[pos][index] != undefined) {
                ++index;
            }
            this.table[pos][index] = key;
            this.table[pos][index + 1] = data;
        }
    }

新的put()方法不同于之前的方法,之前的方法仅存放数据,而新的put()方法使用链中两个相同的单元格同时存放了散列后的键值和数据,第一个单元格存放键值,第二个单元格存放数据。这样便于在碰撞出现时,链中存放多条数据时,从散列表中取值。

get()方法先对键值散列,根据散列后的值找到散列表中相应的位置,查找该位置的链中是否存在这个键值,如果有,则把紧跟在键值后面的单元格的数据返回,没找到就返回undefined。新的get()方法实现如下:

function get(key) {
        var index = 0;
        var pos = this.betterHash(key);
        if(this.table[pos][index] == key) {
            console.log(this.table[pos][index + 1]);
            return;
        } else {
            while (this.table[pos][index] != key) {
                index += 2;
            }
            console.log(this.table[pos][index + 1]);
            return;
        }
        document.write('undefined');
        return;
    }

散列表现在使用的是多维数组存储数据,为了更好的显示使用了开链法后键值的分布,则需要对showDistro()方法进行如下修改:

function showDistro() {
        for ( var i = 0; i < this.table.length; ++i) {
            if (this.table[i][0] != undefined) {
                document.write(i + ": " + this.table[i]);
                document.write("<br />");
            }
        }
    }
2)线性探测法

线性探测法隶属于一种更一般化的散列技术:开放寻址散列。当发生碰撞时,线性探测法会检查散列表中下一个位置是否为空,如果为空,则将数据存储在该位置,如果不为空,则继续检查下一个位置,直到找到空位置为止。当存储数据的数组特别大时,选择线性探测法要比开链法好,有一个公式可以帮助我们来判断使用那种碰撞处理方法:如果数组的大小是待存储数据的1.5倍,使用开链法;如果数组的大小是待存储数据的两倍或两倍以上,则选择线性探测法。

下面用代码说明线性探测法的工作原理,重新定义put()和get()方法,在构造函数中新增加一个数组

this.values = [];

数组value和table并行工作,table用来存储键值,value用来存储对应的数据。put()方法定义如下:

function put(key, data) {
        var pos = this.betterHash(key);
        if (this.table[pos] == undefined) {
            this.table[pos] = key;
                this.values[pos] = data;
        } else {
            while (this.table[pos] != undefined) {
                pos++;
            }
            this.table[pos] = key;
            this.values[pos] = data;
        }
    }

get()方法先搜索散列后的键值在散列表中的位置,如果在table中找到key,则返回values中对应位置上的数据,如果没找到则循环搜索,直到找到了table中对应的键,或者table中对应的值为undefined时,表示键值和数据没有存储到散列表。get()方法如下:

function get(key) {
        var hash= -1;
        var hash= this.betterHash(key);
        if(hash > -1) {
            for (var i=hash; this.table[i] != undefined; i++) {
                        if (this.table[i] == key) {
                                return this.values[i];
                        }
                }
        }
        return undefined;
    }                            

 

转载于:https://www.cnblogs.com/shiningly/p/9281831.html

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我已经记录下来了,请依次回答您的问题: 1、消息认证码和消息检测码的区别在于:消息认证码(MAC)是一种带有密钥的算法,用于对消息进行加密和验证,以保证消息的完整性和真实性;而消息检测码(CRC)是一种不带密钥的算法,用于检测消息的传输错误,并纠正错误,以保证消息的准确性。两者的联系在于都是用于保证消息在传输过程中的完整性和正确性。 2、安全散列函数具有以下特性: - 输入长度任意,输出长度固定; - 输出值是不可预测的,即对于不同的输入值,输出值应该是不同的; - 输出值是唯一的,即对于同一个输入值,输出值应该是唯一的; - 不可逆,即无法从输出值推算出输入值; - 对于输入值的微小变化,输出值也应该有较大的变化。 碰撞是指不同的输入值经过散列函数处理后,得到相同的输出值。这种情况是不应该出现的,因为一旦发生碰撞,就会导致数据的完整性和真实性无法保证。 3、生日问题与散列函数的碰撞性有关系:生日问题是指在一群人中,两个人生日相同的概率有多大。同样地,在散列函数中,也存在碰撞的概率。如果散列函数的输出位数是n位,那么在输入2^(n/2)个不同的值,就会出现碰撞的概率达到50%。找到一个碰撞意味着散列函数存在缺陷,代价可能会非常高,因为攻击者可以通过构造相同散列值的两个输入值来进行攻击。 4、MD5算法要求对输入的消息进行填充,填充的方式是在消息末尾添加一个1和若干个0,使得填充后的消息长度满足对512取模后等于448。如果消息长度已经是448比特的倍数,还需要再添加一个块,这个块的长度为512比特,并且第一个比特为1,其余为0。因此,在这个例子中,需要填充64比特(即一个1和63个0),填充后消息长度为512比特 + 448比特 = 960比特。 5、SHA-1算法也要求对输入的消息进行填充,填充的方式是在消息末尾添加一个1和若干个0,使得填充后的消息长度满足对512取模后等于448。如果消息长度已经是448比特的倍数,还需要再添加一个块,这个块的长度为512比特,并且第一个比特为1,其余为0。在这个例子中,需要填充10个0,填充后消息长度为20928比特。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值