- 散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数
function defaultToString(item) {
if (item === null) {
return 'NULL';
} else if (item === undefined) {
return 'UNDEFINED';
} else if (typeof item === 'string' || item instanceof String) {
return `${item}`;
}
return item.toString();
}
class ValuePair {
constructor(key, value) {
this.key = key;
this.value = value;
}
toString() {
return `[#{this.key}: ${this.value}]`;
}
}
class HashTable {
constructor(toStrFn = defaultToString) {
this.toStrFn = toStrFn;
this.table = {};
}
//散列函数
loseloseHashCode(key) {
if (typeof key === 'number') {
return key;
}
const tableKey = this.toStrFn(key);
let hash = 0;
for (let i = 0; i < tableKey.length; i++) {
hash += tableKey.charCodeAt(i);
}
return hash % 37;
}
hashCode(key) {
return this.loseloseHashCode(key);
}
put(key, value) {
if (key != null && value != null) {
const position = this.hashCode(key);
this.table[position] = new ValuePair(key, value);
return true;
}
return false;
}
get(key) {
const valuePair = this.table[this.hashCode(key)];
return valuePair == null ? undefined : valuePair.value;
}
remove(key) {
const hash = this.hashCode(key);
const valuePair = this.table[hash];
if (valuePair != null) {
delete this.table[hash];
return true;
}
return false;
}
getTable() {
return this.table;
}
isEmpty() {
return this.size() === 0;
}
size() {
return Object.keys(this.table).length;
}
clear() {
this.table = {};
}
toString() {
if (this.isEmpty()) {
return '';
}
const keys = Object.keys(this.table);
let objString = `{${keys[0]} => ${this.table[keys[0]].toString()}}`;
for (let i = 1; i < keys.length; i++) {
objString = `${objString},{${keys[i]} =>
${this.table[keys[i]].toString()}}`;
}
return objString;
}
}
散列表中的冲突问题
-
有时候一些键会有相同的散列值。不同的值在散列表对应的位置的时候。我们称其为冲突;
-
解决办法:
-
分离链接法:分离链接法包括为散列表的每一个位置创建一个==链表==并将元素存储在里面。它是解决冲突的最简单的方法,但是在 HashTable 实例之外还需要额外的存储空间。
class HashTableSepareChaining { constructor(toStrFn = defaultToString) { this.toStrFn = toStrFn; this.table = {}; } put(key, value) { if (key != null && value != null) { const position = this.hashCode(key); if (this.table[positon] == null) { this.table[position] = new LinkedList(); } this.table[position].push(new ValuePair(key, value)); return true; } return false; } get(key) { const position = this.hashCode(key); const linkedList = this.table[position]; if (linkedList != null && !linkedList.isEmpty()) { let current = linkedList.getHead(); while (current != null) { if (current.element.key === key) { return current.element.value; } current = current.next; } } return undefined; } remove(key) { const position = this.hashCode(key); const linkedList = this.table[position]; if (linkedList != null && !linkedList.isEmpty()) { let current = linkedList.getHead(); while (current != null) { if (current.element.key === key) { linkedList.remove(current.element); if (linkedList.isEmpty()) { delete this.table[position]; } return true; } current = current.next; } } return false; } } -
线性探查:将元素直接存储在表中,而不是在单独的数据结构中。
- 过程:当想向表中的某个位置添加一个新元素的时候,如果索引为position的位置已经被占据了,就会尝试position+1的位置,如果position+1的位置也被占据了,就会尝试position+2的位置,以此类推,直到到在散列表中找到一个空闲的位置。
- 线性探查分为两种:
-
- 软删除方法:我们使用一个特殊的值来表示键值对被删除了,而是真正的删除他。(经过一段时间后会逐渐减低散列表的效率)
- 挪动法:需要检验是否有将一个或者多个元素移动到之前的位置。当搜索一个键的时候,==这种方法可以避免找到一个空的位置==。
-
//1. 软删除方法 function defaultToString(item) { if (item === null) { return 'NULL'; } else if (item === undefined) { return 'UNDEFINED'; } else if (typeof item === 'string' || item instanceof String) { return `${item}`; } return item.toString(); } class ValuePairLazy { constructor(key, value, isDeleted = false) { this.key = key; this.value = value; this.isDeleted = isDeleted; } toString() { return `[#{this.key}: ${this.value}]`; } } class HashTableLinearProbingLazy { constructor(toStrFn = defaultToString) { this.toStrFn = toStrFn; this.table = {}; } loseloseHashCode(key) { if (typeof key === 'number') { return key; } const tableKey = this.toStrFn(key); let hash = 0; for (let i = 0; i < tableKey.length; i++) { hash += tableKey.charCodeAt(i); } return hash % 37; } hashCode(key) { return this.loseloseHashCode(key); } put(key, value) { if (key != null && value != null) { const position = this.hashCode(key); if ( this.table[position] == null || (this.table[position] != null && this.table[position].isDeleted) ) { this.table[position] = new ValuePairLazy(key, value); } else { let index = position + 1; while (this.table[index] != null && !this.table[position].isDeleted) { index++; } this.table[index] = new ValuePairLazy(key, value); } return true; } return false; } get(key) { const position = this.hashCode(key); if (this.table[position] != null) { if (this.table[position].key === key && !this.table[position].isDeleted) { return this.table[position].value; } let index = position + 1; while ( this.table[index] != null && (this.table[index].key !== key || this.table[index].isDeleted) ) { if (this.table[index].key === key && this.table[index].isDeleted) { return undefined; } index++; } if ( this.table[index] != null && this.table[index].key === key && !this.table[index].isDeleted ) { return this.table[position].value; } } return undefined; } remove(key) { const position = this.hashCode(key); if (this.table[position] != null) { if (this.table[position].key === key && !this.table[position].isDeleted) { this.table[position].isDeleted = true; return true; } let index = position + 1; while ( this.table[index] != null && (this.table[index].key !== key || this.table[index].isDeleted) ) { index++; } if ( this.table[index] != null && this.table[index].key === key && !this.table[index].isDeleted ) { this.table[index].isDeleted = true; return true; } } return false; } } //2.挪动法 class ValuePair { constructor(key, value) { this.key = key; this.value = value; } toString() { return `[#{this.key}: ${this.value}]`; } } class HashTableLinearProbing { constructor(toStrFn = defaultToString) { this.toStrFn = toStrFn; this.table = {}; } loseloseHashCode(key) { if (typeof key === 'number') { return key; } const tableKey = this.toStrFn(key); let hash = 0; for (let i = 0; i < tableKey.length; i++) { hash += tableKey.charCodeAt(i); } return hash % 37; } hashCode(key) { return this.loseloseHashCode(key); } put(key, value) { if (key != null && value != null) { const position = this.hashCode(key); if (this.table[position] == null) { this.table[position] = new ValuePair(key, value); } else { let index = position + 1; while (this.table[index] != null) { index++; } this.table[index] = new ValuePair(key, value); } return true; } return false; } get(key) { const position = this.hashCode(key); if (this.table[position] != null) { if (this.table[position].key === key) { return this.table[position].value; } let index = position + 1; while (this.table[index] != null && this.table[index].key !== key) { index++; } if (this.table[index] != null && this.table[index].key === key) { return this.table[position].value; } } return undefined; } remove(key) { const position = this.hashCode(key); if (this.table[position] != null) { if (this.table[position].key === key) { delete this.table[position]; this.verifyRemoveSideEffect(key, position); return true; } let index = position + 1; while (this.table[index] != null && this.table[index].key !== key) { index++; } if (this.table[index] != null && this.table[index].key === key) { delete this.table[index]; this.verifyRemoveSideEffect(key, index); return true; } } return false; } verifyRemoveSideEffect(key, removedPosition) { const hash = this.hashCode(key); let index = removedPosition + 1; while (this.table[index] != null) { const posHash = this.hashCode(this.table[index].key); if (posHash <= hash || posHash <= removedPosition) { this.table[removedPosition] = this.table[index]; delete this.table[index]; removedPosition = index; } index++; } } } -