【數據結構】哈希表(線性探測法)

哈希表是一種搜索結構,當數據量大時,哈希搜索的效率高,平均時間複雜度O(1)。

【哈希查找】:
(1)在插入時,根據待插入元素的關鍵碼,以此函數計算出該元素的存儲位置並按此位置進行存放。
(2)在搜索時,對元素的關鍵碼進行同樣的計算,把求得的函數值當作元素的存儲位置,在結構中按此位置取元素比較,若關鍵碼相等,則搜索成功。
該方式即散列方法(Hash Method),在散列方法中使用的轉換函數叫着散列函數(Hash function),構造出來的結構叫散列表(Hash Table)。用該方法進行搜索不必進行多次關鍵碼的比較,因此搜索的速度比較快。

【哈希衝突】:
對於兩個數據元素的關鍵字Ki和Kj(i != j),有Ki != Kj ( i != j) ,但HashFun( Ki ) ==HashFun( Kj ) ,將該種現象稱爲哈希衝突或哈希碰撞。

哈希表元素的插入:
這裏寫圖片描述

【散列函數】:

常見的求哈希值的方法:

1.直接定址法:
取關鍵字的某個線性函數爲散列地址:Hash(Key)= A*Key + B
優點:簡單、均勻
缺點:需要事先知道關鍵字的分佈情況
(適合查找比較小且連續的情況)

2.除留餘數法:
設散列表中允許的地址數爲m,取一個不大於m,但最接近或者等於m的質數,按照哈希函數:Hash( key ) = key % p ( p <= m) ,將關鍵碼轉換成哈希地址。

3.平方取中法
4.摺疊法
5.隨機數法
6.數學分析法

【散列衝突處理方法】:
閉散列法:
在元素插入時遇到哈希衝突,我們可選擇線性探查法處理衝突,還可以選擇二次探查法處理衝突。
這裏我們分析下線性探查法:
給出一組元素,它們的關鍵碼爲:37,25,14,36,49,68,57,11,散列表爲HT[12],表的大小 m=12 ,假設採用Hash(key)= key % p ;(p=11)11是最接近m的質數,就有:
這裏寫圖片描述

添加元素時,使用散列函數確定元素的插入位置,如果此空間有值:
1.該值是所要插入元素的關鍵碼,不進行插入。
2.產生衝突,依次查看其後的下一個桶,如果發現空位置插入新元素

注意:
散列表的載荷因子:a = 插入元素個數 / 散列表的長度

a是散列表裝滿程度的標誌因子。對於開放地址法,載荷因子非常重要,應嚴格限制在 0.7~0.8 以下。超過 0.8 ,查表時的CPU緩存按照指數曲線上升。

【代碼實現】:
1.用vector開闢出一段空間來存放元素。
2.每個節點都有三種狀態 EXIST(存在) 、DELETE(刪除)、EMPTY(空) ,初始化時每個節點的狀態設置爲EMPTY

【Hash.h】

#include<iostream>
#include<vector>
#include<assert.h>
#include<utility>
#include<cstring>

using namespace std;

//定義仿函數
template<class K>
struct _HashFunc
{
    size_t operator()(const K& key)
    {
        return key;
    }
};

//特化string的版本
template<>
struct _HashFunc<string>
{
    static size_t BKDRHash(const char* str)
    {
        size_t seed = 131;    // 31 131 1313 13131 131313
        size_t hash = 0;
        while (*str)
        {
            hash = hash*seed + (*str++);
        }
        return (hash & 0x7fffffff);
    }
    size_t operator()(const string& key)
    {
        return BKDRHash(key.c_str());    //c_str()返回的是一個const char*  類型的字符串
    }
};

enum Status
{
    EXIST,
    DELETE,
    EMPTY
};

template<class K,class V>
struct HashTableNode
{
    //HashTableNode<K,V>* _pNode;
    K _key;
    V _value;
    Status _status;
    HashTableNode(const K& key=K(),const V& value=V())
        :_key(key)
        , _value(value)
        , _status(EMPTY)
    { }

};

//素數表,表內爲哈希表的容量,素數降低哈希衝突
const int _PrimeSize = 28;                
static const unsigned long _PrimeList[_PrimeSize] =
{
    53ul, 97ul, 193ul, 389ul, 769ul,1543ul, 3079ul, 6151ul,
    12289ul, 24593ul,49157ul, 98317ul, 196613ul, 393241ul,
    786433ul,1572869ul, 3145739ul, 6291469ul, 12582917ul,
    25165843ul,50331653ul, 100663319ul, 201326611ul, 
    402653189ul,805306457ul,1610612741ul, 3221225473ul,
    4294967291ul
};

template<class K,class V,class HashFunc=_HashFunc<K>>
class HashTable
{
    typedef HashTableNode<K, V> Node;
public:
    HashTable()
    {}
    HashTable(size_t size)
    {
        assert(size > 0);
        _v.resize(size);
        _size = 0;
    }
    //將K值轉換成哈希值
    size_t _HashTableFunc(const K& key)
    {
        HashFunc hf;    //定義一個HashFunc的變量hf
        size_t hash = hf(key);  //用變量hf調用HashFunc的仿函數,返回對應的整型
        return hash% _v.size(); //算出哈希值,並返回
    }

    pair<Node*, bool> Insert(const K& key, const V& value)
    {
        //檢查是否需要擴容
        CheckCapacity();

        //對K值進行取餘,判斷插入位置
        size_t index = _HashTableFunc(key);
        //如果存在,則循環着繼續找
        while (_v[index]._status !=EMPTY)
        {
            index++;
            if (index == _v.size())
                index = 0;
        }
        _v[index]._key = key;
        _v[index]._value = value;
        _v[index]._status = EXIST;

        _size++;
        return make_pair<Node*, bool>(&_v[index], true);
    }

    Node* find(const K& key)        //查找位置
    {
        size_t index = _HashTableFunc(key);

        //如果存在,則繼續尋找
        while (_v[index]._status == EXIST)
        {
            //若相等,判斷狀態是否是刪除
            //若刪除,則沒找到,返回空
            //若沒刪除,則返回該位置的地址
            if (_v[index]._key == key)
            {
                if (_v[index]._status == DELETE)
                    return NULL;

                return &_v[index];
            }
            index++;
            if (index == _size)
                index = 0;
        }
        return NULL;
    }

    void Delete(const K& key)
    {
        //刪除僅需要將狀態修改
        Node* delNode = find(key);

        if (delNode)
            delNode->_status = DELETE;
    }
private:
    //交換兩個哈希表
    void Swap(HashTable<K, V>& h)
    {
        swap(_v, h._v);
        swap(_size, h._size);
    }

    void CheckCapacity()
    {
        //如果_v爲空,則擴容到11
        if (_v.empty())
        {
            _v.resize(11);
        }

        //如果超過比例係數,則需要擴容
        if (_size * 10 / _v.size() >= 7)
        {
            size_t index = 0;
            while (_PrimeList[index] < _v.size())
            {
                index++;
            }
            size_t newSize = _PrimeList[index];
            HashTable<K, V> newh(newSize);    //新近一個哈希表
            for (size_t i = 0; i < _v.size(); i++)  //將舊的哈希表中的元素重新插入到新的哈希表
            {
                if (_v[i]._status==EXIST)
                newh.Insert(_v[i]._key, _v[i]._value);
            }
            //交換兩個哈希表
            Swap(newh);
        }
    }

private:
    vector<Node> _v;
    size_t _size;

};

【測試存入整型數據】

void test()
{
    int arr[] = { 3, 7, 12, 23, 45, 67, 13, 43 };
    int size = sizeof(arr) / sizeof(arr[0]);
    HashTable<int, int> h1(11);
    for (int i = 0; i < size; i++)
    {
        h1.Insert(arr[i], arr[i]+ 3);
    }
}

【測試以字符串爲關鍵碼存入】

void test2()
{
    HashTable<string, string> h2(11);
    h2.Insert("abs", "1111");
    h2.Insert("222","2222");
    h2.Insert("223", "2223");
    h2.Insert("224", "2224");
    h2.Insert("225", "2225");
}

【test.cpp】

#include"Hash.h"
int main()
{
    //test();
    test2();
    return 0;
}
發佈了70 篇原創文章 · 獲贊 92 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章