欢迎关注我的 个人主页 && 个人博客 && 个人知识库 && 微信公众号“前端自习课”

本周练习内容:数据结构与算法 —— Dictionary 和 HashTable

这些都是数据结构与算法,一部分方法是团队其他成员实现的,一部分我自己做的,有什么其他实现方法或错误,欢迎各位大佬指点,感谢。

一、字典和散列表的概念

  1. 字典是什么?
  2. 字典和集合有什么异同?
  3. 什么是散列表和散列函数?
  4. 散列表的特点是什么?

解析:

  1. 字典是什么?

字典是一种以 键-值对 形式存储数据的数据格式,其中键名用来查询特定元素。

  1. 字典和集合有什么异同?

相同:都是用来存储不同元素的数据格式;
区别:集合是以 值-值 的数据格式存储,而字典是以 键-值 的数据格式存储。

  1. 什么是散列表和散列函数?

哈希表(Hash table,也叫散列表),是根据关键码值(·Key value·)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表

  1. 散列表的特点是什么?

特点:数组和链接优点的结合,查询速度非常的快,几乎是O(1)的时间复杂度,并且插入和删除也容易。

二、请实现一个字典

set(key,value):向字典中添加新元素。
delete(key):通过使用键值从字典中移除键值对应的值。
has(key):如果某个键值存在于这个字典中,则返回 true,否则返回 false。
get(key):使用键值查找对应的值并返回。
clear():删除字典中的所有元素。
size():返回字典包含的元素数量,与数组的 length 属性类似。
keys():将字典的所有键名以数组的形式返回。
values():将字典包含的所有数值以数组形式返回。

使用示例:

  1. let dictionary = new Dictionary();
  2. dictionary.set("Gandalf", "gandalf@email.com");
  3. dictionary.set("John", "johnsnow@email.com");
  4. dictionary.set("Tyrion", "tyrion@email.com");
  5. console.log(dictionary.has("Gandalf"));
  6. console.log(dictionary.size());
  7. console.log(dictionary.keys());
  8. console.log(dictionary.values());
  9. console.log(dictionary.get("Tyrion"));
  10. dictionary.delete("John");
  11. console.log(dictionary.keys());
  12. console.log(dictionary.values());

提示:Web 端优先使用 ES6 以上的语法实现。


解析:

  1. // 二、请实现一个字典
  2. class Dictionary {
  3. constructor(){
  4. this.items = []
  5. }
  6. /**
  7. * 向字典中添加新元素
  8. * @param {*} key 添加的键名
  9. * @param {*} value 添加的值
  10. */
  11. set (key, value) {
  12. if ( !key ) return new Error('请指定插入的key')
  13. this.items[key] = value
  14. }
  15. /**
  16. * 查询某个键值存在于这个字典
  17. * @param {*} key 查询的键名
  18. * @return {Boolean} 是否存在
  19. */
  20. has (key) {
  21. return key in this.items
  22. }
  23. /**
  24. * 通过使用键值从字典中移除键值对应的值
  25. * @param {*} key 移除的键名
  26. * @return {Boolean} 是否移除成功
  27. */
  28. delete (key) {
  29. if(!key || !this.has(key)) return false
  30. delete this.items[key]
  31. return true
  32. }
  33. /**
  34. * 使用键值查找对应的值并返回
  35. * @param {*} key 查找的键名
  36. * @return {*} 查找的结果
  37. */
  38. get (key) {
  39. return this.has(key) ? this.items[key] : undefined
  40. }
  41. /**
  42. * 删除字典中的所有元素
  43. */
  44. clear () {
  45. this.items = {}
  46. }
  47. /**
  48. * 将字典的所有键名以数组的形式返回
  49. * @return {Array} 所有键名的数组
  50. */
  51. keys () {
  52. return Object.keys(this.items)
  53. }
  54. /**
  55. * 将字典的所有键值以数组的形式返回
  56. * @return {Array} 所有键值的数组
  57. */
  58. values () {
  59. let result = []
  60. for(let k in this.items){
  61. if(this.has[k]){
  62. result.push(this.items[k])
  63. }
  64. }
  65. return result
  66. }
  67. /**
  68. * 返回字典包含的元素数量
  69. * @return {Number} 元素数量
  70. */
  71. size () {
  72. const values = this.values()
  73. return values.length
  74. }
  75. }

三、请实现一个散列表

put(key,value):向散列表增加/更新一个新的项。
remove(key):根据键值从散列表中移除值。
get(key):根据键值检索到特定的值。
print():打印散列表中已保存的值。

散列表内部的散列算法:

  1. function hashCode(key) {
  2. let hash = 0;
  3. for (let i = 0; i < key.length; i++) {
  4. hash += key.charCodeAt(i);
  5. }
  6. return hash % 37;
  7. }

使用示例:

  1. const hashTable = new HashTable();
  2. hashTable.put("Gandalf", "gandalf@email.com");
  3. hashTable.put("John", "johnsnow@email.com");
  4. hashTable.put("Tyrion", "tyrion@email.com");
  5. hashTable.print();

解析:

  1. // 三、请实现一个散列表
  2. class HashTable {
  3. constructor(){
  4. this.table = []
  5. }
  6. /**
  7. * 散列函数
  8. * @param {*} key 键名
  9. */
  10. hashCode(key) {
  11. let hash = 0;
  12. for (let i = 0; i < key.length; i++) {
  13. hash += key.charCodeAt(i);
  14. }
  15. return hash % 37;
  16. }
  17. /**
  18. * 向散列表增加/更新一个新的项
  19. * @param {*} key 添加的键名
  20. * @param {*} value 添加的值
  21. */
  22. put (key, value) {
  23. let position = this.hashCode(key)
  24. this.table[position] = value
  25. }
  26. /**
  27. * 根据键值从散列表中移除值
  28. * @param {*} key 移除的键名
  29. * @return {Boolean} 是否成功移除
  30. */
  31. remove (key) {
  32. if ( !key ) return false
  33. let position = this.hashCode(key)
  34. this.table[position] = undefined
  35. return true
  36. }
  37. /**
  38. * 根据键值检索到特定的值
  39. * @param {*} key 查找的键名
  40. * @return {*} 查找的值
  41. */
  42. get (key) {
  43. let position = this.hashCode(key)
  44. return this.table[position]
  45. }
  46. /**
  47. * 打印散列表中已保存的值
  48. * @return {*} 散列表的值
  49. */
  50. print () {
  51. return this.table
  52. }
  53. }

四、请利用之前已实现的链表,实现一个分离链接的散列表

分离链接是为散列表的每一个位置创建一个链表储存元素的方式来处理散列表中的冲突:

6.Dictionary&&HashTable - 图1

请实现新的散列表方法:

put(key,value):将 keyvalue存在一个 ValuePair 对象中(即可定义一个包含 keyvalue 属性的ValuePair类),并将其加入对应位置的链表中。

get(key):返回键值对应的值,没有则返回 undefined

remove(key):从散列表中移除键值对应的元素。

print():打印散列表中已保存的值。

提示:先找到元素储存位置所对应的链表,再找到对应的值。

  1. const hashTable = new HashTable();
  2. hashTable.put("Gandalf", "gandalf@email.com");
  3. hashTable.put("Tyrion", "tyrion@email.com");
  4. hashTable.put("Aaron", "aaron@email.com");
  5. hashTable.put("Ana", "ana@email.com");
  6. hashTable.put("Mindy", "mindy@email.com");
  7. hashTable.put("Paul", "paul@email.com");
  8. hashTable.print();
  9. console.log(hashTable.get("Tyrion"));
  10. console.log(hashTable.get("Aaron"));
  11. hashTable.remove("Tyrion");
  12. hashTable.print();

解析:

  1. // 链表的实现代码省略 可以查看之前的代码
  2. let ValuePair = function (key, value){
  3. this.key = key
  4. this.value = value
  5. this.toString = function(){
  6. return `[${this.key} - ${this.value}]`
  7. }
  8. }
  9. class HashTable {
  10. constructor(){
  11. this.table = []
  12. }
  13. /**
  14. * 散列函数
  15. * @param {*} key 键名
  16. */
  17. hashCode(key) {
  18. let hash = 0;
  19. for (let i = 0; i < key.length; i++) {
  20. hash += key.charCodeAt(i);
  21. }
  22. return hash % 37;
  23. }
  24. /**
  25. * 向散列表增加/更新一个新的项
  26. * @param {*} key 添加的键名
  27. * @param {*} value 添加的值
  28. */
  29. put (key, value) {
  30. let position = this.hashCode(key)
  31. if(this.table[position] == undefined){
  32. this.table[position] = new LinkedList()
  33. }
  34. this.table[position].append(new ValuePair(key, value))
  35. }
  36. /**
  37. * 根据键值从散列表中移除值
  38. * @param {*} key 移除的键名
  39. * @return {Boolean} 是否成功移除
  40. */
  41. remove (key) {
  42. let position = this.hashCode(key)
  43. if ( !key || this.table[position] === undefined ) return false
  44. let current = this.table[position].getHead()
  45. while(current.next){
  46. if(current.element.key === key){
  47. this.table[position].remove(current.element)
  48. if(this.table[position].isEmpty){
  49. this.table[position] = undefined
  50. }
  51. return true
  52. }
  53. current = current.next
  54. }
  55. }
  56. /**
  57. * 根据键值检索到特定的值
  58. * @param {*} key 查找的键名
  59. * @return {*} 查找的值
  60. */
  61. get (key) {
  62. let position = this.hashCode(key)
  63. if(!key || this.table[position] === undefined) return undefined
  64. let current = this.table[position].getHead()
  65. while(current.next()){
  66. if(current.element.key === key){
  67. return current.element.value
  68. }
  69. current = current.next
  70. }
  71. }
  72. /**
  73. * 打印散列表中已保存的值
  74. * @return {*} 散列表的值
  75. */
  76. print () {
  77. return this.table
  78. }
  79. }

五、实现一个线性探查的散列表

线性探查是解决散列表中冲突的另一种方法,当向表中某一个位置加入新元素的时候,如果索引为 index 的位置已经被占据了,就尝试 index+1 的位置。如果 index+1 的位置也被占据,就尝试 index+2,以此类推。

6.Dictionary&&HashTable - 图2

请实现散列表:

put(key,value):将 keyvalue 存在一个 ValuePair 对象中(即可定义一个包含 keyvalue 属性的 ValuePair 类)并分配到散列表。

get(key):返回键值对应的值,没有则返回 undefined

remove(key):从散列表中移除键值对应的元素。

提示:移除一个元素,只需要将其赋值为 undefined。

使用示例:

  1. const hashTable = new HashTable();
  2. hashTable.put("Gandalf", "gandalf@email.com");
  3. hashTable.put("Tyrion", "tyrion@email.com");
  4. hashTable.put("Aaron", "aaron@email.com");
  5. hashTable.put("Ana", "ana@email.com");
  6. hashTable.put("Mindy", "mindy@email.com");
  7. hashTable.put("Paul", "paul@email.com");
  8. hashTable.print();
  9. console.log(hashTable.get("Tyrion"));
  10. console.log(hashTable.get("Aaron"));
  11. hashTable.remove("Tyrion");
  12. hashTable.print();

解析:

  1. let ValuePair = function (key, value){
  2. this.key = key
  3. this.value = value
  4. this.toString = function(){
  5. return `[${this.key} - ${this.value}]`
  6. }
  7. }
  8. class HashTable {
  9. constructor(){
  10. this.table = []
  11. }
  12. /**
  13. * 散列函数
  14. * @param {*} key 键名
  15. */
  16. hashCode(key) {
  17. let hash = 0;
  18. for (let i = 0; i < key.length; i++) {
  19. hash += key.charCodeAt(i);
  20. }
  21. return hash % 37;
  22. }
  23. /**
  24. * 向散列表增加/更新一个新的项
  25. * @param {*} key 添加的键名
  26. * @param {*} value 添加的值
  27. */
  28. put (key, value) {
  29. let position = this.hashCode(key)
  30. if(this.table[position] == undefined){
  31. this.table[position] = new ValuePair(key, value)
  32. }else{
  33. let index = ++position
  34. while(this.table[index] !== undefined){
  35. index ++
  36. }
  37. this.table[index] = new ValuePair(key, value)
  38. }
  39. }
  40. /**
  41. * 根据键值从散列表中移除值
  42. * @param {*} key 移除的键名
  43. * @return {Boolean} 是否成功移除
  44. */
  45. remove (key) {
  46. let position = this.hashCode(key)
  47. if( !key || this.table[position] === undefined ) return undefined
  48. if(this.table[position].key === key){
  49. this.table[index] = undefined
  50. }else{
  51. let index = ++position
  52. while(
  53. this.table[index] === undefined ||
  54. this.table[index].key !== key
  55. ){
  56. index ++
  57. }
  58. if(this.table[index].key === key){
  59. this.table[index] = undefined
  60. }
  61. }
  62. }
  63. /**
  64. * 根据键值检索到特定的值
  65. * @param {*} key 查找的键名
  66. * @return {*} 查找的值
  67. */
  68. get (key) {
  69. let position = this.hashCode(key)
  70. if( !key || this.table[position] === undefined ) return undefined
  71. if(this.table[position].key === key){
  72. return this.table[position].value
  73. }else{
  74. let index = ++position
  75. while(
  76. this.table[index] === undefined ||
  77. this.table[index].key !== key
  78. ){
  79. index ++
  80. }
  81. if(this.table[index].key === key){
  82. return this.table[index].value
  83. }
  84. }
  85. }
  86. /**
  87. * 打印散列表中已保存的值
  88. * @return {*} 散列表的值
  89. */
  90. print () {
  91. return this.table
  92. }
  93. }

下周预告

下周将练习 Tree 的题目。

Author 王平安
E-mail pingan8787@qq.com
博 客 www.pingan8787.com
微 信 pingan8787
每日文章推荐 https://github.com/pingan8787/Leo_Reading/issues
ES小册 js.pingan8787.com

微信公众号

6.Dictionary&&HashTable - 图3