zoukankan      html  css  js  c++  java
  • JavaScript数据结构——集合、字典和散列表

    集合、字典和散列表都可以存储不重复的值。
    在集合中,我们感兴趣的是每个值本身,并把它当作主要元素。在字典和散列表中,我们用 [键,值] 的形式来存储数据。
    集合(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 }
    Set.js

    (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 }
    Dictionary.js

    (3)散列方法声明:

    首先,使用数组来表示集合。
    三个基础方法:
    序号
    方法
    说明
    1
    put(key, value) 向散列表增加一个新的项(也能更新散列表)
    2
    remove(key) 根据键值从散列表中移除值
    3
    get(key)
    返回根据键值检索到的特定的值
    在实现这三个方法之前,要实现的第一个方法是散列函数,它是 HashTable 类中的一个私有方法。

    处理散列表的冲突:

    有时候,一些键会有相同的散列值。不同的值在散列表中对应相同位置的时候,我们称其为冲突。处理冲突有几种方法:分离链接、线性探查和双散列法。
    分离链接:包括为散列表的每一个位置创建一个链表并将元素存储在里面。它是是解决冲突的最简单的方法,但是它在 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
    HashTable.js

    参考书籍:《学习JavaScript数据结构与算法》

    Scoop It and Enjoy the Ride!
  • 相关阅读:
    一本通课后练习 / 高手训练
    毒瘤 dp 题做题记录
    埃及分数
    CF340E Iahub and Permutations
    NOI2020 SDOI 爆零记
    Codeforces *1400-1600 做题记录
    Codeforces Round #636 (Div 3) 题解
    Codeforces Round #634 (Div 3) 题解
    洛谷 P4231 三步必杀
    【洛谷】【线段树+位运算】P2574 XOR的艺术
  • 原文地址:https://www.cnblogs.com/Ruth92/p/5296606.html
Copyright © 2011-2022 走看看