世界就像一个巨大的图书馆,我们读着别人,做着自己,等待被读。 :

ES6的JavaScript数据结构实现之字典与散列表

目的:ES6标准下的JS数据结构的一些实现代码。(作为记录和启发)

内容:字典和散列表。(未完成,待继续)

所有源码在我的Github上(如果觉得不错记得给星鼓励我哦):ES6的JavaScript数据结构实现之字典与散列表

注:ES6也是新增加了Map类。此外还提供了WeakMap和WeakSet。基本上,Map和Set与其弱化版本之间仅有的区别是:WeakMap和WeakSet类没有entries、keys、values等方法;只能用对象作为键。而这两个弱化类是为了性能,可以使得JavaScript的垃圾回收器可以从中清除整个入口。此外,由于弱化类键只能是对象,所以不知道键就没办法找到值,这个性质可以用于封装ES6类的私有属性。

一、基础数据结构

1、字典(以[键,值]对的形式存储数据,table[key]={key, value})

 //注:由于JavaScript不是强类型的语言,不能保证键一定是字符串,所以需要把所有键名的传入的对象转换为字符串,使得Dictionary类中搜索和获取值更简单。

  1 class ValuePair {
  2   constructor(key, value) {
  3     this.key = key;
  4     this.value = value;
  5   }
  6 
  7   toString() {
  8     return `[#${this.key}: ${this.value}]`;
  9   }
 10 }
 11 
 12 function defaultToString(item) {
 13   if (item === null) {
 14     return 'NULL';
 15   } if (item === undefined) {
 16     return 'UNDEFINED';
 17   } if (typeof item === 'string' || item instanceof String) {
 18     return `${item}`;
 19   }
 20   return item.toString();
 21 }
 22 
 23 class Dictionary {
 24   constructor(toStrFn = defaultToString) {
 25     this.toStrFn = toStrFn;
 26     this.table = {};
 27   }
 28   hasKey(key) {
 29     return this.table[this.toStrFn(key)] != null;
 30   }
 31   set(key, value) {
 32     if (key != null && value != null) {
 33       const tableKey = this.toStrFn(key);
 34       this.table[tableKey] = new ValuePair(key,value);
 35       return true;
 36     }
 37     return false;
 38   }
 39   remove(key) {
 40     if (this.hasKey(key)) {
 41       delete this.table[this.toStrFn(key)];
 42       return true;
 43     }
 44     return false;
 45   }
 46   get(key) {
 47     const valuePair = this.table[this.toStrFn(key)];
 48     return valuePair == null ? undefined : valuePair.value;
 49   }
 50   keyValues() {
 51     return Object.values(this.table);
 52   }
 53   values() {
 54     return this.keyValues().map(valuePair => valuePair.value);
 55   }
 56 
 57   keys() {
 58     return this.keyValues().map(valuePair => valuePair.key);
 59   }
 60   forEach(callbackFn) {
 61     const valuePairs = this.keyValues();
 62     for (let i = 0; i < valuePairs.length; i++) {
 63       const result = callbackFn(valuePairs[i].key, valuePairs[i].value);
 64       if (result === false) {
 65         break;
 66       }
 67     }
 68   }
 69 
 70     isEmpty() {
 71     return this.size() === 0;
 72   }
 73 
 74   size() {
 75     return Object.keys(this.table).length;
 76   }
 77 
 78   clear() {
 79     this.table = {};
 80   }
 81 
 82   toString() {
 83     if (this.isEmpty()) {
 84       return '';
 85     }
 86     const valuePairs = this.keyValues();
 87     let objString = `${valuePairs[0].toString()}`;
 88     for (let i = 1; i < valuePairs.length; i++) {
 89       objString = `${objString},${valuePairs[i].toString()}`;
 90     }
 91     return objString;
 92   }
 93 
 94 
 95 }
 96 
 97 const dictionary = new Dictionary();
 98 dictionary.set('Bob','student');
 99 dictionary.set('Alice','teacher');
100 dictionary.set('Jack','student');
101 console.log(dictionary);
102 console.log(dictionary.hasKey('Bob'));
103 dictionary.remove('Jack');
104 console.log(dictionary);
105 console.log(dictionary.get('Alice'));
106 console.log(dictionary.keyValues());
107 console.log(dictionary.keys());
108 console.log(dictionary.values());
109 dictionary.forEach((k, v) => {
110   console.log(`forEach: `, `key: ${k},value: ${v}`);
111 });
Dictionary

 

2、散列表(HashTable类或HashMap类,它是Dictionary类的一种散列表实现方式)

  1 class ValuePair {
  2   constructor(key, value) {
  3     this.key = key;
  4     this.value = value;
  5   }
  6 
  7   toString() {
  8     return `[#${this.key}: ${this.value}]`;
  9   }
 10 }
 11 
 12 function defaultToString(item) {
 13   if (item === null) {
 14     return 'NULL';
 15   } if (item === undefined) {
 16     return 'UNDEFINED';
 17   } if (typeof item === 'string' || item instanceof String) {
 18     return `${item}`;
 19   }
 20   return item.toString();
 21 }
 22 
 23 class HashTable {
 24   constructor(toStrFn = defaultToString) {
 25     this.toStrFn = toStrFn;
 26     this.table = {};
 27   }
 28   loseloseHashCode(key) {
 29     if (typeof key === 'number') {
 30       return key;
 31     }
 32     const tableKey = this.toStrFn(key);
 33     let hash = 0;
 34     for (let i = 0; i < tableKey.length; i++) {
 35       hash += tableKey.charCodeAt(i);
 36     }
 37     return hash % 37;
 38   }
 39   hashCode(key) {
 40     return this.loseloseHashCode(key);
 41   }
 42   put(key,value) {
 43     if (key != null && value != null) {
 44       const position = this.hashCode(key);
 45       this.table[position] = new ValuePair(key, value);
 46       return true;
 47     }
 48     return false;
 49   }
 50   get(key) {
 51     const valuepair = this.table[this.hashCode(key)];
 52     return valuepair == null ? undefined : valuepair.value;
 53   }
 54   remove(key) {
 55     const hash = this.hashCode(key);
 56     const valuepair = this.table[hash];
 57     if (valuepair != null ) {
 58       delete this.table[hash];
 59       return true;
 60     }
 61     return false;
 62   }
 63     getTable() {
 64     return this.table;
 65   }
 66 
 67   isEmpty() {
 68     return this.size() === 0;
 69   }
 70 
 71   size() {
 72     return Object.keys(this.table).length;
 73   }
 74 
 75   clear() {
 76     this.table = {};
 77   }
 78 
 79   toString() {
 80     if (this.isEmpty()) {
 81       return '';
 82     }
 83     const keys = Object.keys(this.table);
 84     let objString = `{${keys[0]} => ${this.table[keys[0]].toString()}}`;
 85     for (let i = 1; i < keys.length; i++) {
 86       objString = `${objString},{${keys[i]} => ${this.table[keys[i]].toString()}}`;
 87     }
 88     return objString;
 89   }
 90 
 91 
 92 
 93 }
 94 
 95 const hash = new HashTable();
 96 console.log(hash.hashCode('Bob') + '-Bob');
 97 hash.put('Bob', 20); 
 98 console.log(hash);
 99 hash.put('Alice', 21); 
100 hash.put('Jack', 19); 
101 console.log(hash);
102 console.log(hash.get('Alice'));
103 hash.remove('Jack');
104 console.log(hash);
HashTable

 

二、散列表的扩展

问题:有时候,一些键会有相同的散列值,不同的值在散列表中对应相同位置的时候,称为冲突。(冲突会导致散列表只保存最新的值,旧的值会被覆盖)

解决冲突:分离链接、线性探查和双散列法。(我们研究前两个)

1、分离链接

//注:分离链接法包括为散列表的每一个位置创建一个链表并将元素存储在里面。(优点:简单;缺点:需要额外的存储空间)

 //本质是将Hash表的位置当做链表处理,先找到位置,对元素进行处理

  1 class ValuePair {
  2   constructor(key, value) {
  3     this.key = key;
  4     this.value = value;
  5   }
  6 
  7   toString() {
  8     return `[#${this.key}: ${this.value}]`;
  9   }
 10 }
 11 
 12 function defaultToString(item) {
 13   if (item === null) {
 14     return 'NULL';
 15   } if (item === undefined) {
 16     return 'UNDEFINED';
 17   } if (typeof item === 'string' || item instanceof String) {
 18     return `${item}`;
 19   }
 20   return item.toString();
 21 }
 22 //载入之前我们写好的Linkedlist
 23 class Node {
 24 constructor(element, next) {
 25 this.element = element;
 26 this.next = next;
 27 }
 28 }
 29 
 30 function defaultEquals(a, b) {
 31 return a === b;
 32 }
 33 
 34 class LinkedList {
 35 constructor(equalsFn = defaultEquals) {
 36 this.equalsFn = equalsFn;
 37 this.count = 0;
 38 this.head = undefined;
 39 }
 40 
 41 push(element){
 42 const node = new Node(element);
 43 let current;
 44 if (this.head == null){
 45 this.head = node;
 46 } else {
 47 current = this.head;
 48 while (current.next != null) {
 49 current = current.next;
 50 }
 51 current.next = node;
 52 }
 53 this.count++;
 54 }
 55 
 56 getElementAt(index) {
 57 if (index >= 0 && index <= this.count) {
 58 let node = this.head;
 59 for (let i = 0; i < index && node != null; i++) {
 60 node = node.next;
 61 }
 62 return node;
 63 }
 64 return undefined;
 65 }
 66 
 67 insert(element, index) {
 68 if (index >= 0 && index <= this.count) {
 69 const node = new Node(element);
 70 if (index ==0 ) {
 71 const current = this.head;
 72 node.next = current;
 73 this.head = node;
 74 } else {
 75 const previous = this.getElementAt(index - 1);
 76 node.next = previous.next;
 77 previous.next = node;
 78 }
 79 this.count++;
 80 return true;
 81 }
 82 return false;
 83 }
 84 
 85 removeAt(index) {
 86 if (index >= 0 && index < this.count) {
 87 let current = this.head;
 88 if (index == 0) {
 89 this.head = current.next; 
 90 }else {
 91 const previous = this.getElementAt(index - 1);
 92 current = previous.next;
 93 previous.next = current;
 94 
 95 }
 96 this.count--;
 97 return current.element;
 98 }
 99 return undefined;
100 }
101 
102 remove(element) {
103 const index = this.indexOf(element);
104 return this.removeAt(index);
105 }
106 
107 indexOf(element) {
108 let current = this.head;
109 for (let i = 0; i < this.count && current != null; i++) {
110 if (this.equalsFn(element, current.element)) {
111 return i;
112 }
113 current = current.next;
114 } 
115 return -1;
116 }
117 
118 isEmpty() {
119 return this.size() === 0;
120 }
121 
122 size() {
123 return this.count;
124 }
125 
126 getHead() {
127 return this.head;
128 }
129 
130 clear() {
131 this.head = undefined;
132 this.count = 0;
133 }
134 
135 toString() {
136 if (this.head == null) {
137 return '';
138 }
139 let objString = `${this.head.element}`;
140 let current = this.head.next;
141 for (let i = 1; i < this.size() && current != null; i++) {
142 objString = `${objString},${current.element}`;
143 current = current.next;
144 }
145 return objString;
146 }
147 }
148 
149 //分离链接
150 class HashTableSeparateChaining {
151   constructor(toStrFn = defaultToString) {
152     this.toStrFn = toStrFn;
153     this.table = {};
154   }
155 
156   loseloseHashCode(key) {
157     if (typeof key === 'number') {
158       return key;
159     }
160     const tableKey = this.toStrFn(key);
161     let hash = 0;
162     for (let i = 0; i < tableKey.length; i++) {
163       hash += tableKey.charCodeAt(i);
164     }
165     return hash % 37;
166   }
167 
168   hashCode(key) {
169     return this.loseloseHashCode(key);
170   }
171 
172   put(key, value) {
173     if (key != null && value != null) {
174       const position = this.hashCode(key);
175       if (this.table[position] == null) {
176         this.table[position] = new LinkedList();
177       }
178       this.table[position].push(new ValuePair(key, value));
179       return true;
180     }
181     return false;
182   }
183 
184   get(key) {
185     const position = this.hashCode(key);
186     const linkedList = this.table[position];
187     if (linkedList != null && !linkedList.isEmpty()) {
188       let current = linkedList.getHead();
189       while (current != null) {
190         if (current.element.key === key) {
191           return current.element.value;
192         }
193         current = current.next;
194       }
195     }
196     return undefined;
197   }
198 
199   remove(key) {
200     const position = this.hashCode(key);
201     const linkedList = this.table[position];
202     if (linkedList != null && !linkedList.isEmpty()) {
203       let current = linkedList.getHead();
204       while (current != null) {
205         if (current.element.key === key) {
206           linkedList.remove(current.element);
207           if (linkedList.isEmpty()) {
208             delete this.table[position];
209           }
210           return true;
211         }
212         current = current.next;
213       }
214     }
215     return false;
216   }
217 
218   isEmpty() {
219     return this.size() === 0;
220   }
221 
222   size() {
223     let count = 0;
224     Object.values(this.table).forEach(linkedList => {
225       count += linkedList.size();
226     });
227     return count;
228   }
229 
230   clear() {
231     this.table = {};
232   }
233 
234   getTable() {
235     return this.table;
236   }
237 
238   toString() {
239     if (this.isEmpty()) {
240       return '';
241     }
242     const keys = Object.keys(this.table);
243     let objString = `{${keys[0]} => ${this.table[keys[0]].toString()}}`;
244     for (let i = 1; i < keys.length; i++) {
245       objString = `${objString},{${keys[i]} => ${this.table[
246         keys[i]
247       ].toString()}}`;
248     }
249     return objString;
250   }
251 }
252 
253 const hashTable = new HashTableSeparateChaining();
254 
255 hashTable.put('Ygritte', 'ygritte@email.com');
256 hashTable.put('Jonathan', 'jonathan@email.com');
257 hashTable.put('Jamie', 'jamie@email.com');
258 hashTable.put('Jack', 'jack@email.com');
259 hashTable.put('Jasmine', 'jasmine@email.com');
260 hashTable.put('Jake', 'jake@email.com');
261 hashTable.put('Nathan', 'nathan@email.com');
262 hashTable.put('Athelstan', 'athelstan@email.com');
263 hashTable.put('Sue', 'sue@email.com');
264 hashTable.put('Aethelwulf', 'aethelwulf@email.com');
265 hashTable.put('Sargeras', 'sargeras@email.com');
266 
267 console.log('**** Printing Hash **** ');
268 
269 console.log(hashTable.toString());
HashTableSeparateChaining

 

2、线性探查

 //注:其处理冲突的方法是将元素直接存储到表中,而不是单独的数据结构中。当在某个位置position添加新元素,出现冲突时,就尝试添加到position+1的位置,如果position+1的位置也被某元素占据,则继续迭代,直到一个“空闲”的位置。

//采用需要检验是否有必要将一个或多个元素移动到之前的位置的方法。

  1 class ValuePair {
  2   constructor(key, value) {
  3     this.key = key;
  4     this.value = value;
  5   }
  6 
  7   toString() {
  8     return `[#${this.key}: ${this.value}]`;
  9   }
 10 }
 11 
 12 function defaultToString(item) {
 13   if (item === null) {
 14     return 'NULL';
 15   } if (item === undefined) {
 16     return 'UNDEFINED';
 17   } if (typeof item === 'string' || item instanceof String) {
 18     return `${item}`;
 19   }
 20   return item.toString();
 21 }
 22 
 23 //线性探查
 24 class HashTableLinearProbing {
 25   constructor(toStrFn = defaultToString) {
 26     this.toStrFn = toStrFn;
 27     this.table = {};
 28   }
 29 
 30   loseloseHashCode(key) {
 31     if (typeof key === 'number') {
 32       return key;
 33     }
 34     const tableKey = this.toStrFn(key);
 35     let hash = 0;
 36     for (let i = 0; i < tableKey.length; i++) {
 37       hash += tableKey.charCodeAt(i);
 38     }
 39     return hash % 37;
 40   }
 41 
 42   hashCode(key) {
 43     return this.loseloseHashCode(key);
 44   }
 45 
 46   put(key, value) {
 47     if (key != null && value != null) {
 48       const position = this.hashCode(key);
 49       if (this.table[position] == null) {
 50         this.table[position] = new ValuePair(key, value);
 51       } else {
 52         let index = position + 1;
 53         while (this.table[index] != null) {
 54           index++;
 55         }
 56         this.table[index] = new ValuePair(key, value);
 57       }
 58       return true;
 59     }
 60     return false;
 61   }
 62 
 63   get(key) {
 64     const position = this.hashCode(key);
 65     if (this.table[position] != null) {
 66       if (this.table[position].key === key) {
 67         return this.table[position].value;
 68       }
 69       let index = position + 1;
 70       while (this.table[index] != null && this.table[index].key !== key) {
 71         index++;
 72       }
 73       if (this.table[index] != null && this.table[index].key === key) {
 74         return this.table[position].value;
 75       }
 76     }
 77     return undefined;
 78   }
 79 
 80   remove(key) {
 81     const position = this.hashCode(key);
 82     if (this.table[position] != null) {
 83       if (this.table[position].key === key) {
 84         delete this.table[position];
 85         this.verifyRemoveSideEffect(key, position);
 86         return true;
 87       }
 88       let index = position + 1;
 89       while (this.table[index] != null && this.table[index].key !== key) {
 90         index++;
 91       }
 92       if (this.table[index] != null && this.table[index].key === key) {
 93         delete this.table[index];
 94         this.verifyRemoveSideEffect(key, index);
 95         return true;
 96       }
 97     }
 98     return false;
 99   }
100 
101   verifyRemoveSideEffect(key, removedPosition) {
102     const hash = this.hashCode(key);
103     let index = removedPosition + 1;
104     while (this.table[index] != null) {
105       const posHash = this.hashCode(this.table[index].key);
106       if (posHash <= hash || posHash <= removedPosition) {
107         this.table[removedPosition] = this.table[index];
108         delete this.table[index];
109         removedPosition = index;
110       }
111       index++;
112     }
113   }
114 
115   isEmpty() {
116     return this.size() === 0;
117   }
118 
119   size() {
120     return Object.keys(this.table).length;
121   }
122 
123   clear() {
124     this.table = {};
125   }
126 
127   getTable() {
128     return this.table;
129   }
130 
131   toString() {
132     if (this.isEmpty()) {
133       return '';
134     }
135     const keys = Object.keys(this.table);
136     let objString = `{${keys[0]} => ${this.table[keys[0]].toString()}}`;
137     for (let i = 1; i < keys.length; i++) {
138       objString = `${objString},{${keys[i]} => ${this.table[
139         keys[i]
140       ].toString()}}`;
141     }
142     return objString;
143   }
144 }
145 
146 
147 const hashTable = new HashTableLinearProbing();
148 hashTable.put('Jamie', 'jamie@email.com');
149 hashTable.put('Jack', 'jack@email.com');
150 hashTable.put('Jasmine', 'jasmine@email.com');
151 hashTable.put('Jake', 'jake@email.com');
152 hashTable.put('Nathan', 'nathan@email.com');
153 hashTable.put('Athelstan', 'athelstan@email.com');
154 hashTable.put('Sue', 'sue@email.com');
155 hashTable.put('Aethelwulf', 'aethelwulf@email.com');
156 hashTable.put('Sargeras', 'sargeras@email.com');
157 
158 console.log(hashTable);
159 console.log(hashTable.toString());
160 console.log(hashTable.get('Nathan')); // nathan@email.com
161 
162 hashTable.remove('Ygritte');
163 console.log(hashTable.get('Ygritte')); // undefined
164 console.log(hashTable.toString());
HashTableLinearProbing

 

三、小结

实现的lose lose散列函数并不是一个表现良好的散列函数,因为它会产生太多的冲突。一个表现良好的散列函数是由几个方面构成的:插入和检索元素的时间(即性能),以及较低的冲突可能性。例如djb2函数。

1 djb2HashCode(key) {
2   const tableKey = this.toStrFn(key);
3   let hash = 5381;
4   for(let i = 0; i < tableKey.length; i++) {
5    hash = (hash * 33) + tableKey.charCodeAt(i);  
6     }  
7  return hash % 1013;        
8 }
djb2

 

posted @ 2019-10-16 19:10  Secret_Wu  阅读(652)  评论(0编辑  收藏  举报