集合、字典和散列表都可以存储不重复的值。
在集合中,我们感兴趣的是每个值本身,并把它当作主要元素。在字典和散列表中,我们用 [键,值] 的形式来存储数据。
集合(Set 类):[值,值]对,是一组由无序且唯一(即不能重复)的项组成的。
字典(Map 类):[键,值]对,也称作映射,其中键名是用来查询特定元素的。
散列(HashTable类/HashMap 类):[键,值]对,是Dictionary类的一种散列表实现方式。散列函数的作用是给定一个键值,然后返回值在表中的地址。散列算法的作用是尽可能快地在数据结构中找到一个值。
在一些编程语言中,还有一种叫作散列集合的实现。散列集合由一个集合构成,但是插入、移除或获取元素时,使用的是散列函数。实现散列集合的不同之处在于,不再添加键值对,而是只插入值而没有键。和集合相似,散列集合只存储唯一的不重复的值。
--------------------------------------------------------------------------
(1)集合方法声明:
首先,使用对象来表示集合。
序号
|
方法
|
说明
|
1
|
add(value) | 向集合添加一个新的项 |
2
|
remove(value) | 从集合移除一个值 |
3
|
has(value)
|
如果值在结合中,返回 true,否则返回 false |
4
|
clear ( )
|
移除集合中的所有项 |
5
|
size ( )
|
返回集合所包含元素的数量。与数组的 length 属性类似。 |
6
|
values ( )
|
返回一个包含集合中所有值的数组。
|
集合的实现:
1 function Set() { 2 var items = {}; 3 4 /*this.has = function(value) { 5 return value in items; // in 操作符会在通过对象访问给定属性时返回true,无论该属性存在于实例中还是原型中 6 }*/ 7 8 // better way 9 this.has = function(value) { 10 return items.hasOwnProperty(value); // hasOwnProperty()方法只在给定属性存在于对象实例中时,才会返回true 11 }; 12 13 this.add = function(value) { 14 if (!this.has(value)) { 15 items[value] = value; //添加一个值的时候,把它同时作为键和值保存,因为这样有利于查找这个值 16 reutrn true; // 返回true,表示添加了这个值 17 } 18 return false; // 返回false,表示没有添加它 19 }; 20 21 this.remove = function(value) { 22 if (this.has(value)) { 23 delete items[value]; // 既然用对象来存储集合的items对象,就可以简单地使用delete操作符从items对象中移除属性 24 return true; 25 } 26 return false; 27 }; 28 29 this.clear = function() { 30 items = {}; 31 }; 32 33 // size实现方法一(只能在现代浏览器中运行) 34 /*this.size = function() { 35 return Object.keys(items).length; // Object类有一个keys方法,返回一个包含给定对象所有属性的数组 36 }*/ 37 38 // size实现方法二(可以在任何浏览器上运行) 39 this.size = function() { 40 var count = 0; 41 for (var prop in items) { // for...in...用于枚举对象中的属性,包括实例和原型中的属性 42 if (items.hasOwnProperty(prop)) { // 检查它们是否是对象自身的属性 43 ++count; 44 } 45 } 46 return count; 47 }; 48 49 // values实现方法一,同size实现方法一 50 /*this.values = function() { 51 return Object.keys(items); 52 }*/ 53 54 // values实现方法二,同size实现方法二 55 this.values = function() { 56 var keys = []; 57 for (var key in items) { 58 keys.push(key); 59 } 60 return keys; 61 }; 62 }
(2)字典方法声明:
首先,使用对象来表示集合。
序号
|
方法
|
说明
|
1
|
set(key, value) | 向字典中添加新元素 |
2
|
remove(key) | 通过使用键值来从字典中移除键值对应的数据值 |
3
|
has(key)
|
如果某个键值存在于这个字典中,则返回 true,反之则返回 false |
4
|
get(key)
|
通过键值查找特定的数值并返回 |
5
|
clear ( )
|
将这个字典中的所有元素全部删除 |
6
|
size ( )
|
返回字典中所包含元素的数量。与数组的 length 属性类似。 |
7
|
keys()
|
将字典所包含的所有键名以数组形式返回
|
8
|
values ( )
|
将字典所包含的所有数值以数组形式返回 |
字典的实现:
1 function Dictionary() { 2 var items = {}; 3 4 this.has = function(value) { // 之所以要先实现该方法,是因为它会被set和remove等其他方法调用 5 return key in items; 6 }; 7 8 this.set = function(key, value) { 9 items[key] = value; 10 }; 11 12 this.remove = function(key) { 13 if (this.has(key)) { 14 delete items[key]; 15 return true; 16 } 17 return false; 18 }; 19 20 this.get = function(key) { 21 return this.has(key) ? items[key] : undefined; // get方法会首先验证我们想要的值是否存在(通过查找key值) 22 }; 23 24 this.values = function() { 25 var values = []; 26 for (var k in items) { 27 if (this.has(k)) { 28 values.push(items[k]); 29 } 30 } 31 return values; 32 }; 33 34 this.clear = function() { 35 items = {}; 36 }; 37 38 this.size = function() { 39 return Object.keys(items).length; // Object类有一个keys方法,返回一个包含给定对象所有属性的数组 40 }; 41 42 this.keys = function() { 43 return Object.keys(items); 44 }; 45 46 this.getItems = function() { 47 reutrn items; 48 }; 49 }
(3)散列方法声明:
首先,使用数组来表示集合。
三个基础方法:
序号
|
方法
|
说明
|
1
|
put(key, value) | 向散列表增加一个新的项(也能更新散列表) |
2
|
remove(key) | 根据键值从散列表中移除值 |
3
|
get(key)
|
返回根据键值检索到的特定的值 |
处理散列表的冲突:
有时候,一些键会有相同的散列值。不同的值在散列表中对应相同位置的时候,我们称其为冲突。处理冲突有几种方法:分离链接、线性探查和双散列法。
分离链接:包括为散列表的每一个位置创建一个链表并将元素存储在里面。它是是解决冲突的最简单的方法,但是它在 HashTable 实例之外还需要额外的存储空间。
线性探查:当想向表中某个位置加入一个新元素的时候,如果索引为 index 的位置已经被占据了,就尝试 index+1 的位置。如果 index+1 的位置也被占据了,就尝试 index+2 的位置,以此类推。一个表现良好的散列函数是由几个方面构成的:插入和检索元素的时间(即性能),当然也包括较低的冲突的可能。
散列表的实现:
1 function HashTable() { 2 var table = []; 3 4 // 散列函数,是HashTable中的一个私有方法 5 var loseloseHashCode = function(key) { 6 var hash = 0; 7 for (var i=0; i<key.length; i++) { 8 hash += key.charCodeAt(i); // 给定一个key参数,我们就能根据组成key的每个字符的ASCII码值的和得到一个数字 9 } 10 return hash % 37; // 为了得到比较小的数值,我们会使用hash值和一个任意数做除法的余数 11 }; 12 13 // 更好的散列函数(这并不是最好的散列函数,但这是最被社区推荐的散列函数之一) 14 var djb2HashCode = function(key) { 15 var hash = 5381; // 初始化赋值为一个质数,大多数实现都使用5318 16 for (var i=0; i<key.length; i++) { 17 hash += hash * 33 + key.charCodeAt(i); // 将hash与33相乘,当作一个魔力数 18 } 19 return hash % 1013; // 将相加的和与另一个随机质数相除 20 } 21 22 this.put = function(key, value) { 23 var position = loseloseHashCode(key); // 给定一个键值,我们需要根据所创建的散列函数计算出它在表中的位置 24 table[position] = value; // 将value参数添加到用散列函数计算出的对应的位置上 25 }; 26 27 this.get = function(key) { 28 return table[loseloseHashCode(key)]; //loseloseHashCode(key)会返回值的位置 29 }; 30 31 this.remove = function(key) { 32 table[loseloseHashCode(key)] = undefined; 33 }; 34 35 36 /*解决冲突方法一:分离链接*/ 37 38 // 为了实现一个使用了分离链接的HashTable实例,我们需要一个新的辅助类来表示将要加入LinkedList实例的元素 39 var ValuePair = function(key, value) { 40 this.key = key; 41 this.value = value; 42 } 43 44 // 分离链接:重写put方法 45 this.put = function(key, value) { 46 var position = loseloseHashCode(key); 47 // 如果这个位置是第一次被加入元素,我们会在这个位置上初始化一个LinkedList类的实例 48 if (table[position] == undefined) { 49 table[position] = new LinkedList(); 50 } 51 table[position].append(new ValuePair(key, value)); 52 } 53 54 // 分离链接:重写get方法 55 this.get = function(key) { 56 var position = loseloseHashCode(key); 57 58 if (table[position] !== undefined) { 59 60 //遍历链表来寻找键/值 61 var current = table[position].getHead(); 62 63 while (current.next) { 64 if (current.element.key === key) { 65 return current.element.value; 66 } 67 current = current.next; 68 } 69 70 // 检查元素在链表第一个或最后一个节点的情况 71 if (current.element.key === key) { 72 return current.element.value; 73 } 74 } 75 return undefined; 76 } 77 78 // 分离链接:重写remove方法 79 this.remove = function(key) { 80 var position = loseloseHashCode(key); 81 82 if (table[position] !== undefined) { 83 84 var current = table[position].getHead(); 85 while (current.next) { 86 if (current.element.key === key) { 87 table[position].remove(current.element); 88 if (table[position].isEmpty()) { 89 table[position] = undefined; 90 } 91 return true; 92 } 93 current = current.next; 94 } 95 96 // 检查是否为第一个或最后一个元素 97 if (current.element.key === key) { 98 table[position].remove(current.element); 99 if (table[position].isEmpty()) { 100 table[position] = undefined; 101 } 102 return true; // 返回true表示这个元素已经被移除 103 } 104 } 105 106 return false; // 返回false表示这个元素在散列表中不存在 107 } 108 109 110 111 /*解决冲突方法二:线性探查*/ 112 var ValuePair = function(key, value) { 113 this.key = key; 114 this.value = value; 115 } 116 117 // 线性探查:重写put方法 118 this.put = function(key, value) { 119 var position = loseloseHashCode(key); 120 121 if (table[position] == undefined) { 122 table[position] = new ValuePair(key, value); 123 } else { 124 var index = ++position; 125 while (table[index] != undefined) { 126 index++; 127 } 128 table[index] = new ValuePair(key, value); 129 } 130 } 131 132 // 线性探查:重写get方法 133 this.get = function(key) { 134 var position = loseloseHashCode(key); 135 136 if (table[position] !== undefined) { 137 if (table[position].key === key) { 138 return table[position].value; 139 } else { 140 var index = ++position; 141 while (table[index] === undefined || table[index].key !== key) { 142 index++; 143 } 144 if (table[index].key === key) { //只是为了确认一下 145 return table[index].value; 146 } 147 } 148 } 149 return undefined; 150 } 151 152 // 分离链接:重写remove方法 153 this.remove = function(key) { 154 var position = loseloseHashCode(key); 155 156 if (table[position] !== undefined) { 157 if (table[position].key === key) { 158 table[index] = undefined; 159 } else { 160 var index = ++position; 161 while (table[index] === undefined || table[index].key !== key) { 162 index++; 163 } 164 if (table[index].key === key) { //只是为了确认一下 165 table[index] = undefined; 166 } 167 } 168 } 169 return undefined; 170 } 171 } 172 173 HashTable.js
参考书籍:《学习JavaScript数据结构与算法》