字典和散列表
字典也称作映射、符号表或关联数组
// 默认变字符串String,把所有作为键名传入的对象转化为字符串
// 注意:item变量是对象的话会导致输出为 [object Object]
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();
}
// ValuePair类中设置键和值
class ValuePair {
constructor(key, value) {
// 保存原始的key就好了,不需要转换成String
this.key = key;
this.value = value;
}
toString() {
return `[#${this.key}: ${this.value}]`;
}
}
// 字典开始
class Dictionary {
constructor(toStrFn = defaultToString) {
this.toStrFn = toStrFn;
this.table = {};
}
// 向字典中添加新元素。如果key已经存在,那么已存在的 value会被新的值覆盖
set(key, value) {
if (key != null && value != null) {
const tableKey = this.toStrFn(key);
this.table[tableKey] = new ValuePair(key, value);
return true;
}
return false;
}
// 从字典中检索一个值
get(key) {
const valuePair = this.table[this.toStrFn(key)];
return valuePair == null ? undefined : valuePair.value;
// 或者像下面这么写
// if (this.hasKey(key)) {
// return this.table[this.toStrFn(key)];
// }
// return undefined;
}
// 检测一个键是否存在于字典中
hasKey(key) {
return this.table[this.toStrFn(key)] != null;
}
// 通过使用键值作为参数来从字典中移除键值对应的数据值
remove(key) {
if (this.hasKey(key)) {
delete this.table[this.toStrFn(key)];
return true;
}
return false;
}
// 将字典所包含的所有数值以数组形式返回
values() {
return this.keyValues().map((valuePair) => valuePair.value);
}
// 将字典所包含的所有键名以数组形式返回
keys() {
return this.keyValues().map((valuePair) => valuePair.key);
// 或者像下面这样写
// const keys = [];
// const valuePairs = this.keyValues();
// for (let i = 0; i < valuePairs.length; i++) {
// keys.push(valuePairs[i].key);
// }
// return keys;
}
// 将字典中所有[键,值]对返回
keyValues() {
return Object.values(this.table);
// 或者像下面这样写
// const valuePairs = [];
// for (const k in this.table) {
// if (this.hasKey(k)) {
// valuePairs.push(this.table[k]);
// }
// }
// return valuePairs
}
// 迭代数据结构中每个值的方法
forEach(callbackFn) {
const valuePairs = this.keyValues();
for (let i = 0; i < valuePairs.length; i++) {
const result = callbackFn(valuePairs[i].key, valuePairs[i].value);
if (result === false) {
break;
}
}
}
// 检验字典是否为空
isEmpty() {
return this.size() === 0;
}
// 返回字典所包含值的数量。与数组的length属性类似
size() {
return Object.keys(this.table).length;
}
// 清空字典内容
clear() {
this.table = {};
}
toString() {
if (this.isEmpty()) {
return "";
}
const valuePairs = this.keyValues();
let objString = `${valuePairs[0].toString()}`;
for (let i = 1; i < valuePairs.length; i++) {
objString = `${objString},${valuePairs[i].toString()}`;
}
return objString;
}
}
散列表
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;
}
/* djb2HashCode(key) {
const tableKey = this.toStrFn(key);
let hash = 5381;
for (let i = 0; i < tableKey.length; i++) {
hash = (hash * 33) + tableKey.charCodeAt(i);
}
return hash % 1013;
} */
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;
}
}
散列表和散列集合
有时候,一些键会有相同的散列值。不同的值在散列表中对应相同位置的时候,我们称其为冲突。处理冲突有几种方法:分离链接、线性探查和双散列法。分离链接法为散列表的每一个位置创建一个链表并将元素存储在里面。线性探查法处理冲突的方法是将元素直接存储到表中,而不是在单独的数据结构中,当想向表中某个位置添加一个新元素的时候,如果索引为 position 的位置已经被占据了,就尝试 position+1 的位置。如果 position+1 的位置也被占据了,就尝试 position+2 的位置,以此类推,直到在散列表中找到一个空闲的位置
分离链接法
// 使用分离链接法来处理冲突
class HashTableSeparateChaining {
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 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;
}
isEmpty() {
return this.size() === 0;
}
size() {
let count = 0;
Object.values(this.table).forEach((linkedList) => {
count += linkedList.size();
});
return count;
}
clear() {
this.table = {};
}
getTable() {
return 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;
}
}
线性探查法
线性探查技术分为两种:软删除和惰性删除
软删除
// 软删除
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++;
}
}
isEmpty() {
return this.size() === 0;
}
size() {
return Object.keys(this.table).length;
}
clear() {
this.table = {};
}
getTable() {
return 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;
}
}
惰性删除
// 惰性删除
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;
}
isEmpty() {
return this.size() === 0;
}
size() {
let count = 0;
Object.values(this.table).forEach((valuePair) => {
count += valuePair.isDeleted === true ? 0 : 1;
});
return count;
}
clear() {
this.table = {};
}
getTable() {
return 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;
}
}
推荐使用 Map WeakMap