妩魅 發表於 2019-8-6 14:31:00

JavaScript数据结构——字典和散列表的实现

<p>  在前一篇文章中,我们介绍了如何在JavaScript中实现集合。字典和集合的主要区别就在于,集合中数据是以<strong>[值,值]</strong>的形式保存的,我们只关心值本身;而在字典和散列表中数据是以<strong>[键,值]</strong>的形式保存的,键不能重复,我们不仅关心键,也关心键所对应的值。</p>
<p>  我们也可以把字典称之为映射表。由于字典和集合很相似,我们可以在前一篇文章中的集合类Set的基础上来实现我们的字典类Dictionary。与Set类相似,ES6的原生Map类已经实现了字典的全部功能,稍后我们会介绍它的用法。</p>
<p>  下面是我们的Dictionary字典类的实现代码:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">class Dictionary {
    constructor () {
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.items =<span style="color: rgba(0, 0, 0, 1)"> {};
    }

    set (key, value) { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 向字典中添加或修改元素</span>
      <span style="color: rgba(0, 0, 255, 1)">this</span>.items =<span style="color: rgba(0, 0, 0, 1)"> value;
    }

    get (key) { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 通过键值查找字典中的值</span>
      <span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.items;
    }

    </span><span style="color: rgba(0, 0, 255, 1)">delete</span> (key) { <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 通过使用键值来从字典中删除对应的元素</span>
      <span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.has(key)) {
            </span><span style="color: rgba(0, 0, 255, 1)">delete</span> <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.items;
            </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
      }
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
    }

    has (key) { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 判断给定的键值是否存在于字典中</span>
      <span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.items.hasOwnProperty(key);
    }

    clear() { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 清空字典内容</span>
      <span style="color: rgba(0, 0, 255, 1)">this</span>.items =<span style="color: rgba(0, 0, 0, 1)"> {};
    }

    size () { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 返回字典中所有元素的数量</span>
      <span style="color: rgba(0, 0, 255, 1)">return</span> Object.keys(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.items).length;
    }

    keys () { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 返回字典中所有的键值</span>
      <span style="color: rgba(0, 0, 255, 1)">return</span> Object.keys(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.items);
    }

    values () { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 返回字典中所有的值</span>
      <span style="color: rgba(0, 0, 255, 1)">return</span> Object.values(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.items);
    }

    getItems () { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 返回字典中的所有元素</span>
      <span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.items;
    }
}</span></pre>
</div>
<p>  与Set类很相似,只是把其中value的部分替换成了key。我们来看看一些测试用例:</p>
<div class="cnblogs_code">
<pre>let Dictionary = require('./dictionary'<span style="color: rgba(0, 0, 0, 1)">);

let dictionary </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Dictionary();
dictionary.set(</span>'Gandalf', 'gandalf@email.com'<span style="color: rgba(0, 0, 0, 1)">);
dictionary.set(</span>'John', 'john@email.com'<span style="color: rgba(0, 0, 0, 1)">);
dictionary.set(</span>'Tyrion', 'tyrion@email.com'<span style="color: rgba(0, 0, 0, 1)">);
console.log(dictionary.has(</span>'Gandalf')); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> true</span>
console.log(dictionary.size()); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 3</span>
console.log(dictionary.keys()); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> [ 'Gandalf', 'John', 'Tyrion' ]</span>
console.log(dictionary.values()); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> [ 'gandalf@email.com', 'john@email.com', 'tyrion@email.com' ]</span>
console.log(dictionary.get('Tyrion')); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> tyrion@email.com</span>
<span style="color: rgba(0, 0, 0, 1)">
dictionary.</span><span style="color: rgba(0, 0, 255, 1)">delete</span>('John'<span style="color: rgba(0, 0, 0, 1)">);
console.log(dictionary.keys()); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> [ 'Gandalf', 'Tyrion' ]</span>
console.log(dictionary.values()); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> [ 'gandalf@email.com', 'tyrion@email.com' ]</span>
console.log(dictionary.getItems()); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> { Gandalf: 'gandalf@email.com', Tyrion: 'tyrion@email.com' }</span></pre>
</div>
<p>  相应地,下面是使用ES6的原生Map类的测试结果:</p>
<div class="cnblogs_code">
<pre>let dictionary = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Map();
dictionary.set(</span>'Gandalf', 'gandalf@email.com'<span style="color: rgba(0, 0, 0, 1)">);
dictionary.set(</span>'John', 'john@email.com'<span style="color: rgba(0, 0, 0, 1)">);
dictionary.set(</span>'Tyrion', 'tyrion@email.com'<span style="color: rgba(0, 0, 0, 1)">);
console.log(dictionary.has(</span>'Gandalf')); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> true</span>
console.log(dictionary.size); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 3</span>
console.log(dictionary.keys()); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> { 'Gandalf', 'John', 'Tyrion' }</span>
console.log(dictionary.values()); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> { 'gandalf@email.com', 'john@email.com', 'tyrion@email.com' }</span>
console.log(dictionary.get('Tyrion')); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> tyrion@email.com</span>
<span style="color: rgba(0, 0, 0, 1)">
dictionary.</span><span style="color: rgba(0, 0, 255, 1)">delete</span>('John'<span style="color: rgba(0, 0, 0, 1)">);
console.log(dictionary.keys()); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> { 'Gandalf', 'Tyrion' }</span>
console.log(dictionary.values()); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> { 'gandalf@email.com', 'tyrion@email.com' }</span>
console.log(dictionary.entries()); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> { [ Gandalf: 'gandalf@email.com' ], [ Tyrion: 'tyrion@email.com' ] }</span></pre>
</div>
<p>  和前面我们自定义的Dictionary类稍微有一点不同,values()方法和keys()方法返回的不是一个数组,而是Iterator迭代器。另一个就是这里的size是一个属性而不是方法,然后就是Map类没有getItems()方法,取而代之的是entries()方法,它返回的也是一个Iterator。有关Map类的详细详细介绍可以查看这里。</p>
<p>  在ES6中,除了原生的Set和Map类外,还有它们的弱化版本,分别是WeakSet和WeakMap,我们在《JavaScript数据结构——栈的实现与应用》一文中已经见过WeakMap的使用了。Map和Set与它们各自的弱化版本之间的主要区别是:</p>
<ul>
<li>WeakSet或WeakMap类没有entries、keys和values等迭代器方法,只能通过get和set方法访问和设置其中的值。这也是为什么我们在《JavaScript数据结构——栈的实现与应用》一文中要使用WeakMap类来定义类的私有属性的原因。</li>
<li>只能用对应作为键值,或者说其中的内容只能是对象,而不能是数字、字符串、布尔值等基本数据类型。</li>
</ul>
<p>  弱化的Map和Set类主要是为了提供JavaScript代码的性能。</p>
<h3>散列表</h3>
<p>  散列表(或者叫哈希表),是一种改进的dictionary,它将key通过一个固定的算法(散列函数或哈希函数)得出一个数字,然后将dictionary中key所对应的value存放到这个数字所对应的数组下标所包含的存储空间中。在原始的dictionary中,如果要查找某个key所对应的value,我们需要遍历整个字典。为了提高查询的效率,我们将key对应的value保存到数组里,只要key不变,使用相同的散列函数计算出来的数字就是固定的,于是就可以很快地在数组中找到你想要查找的value。下面是散列表的数据结构示意图:</p>
<p><img src="https://img2018.cnblogs.com/blog/51946/201908/51946-20190805160412699-1256528451.png" alt="" style="display: block; margin-left: auto; margin-right: auto"></p>
<p>  下面是我们散列函数loseloseHashCode()的实现代码:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">loseloseHashCode (key) {
    let hash </span>= 0<span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 0, 255, 1)">for</span> (let i = 0; i &lt; key.length; i++<span style="color: rgba(0, 0, 0, 1)">) {
      hash </span>+=<span style="color: rgba(0, 0, 0, 1)"> key.charCodeAt(i);
    }
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> hash % 37<span style="color: rgba(0, 0, 0, 1)">;
}</span></pre>
</div>
<p>  这个散列函数的实现很简单,我们将传入的key中的每一个字符使用charCodeAt()函数(有关该函数的详细内容可以查看这里)将其转换成ASCII码,然后将这些ASCII码相加,最后用37求余,得到一个数字,这个数字就是这个key所对应的hash值。接下来要做的就是将value存放到hash值所对应的数组的存储空间内。下面是我们的HashTable类的主要实现代码:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">class HashTable {
    constructor () {
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.table =<span style="color: rgba(0, 0, 0, 1)"> [];
    }

    loseloseHashCode (key) { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 散列函数</span>
      let hash = 0<span style="color: rgba(0, 0, 0, 1)">;
      </span><span style="color: rgba(0, 0, 255, 1)">for</span> (let i = 0; i &lt; key.length; i++<span style="color: rgba(0, 0, 0, 1)">) {
            hash </span>+=<span style="color: rgba(0, 0, 0, 1)"> key.charCodeAt(i);
      }
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> hash % 37<span style="color: rgba(0, 0, 0, 1)">;
    }

    put (key, value) { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 将键值对存放到哈希表中</span>
      let position = <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.loseloseHashCode(key);
      console.log(`${position} </span>-<span style="color: rgba(0, 0, 0, 1)"> ${key}`);
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.table =<span style="color: rgba(0, 0, 0, 1)"> value;
    }

    get (key) { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 通过key查找哈希表中的值</span>
      <span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">this</span>.table[<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.loseloseHashCode(key)];
    }

    remove (key) { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 通过key从哈希表中删除对应的值</span>
      <span style="color: rgba(0, 0, 255, 1)">this</span>.table[<span style="color: rgba(0, 0, 255, 1)">this</span>.loseloseHashCode(key)] =<span style="color: rgba(0, 0, 0, 1)"> undefined;
    }

    isEmpty () { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 判断哈希表是否为空</span>
      <span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">this</span>.size() === 0<span style="color: rgba(0, 0, 0, 1)">;
    }

    size () { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 返回哈希表的长度</span>
      let count = 0<span style="color: rgba(0, 0, 0, 1)">;
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.table.forEach(item =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (item !== undefined) count++<span style="color: rgba(0, 0, 0, 1)">;
      });
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> count;
    }

    clear () { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 清空哈希表</span>
      <span style="color: rgba(0, 0, 255, 1)">this</span>.table =<span style="color: rgba(0, 0, 0, 1)"> [];
    }
}</span></pre>
</div>
<p>  测试一下上面的这些方法:</p>
<div class="cnblogs_code">
<pre>let HashTable = require('./hashtable'<span style="color: rgba(0, 0, 0, 1)">);

let hash </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> HashTable();
hash.put(</span>'Gandalf', 'gandalf@email.com'); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 19 - Gandalf</span>
hash.put('John', 'john@email.com'); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 29 - John</span>
hash.put('Tyrion', 'tyrion@email.com'); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 16 - Tyrion</span>
<span style="color: rgba(0, 0, 0, 1)">
console.log(hash.isEmpty()); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> false</span>
console.log(hash.size()); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 3</span>
console.log(hash.get('Gandalf')); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> gandalf@email.com</span>
console.log(hash.get('Loiane')); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> undefined</span>
<span style="color: rgba(0, 0, 0, 1)">
hash.remove(</span>'Gandalf'<span style="color: rgba(0, 0, 0, 1)">);
console.log(hash.get(</span>'Gandalf')); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> undefined</span>
<span style="color: rgba(0, 0, 0, 1)">hash.clear();
console.log(hash.size()); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 0</span>
console.log(hash.isEmpty()); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> true</span></pre>
</div>
<p>  为了方便查看hash值和value的对应关系,我们在put()方法中加入了一行console.log(),用来打印key的hash值和value之间的对应关系。可以看到,测试的结果和前面我们给出的示意图是一致的。</p>
<p>  散列集合的实现和散列表类似,只不过在散列集合中不再使用键值对,而是只有值没有键。这个我们在前面介绍集合和字典的时候已经讲过了,这里不再赘述。</p>
<p>  细心的同学可能已经发现了,这里我们提供的散列函数可能过于简单,以致于我们无法保证通过散列函数计算出来的hash值一定是唯一的。换句话说,传入不同的key值,我们有可能会得到相同的hash值。尝试一下下面这些keys:</p>
<div class="cnblogs_code">
<pre>let hash = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> HashTable();
hash.put(</span>'Gandalf', 'gandalf@email.com'<span style="color: rgba(0, 0, 0, 1)">);
hash.put(</span>'John', 'john@email.com'<span style="color: rgba(0, 0, 0, 1)">);
hash.put(</span>'Tyrion', 'tyrion@email.com'<span style="color: rgba(0, 0, 0, 1)">);
hash.put(</span>'Aaron', 'aaron@email.com'<span style="color: rgba(0, 0, 0, 1)">);
hash.put(</span>'Donnie', 'donnie@email.com'<span style="color: rgba(0, 0, 0, 1)">);
hash.put(</span>'Ana', 'ana@email.com'<span style="color: rgba(0, 0, 0, 1)">);
hash.put(</span>'Jamie', 'jamie@email.com'<span style="color: rgba(0, 0, 0, 1)">);
hash.put(</span>'Sue', 'sue@email.com'<span style="color: rgba(0, 0, 0, 1)">);
hash.put(</span>'Mindy', 'mindy@email.com'<span style="color: rgba(0, 0, 0, 1)">);
hash.put(</span>'Paul', 'paul@email.com'<span style="color: rgba(0, 0, 0, 1)">);
hash.put(</span>'Nathan', 'nathan@email.com');</pre>
</div>
<p><img src="https://img2018.cnblogs.com/blog/51946/201908/51946-20190805163002860-1675025615.png" alt="" style="display: block; margin-left: auto; margin-right: auto"></p>
<p>  从结果中可以看到,尽管有些keys不同,但是通过我们提供的散列函数居然得到了相同的hash值,这显然违背了我们的设计原则。在哈希表中,这个叫做散列冲突,为了得到一个可靠的哈希表,我们必须尽可能地避免散列冲突。那如何避免这种冲突呢?这里介绍两种解决冲突的方法:分离链接和线性探查。</p>
<h4>分离链接</h4>
<p>&nbsp;  所谓分离链接,就是将原本存储在哈希表中的值改成链表,这样在哈希表的同一个位置上,就可以存储多个不同的值。链表中的每一个元素,同时存储了key和value。示意图如下:</p>
<p><img src="https://img2018.cnblogs.com/blog/51946/201908/51946-20190806100826605-514054718.png" alt="" width="749" height="572" style="display: block; margin-left: auto; margin-right: auto"></p>
<p>  这样,当不同的key通过散列函数计算出相同的hash值时,我们只需要找到数组中对应的位置,然后往其中的链表添加新的节点即可,从而有效地避免了散列冲突。为了实现这种数据结构,我们需要定义一个新的辅助类ValuePair,它的内容如下:</p>
<div class="cnblogs_code">
<pre>let ValuePair = <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> (key, value) {
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.key =<span style="color: rgba(0, 0, 0, 1)"> key;
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.value =<span style="color: rgba(0, 0, 0, 1)"> value;

</span><span style="color: rgba(0, 0, 255, 1)">this</span>.toString = <span style="color: rgba(0, 0, 255, 1)">function</span> () { <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 提供toString()方法以方便我们测试</span>
      <span style="color: rgba(0, 0, 255, 1)">return</span> `[${<span style="color: rgba(0, 0, 255, 1)">this</span>.key} - ${<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.value}]`;
}
};</span></pre>
</div>
<p>  ValuePair类具有两个属性,key和value,用来保存我们要存入到散列表中的元素的键值对。toString()方法在这里不是必须的,该方法是为了后面我们方便测试。</p>
<p>  新的采用了分离链接的HashTableSeparateChaining类可以继承自前面的HashTable类,完整的代码如下:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">class HashTableSeparateChaining extends HashTable {
    constructor () {
      super();
    }

    put (key, value) {
      let position </span>= <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.loseloseHashCode(key);

      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">this</span>.table ===<span style="color: rgba(0, 0, 0, 1)"> undefined) {
            </span><span style="color: rgba(0, 0, 255, 1)">this</span>.table = <span style="color: rgba(0, 0, 255, 1)">new</span> LinkedList(); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 单向链表,需要引入LinkedList类</span>
<span style="color: rgba(0, 0, 0, 1)">      }
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.table.append(<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ValuePair(key, value));
    }

    get (key) {
      let position </span>= <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.loseloseHashCode(key);

      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">this</span>.table !==<span style="color: rgba(0, 0, 0, 1)"> undefined) {
            let current </span>= <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.table.getHead();
            </span><span style="color: rgba(0, 0, 255, 1)">while</span><span style="color: rgba(0, 0, 0, 1)"> (current) {
                </span><span style="color: rgba(0, 0, 255, 1)">if</span> (current.element.key === key) <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> current.element.value;
                current </span>=<span style="color: rgba(0, 0, 0, 1)"> current.next;
            }
      }
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> undefined;
    }

    remove (key) {
      let position </span>= <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.loseloseHashCode(key);
      let hash </span>= <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.table;

      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (hash !==<span style="color: rgba(0, 0, 0, 1)"> undefined) {
            let current </span>=<span style="color: rgba(0, 0, 0, 1)"> hash.getHead();
            </span><span style="color: rgba(0, 0, 255, 1)">while</span><span style="color: rgba(0, 0, 0, 1)"> (current) {
                </span><span style="color: rgba(0, 0, 255, 1)">if</span> (current.element.key ===<span style="color: rgba(0, 0, 0, 1)"> key) {
                  hash.remove(current.element);
                  </span><span style="color: rgba(0, 0, 255, 1)">if</span> (hash.isEmpty()) <span style="color: rgba(0, 0, 255, 1)">this</span>.table =<span style="color: rgba(0, 0, 0, 1)"> undefined;
                  </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
                }
                current </span>=<span style="color: rgba(0, 0, 0, 1)"> current.next;
            }
      }

      </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
    }

    size () {
      let count </span>= 0<span style="color: rgba(0, 0, 0, 1)">;
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.table.forEach(item =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (item !== undefined) count +=<span style="color: rgba(0, 0, 0, 1)"> item.size();
      });
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> count;
    }

    toString() {
      let objString </span>= ""<span style="color: rgba(0, 0, 0, 1)">;
      </span><span style="color: rgba(0, 0, 255, 1)">for</span> (let i = 0; i &lt; <span style="color: rgba(0, 0, 255, 1)">this</span>.table.length; i++<span style="color: rgba(0, 0, 0, 1)">) {
            let ci </span>= <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.table;
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (ci === undefined) <span style="color: rgba(0, 0, 255, 1)">continue</span><span style="color: rgba(0, 0, 0, 1)">;

            objString </span>+=<span style="color: rgba(0, 0, 0, 1)"> `${i}: `;
            let current </span>=<span style="color: rgba(0, 0, 0, 1)"> ci.getHead();
            </span><span style="color: rgba(0, 0, 255, 1)">while</span><span style="color: rgba(0, 0, 0, 1)"> (current) {
                objString </span>+=<span style="color: rgba(0, 0, 0, 1)"> current.element.toString();
                current </span>=<span style="color: rgba(0, 0, 0, 1)"> current.next;
                </span><span style="color: rgba(0, 0, 255, 1)">if</span> (current) objString += ', '<span style="color: rgba(0, 0, 0, 1)">;
            }
            objString </span>+= '\r\n'<span style="color: rgba(0, 0, 0, 1)">;
      }
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> objString;
    }
}</span></pre>
</div>
<p>  其中的LinkedList类为单向链表,具体内容可以查看《JavaScript数据结构——链表的实现与应用》。注意,现在hash数组中的每一个元素都是一个单向链表,单向链表的所有操作我们可以借助于LinkedList类来完成。我们重写了size()方法,因为现在要计算的是数组中所有链表的长度总和。</p>
<p>  下面是HashTableSeparateChaining类的测试用例及结果:</p>
<div class="cnblogs_code">
<pre>let hash = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> HashTableSeparateChaining();

hash.put(</span>'Gandalf', 'gandalf@email.com'<span style="color: rgba(0, 0, 0, 1)">);
hash.put(</span>'John', 'john@email.com'<span style="color: rgba(0, 0, 0, 1)">);
hash.put(</span>'Tyrion', 'tyrion@email.com'<span style="color: rgba(0, 0, 0, 1)">);
hash.put(</span>'Aaron', 'aaron@email.com'<span style="color: rgba(0, 0, 0, 1)">);
hash.put(</span>'Donnie', 'donnie@email.com'<span style="color: rgba(0, 0, 0, 1)">);
hash.put(</span>'Ana', 'ana@email.com'<span style="color: rgba(0, 0, 0, 1)">);
hash.put(</span>'Jamie', 'jamie@email.com'<span style="color: rgba(0, 0, 0, 1)">);
hash.put(</span>'Sue', 'sue@email.com'<span style="color: rgba(0, 0, 0, 1)">);
hash.put(</span>'Mindy', 'mindy@email.com'<span style="color: rgba(0, 0, 0, 1)">);
hash.put(</span>'Paul', 'paul@email.com'<span style="color: rgba(0, 0, 0, 1)">);
hash.put(</span>'Nathan', 'nathan@email.com'<span style="color: rgba(0, 0, 0, 1)">);

console.log(hash.toString());
console.log(`size: ${hash.size()}`);
console.log(hash.get(</span>'John'<span style="color: rgba(0, 0, 0, 1)">));

console.log(hash.remove(</span>'Ana'<span style="color: rgba(0, 0, 0, 1)">));
console.log(hash.remove(</span>'John'<span style="color: rgba(0, 0, 0, 1)">));
console.log(hash.toString());</span></pre>
</div>
<p><img src="https://img2018.cnblogs.com/blog/51946/201908/51946-20190806122112179-145383962.png" alt="" width="500" height="308" style="display: block; margin-left: auto; margin-right: auto"></p>
<p>  可以看到,结果和上面示意图上给出的是一致的,size()、remove()和get()方法的执行结果也符合预期。</p>
<h4>线性探查</h4>
<p>  避免散列冲突的另一种方法是线性探查。当向哈希数组中添加某一个新元素时,如果该位置上已经有数据了,就继续尝试下一个位置,直到对应的位置上没有数据时,就在该位置上添加数据。我们将上面的例子改成线性探查的方式,存储结果如下图所示:</p>
<p><img src="https://img2018.cnblogs.com/blog/51946/201908/51946-20190806123149359-2054826872.png" alt="" width="442" height="649" style="display: block; margin-left: auto; margin-right: auto"></p>
<p>  现在我们不需要单向链表LinkedList类了,但是ValuePair类仍然是需要的。同样的,我们的HashTableLinearProbing类继承自HashTable类,完整的代码如下:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">class HashTableLinearProbing extends HashTable {
    constructor () {
      super();
    }

    put (key, value) {
      let position </span>= <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.loseloseHashCode(key);

      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">this</span>.table ===<span style="color: rgba(0, 0, 0, 1)"> undefined) {
            </span><span style="color: rgba(0, 0, 255, 1)">this</span>.table = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ValuePair(key, value);
      }
      </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
            let index </span>= position + 1<span style="color: rgba(0, 0, 0, 1)">;
            </span><span style="color: rgba(0, 0, 255, 1)">while</span> (<span style="color: rgba(0, 0, 255, 1)">this</span>.table !==<span style="color: rgba(0, 0, 0, 1)"> undefined) {
                index </span>++<span style="color: rgba(0, 0, 0, 1)">;
            }
            </span><span style="color: rgba(0, 0, 255, 1)">this</span>.table = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ValuePair(key, value);
      }
    }

    get (key) {
      let position </span>= <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.loseloseHashCode(key);

      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">this</span>.table !==<span style="color: rgba(0, 0, 0, 1)"> undefined) {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">this</span>.table.key === key) <span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.table.value;
            let index </span>= position + 1<span style="color: rgba(0, 0, 0, 1)">;
            </span><span style="color: rgba(0, 0, 255, 1)">while</span> (<span style="color: rgba(0, 0, 255, 1)">this</span>.table !== undefined &amp;&amp; <span style="color: rgba(0, 0, 255, 1)">this</span>.table.key !==<span style="color: rgba(0, 0, 0, 1)"> key) {
                index </span>++<span style="color: rgba(0, 0, 0, 1)">;
            }
            </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.table.value;
      }
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> undefined;
    }

    remove (key) {
      let position </span>= <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.loseloseHashCode(key);

      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">this</span>.table !==<span style="color: rgba(0, 0, 0, 1)"> undefined) {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">this</span>.table.key ===<span style="color: rgba(0, 0, 0, 1)"> key) {
                </span><span style="color: rgba(0, 0, 255, 1)">this</span>.table =<span style="color: rgba(0, 0, 0, 1)"> undefined;
                </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
            }
            let index </span>= position + 1<span style="color: rgba(0, 0, 0, 1)">;
            </span><span style="color: rgba(0, 0, 255, 1)">while</span> (<span style="color: rgba(0, 0, 255, 1)">this</span>.table !== undefined &amp;&amp; <span style="color: rgba(0, 0, 255, 1)">this</span>.table.key !==<span style="color: rgba(0, 0, 0, 1)"> key) {
                index </span>++<span style="color: rgba(0, 0, 0, 1)">;
            }
            </span><span style="color: rgba(0, 0, 255, 1)">this</span>.table =<span style="color: rgba(0, 0, 0, 1)"> undefined;
            </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
      }
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
    }

    toString() {
      let objString </span>= ""<span style="color: rgba(0, 0, 0, 1)">;
      </span><span style="color: rgba(0, 0, 255, 1)">for</span> (let i = 0; i &lt; <span style="color: rgba(0, 0, 255, 1)">this</span>.table.length; i++<span style="color: rgba(0, 0, 0, 1)">) {
            let ci </span>= <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.table;
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (ci === undefined) <span style="color: rgba(0, 0, 255, 1)">continue</span><span style="color: rgba(0, 0, 0, 1)">;

            objString </span>+=<span style="color: rgba(0, 0, 0, 1)"> `${i}: ${ci}\r\n`;
      }
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> objString;
    }
}</span></pre>
</div>
<p>  使用上面和HashTableSeparateChaining类相同的测试用例,我们来看看测试结果:</p>
<p><img src="https://img2018.cnblogs.com/blog/51946/201908/51946-20190806123655318-138175311.png" alt="" width="310" height="447" style="display: block; margin-left: auto; margin-right: auto"></p>
<p>  可以和HashTableSeparateChaining类的测试结果比较一下,多出来的位置6、14、17、33,正是HashTableSeparateChaining类中每一个链表的剩余部分。get()和remove()方法也能正常工作,我们不需要重写size()方法,和基类HashTable中一样,hash数组中每一个位置只保存了一个元素。另一个要注意的地方是,由于JavaScript中定义数组时不需要提前给出数组的长度,因此我们可以很容易地利用JavaScript语言的这一特性来实现线性探查。在某些编程语言中,数组的定义是必须明确给出长度的,这时我们就需要重新考虑我们的HashLinearProbing类的实现了。</p>
<p>  loseloseHashCode()散列函数并不是一个表现良好的散列函数,正如你所看到的,它会很轻易地产生散列冲突。一个表现良好的散列函数必须能够尽可能低地减少散列冲突,并提高性能。我们可以在网上找一些不同的散列函数的实现方法,下面是一个比loseloseHashCode()更好的散列函数djb2HashCode():</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">djb2HashCode (key) {
    let hash </span>= 5381<span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 0, 255, 1)">for</span> (let i = 0; i &lt; key.length; i++<span style="color: rgba(0, 0, 0, 1)">) {
      hash </span>= hash * 33 +<span style="color: rgba(0, 0, 0, 1)"> key.charCodeAt(i);
    }
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> hash % 1013<span style="color: rgba(0, 0, 0, 1)">;
}</span></pre>
</div>
<p>  我们用相同的测试用例来测试dj2HashCode(),下面是测试结果:</p>
<p><img src="https://img2018.cnblogs.com/blog/51946/201908/51946-20190806142818019-375143548.png" alt="" style="display: block; margin-left: auto; margin-right: auto"></p>
<p>  这次没有冲突!然而这并不是最好的散列函数,但它是社区最推崇的散列函数之一。</p>
<p>  下一章我们将介绍如何用JavaScript来实现树。</p><br><br>
来源:https://www.cnblogs.com/jaxu/p/11302315.html
頁: [1]
查看完整版本: JavaScript数据结构——字典和散列表的实现