哈希表的一个简单实现

引子

先来看一个问题:

SCP基金会建立了一个收容点,为每一个收容的SCP都编号为“SCP-xxxx-xx”。编号的每一个SCP都有一个专门的文件描述其收容注意事项。请你设计一个程序,输入编号就可以找到对应的文件并且输出。

这个问题的核心在于,如何通过编号迅速找到对应的文件。如果我们使用fstream对象来代表文件的话,一个可能的思路是这样的:

struct SCP{
    fstream document;//文件
    string codeName;// 编号
};

int main(){
    vector<SCP> data;
    ...
    for(auto ix = data.begin(); ix != data.end(); ++ix){
        if(ix->codeName == key){
            cout << ix->document;
            break;
        }
    }
}

用一个数组存储打包的数据,然后通过搜索算法得到结果。这样做可行,但会花费O(n)的时间。有没有更快捷的方法,可以输入编号之后马上就得到对应的文件呢?

哈希表就是为此而生。

哈希表

哈希表(Hash Map)维护很多数据。每一对数据都由组成。其特性是只要输入键,就可以在O(1)时间内找到对应的值。如果使用一般未排序的线性表的话,搜索过程会花掉O(n)时间。刚才例子里的codeName就是键,document就是值。

因为这一种特性很像是查字典的时候,可以根据字典索引迅速找到对应的字词。所以Python提供的哈希表数据结构就叫做dictionary(字典)。

哈希函数

哈希表的特性来源于哈希函数,哈希函数具有把输入值在O(1)时间内转化为一个索引的能力。

输入
输入
输入
输入
输出
1123
HashFunction
TheWorld
3.1415926
各种数据
索引值0,1,2,3,4...

不过并非所有类型数据都可以作为输入的键值。Python把这样的数据类型形容为Unhashable。

哈希函数使用的这种算法就称作哈希算法。哈希算法还有许多其他特性,比如不能用输出的索引值逆推出输入值。但在这里我们只关心这一个特点。

这个能力对我们非常有用。我们只需要把编号输入给哈希函数,然后得到一个索引,只要把document存储到数组上索引指示的位置,之后我们就可以输入编号到哈希函数得到索引 ⇒ \Rightarrow 按照索引找到数组内对应的文件。全程只消耗O(1)时间,基本上可以认为做到了完美。

哈希函数の碰撞

但事与愿违,鸽巢原理指出,不论多高明的哈希算法,都无法保证每个键值都获得独一无二的索引。如果两个输入的键值获得了相同的索引,在数组上存储时就会发生冲突。不仅如此,这还会打破键与值之间一一对应的特性(因为好几个键获得了同一个索引),从而给查找也带来麻烦。

首先,我们需要在索引相同的情况下找到正确的值。所以把key和data绑定起来:

class HashNode {
public:
  int key;
  string data;// 演示只使用了string作为data类型,其实key和data的类型可以自己选择。
  HashNode() : key(0), data() {}
  HashNode(const int key, const string &data) : key(key), data(data) {}
};

其次,我们把HashNode储存在一个list内,再把list储存在一个vector内,这样就形成了一个二维结构,索引重复的就放入链表内。
在这里插入图片描述

图源《我的第一本算法书》,侵删。

简单的代码实现

因为本文核心是了解哈希表的结构,所以我并不会花大量时间去实现底层结构,点到为止即可。

所以我会使用STL容器来帮助我实现这样的数组-链表结构。

class HashNode {
public:
  int key;
  string data;
  HashNode() : key(0), data() {}
  HashNode(const int key, const string &data) : key(key), data(data) {}
};

// Seemed a little useless...but it may become nessasary when expanding the system...I guess... 
class HashList : public list<HashNode> {
  typedef unsigned  size_t;
public:
  size_t index;
  HashList() : list<HashNode>(), index(0) {}
  HashList(const HashList &rhs) : index(rhs.index), list<HashNode>(rhs) {}
};

class HashTable {
  typedef unsigned  size_t;

private:
  // Using list to tackle Hash collision.
  vector<HashList> _table;

public:
  // A Hash method, but not a good method.
  static size_t Hash(int key) {
    return (key*key*key+key*key+2*key+7)%50;
  }
  HashTable() : _table(50) {}
  HashTable(const HashTable &rhs) : _table(rhs._table) {}
  ~HashTable() = default;

  void insert(int key, const string &data) {
    _table[Hash(key)].push_back(HashNode(key, data));
  }

  void remove(int key) {
    auto ix(Hash(key));
    if (_table[ix].empty()) {
      return;
    }
    HashList::iterator it{_table[ix].begin()};
    while (it != _table[ix].end()) {
      if (it->key == key) {
        _table[ix].erase(it);
      }
      ++it;
    }
  }
  bool isEmpty() const {
    bool empty{true};
    for (auto &i : _table) {
      empty = i.empty();
    }
    return empty;
  }
  const string &operator[](int key) const {
    auto ix{Hash(key)};
    for (auto &i : _table[ix]) {
      if (i.key == key) {
        return i.data;
      }
    }
    throw "No that value";
  }
  string &operator[](int key) {
    auto ix{Hash(key)};
    for (auto &i : _table[ix]) {
      if (i.key == key) {
        return i.data;
      }
    }
    throw "No that value";
  }
};

由于以上代码是作者在极度疲倦状态下写成的,所以质量不佳,见谅。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值