hash_map的簡潔實現


hash_map的簡潔實現

 

hash_map是經常被使用的一種數據結構,而其實現方式也是多種多樣。如果要求我們使用盡可能簡單的方式實現hash_map,具體該如何做呢?

我們知道hash_map最重要兩個概念是hash函數和沖突解決算法。hash_map-值之間的映射關系,hash函數將鍵映射為內存地址,沖突解決算法用於解決不同的鍵映射為相同地址時候的情況。

數據結構和算法導論中介紹了大量的hash函數和沖突解決算法,如果選擇實現精簡的hash_map,那么可以選擇“除留取余法”作為hash函數,選擇“開散列地址鏈”作為沖入解決算法。這樣的選擇不僅使得hash_map實現簡單,而且有很高的查詢效率。其設計結構如下:

基於地址鏈的開散列哈希表實現

如圖所示,鍵347通過除留取余法得到地址鏈的索引,然后將347對應的元素插入到該地址鏈的尾端。

為了實現地址鏈,我們需要定義鏈表節點類型Node。為了簡化問題,我們將鍵值都定義為int類型。

struct Node // 地址鏈節點
{
     int key; //
     int value; //
    Node*next; // 鏈接指針
    Node( int k, int v):key(k),value(v),next(NULL){}
};

value用於記錄節點數組,key在檢索節點時會被使用,next保存鏈表關系。

基於此,定義hash_map類型SimHash

 

#define SIZE 100  // 地址鏈個數,足夠大
class SimHash
{
    Node**map; // 地址鏈數組
    size_t hash( int key) // hash函數,除留取余法
    {
         return key%SIZE;
    }
public:
    SimHash()
    {
        map= new Node*[SIZE];
         for(size_t i= 0;i<SIZE;i++)map[i]=NULL; // 初始化數組為空
    }
    ~SimHash()
    {
         for(size_t i= 0;i<SIZE;i++) // 清除所有節點
        {
            Node*p;
             while(p=map[i])
            {
                map[i]=p->next;
                delete p;
            }
        }
        delete[] map; // 清除數組
    }
     void insert( int key, int value)
    {
        Node*f=find(key); // 插入前查詢
         if(f)
        {
            f->value=value; // 存在鍵則覆蓋
             return;
        }
        Node*p=map[hash(key)]; // 確定地址鏈索引
        Node*q= new Node(key,value); // 創建節點
         while(p&&p->next)p=p->next; // 索引到地址鏈末端
         if(p)p->next=q; // 添加節點
         else map[hash(key)]=q; // 地址鏈的第一個節點
    }
     void remove( int key)
    {
        Node*p=map[hash(key)]; // 確定地址鏈索引
        Node*q=NULL; // 前驅指針
         while(p)
        {
             if(p->key==key) // 找到鍵
            {
                 if(q)q->next=p->next; // 刪除節點
                 else map[hash(key)]=p->next; // 刪除地址鏈的最后一個節點
                delete p;
                 break;
            }
            q=p;
            p=p->next;
        }
    }
    Node* find( int key)
    {
        Node*p=map[hash(key)]; // 確定地址鏈索引
         while(p)
        {
             if(p->key==key) break; // 查詢成功
            p=p->next;
        }
         return p;
    }
};

首先,我們需要一個Node**類型的指針map記錄地址鏈數組的地址,至於數組大小,我們給一個較大的值(這里設置為100),為了簡化問題,不需要考慮插入時表滿等情況。在構造函數和析構函數內對map內存分配和撤銷。另外,節點的內存是動態增加的,因此析構時需要單獨處理每個地址鏈表。

對於數據結構,最關鍵的便是插入、刪除、檢索操作,為此定義操作insertremovefind

檢索操作實現時,首先通過hash函數定位地址鏈表的索引,然后在地址鏈表上檢索鍵是否存在即可。

插入操作實現時,首先會檢索鍵是否存在,如果存在則僅僅更新對應節點的數據即可,否則創建新的節點,插入到鏈表的結尾。對於空鏈表需要做特殊處理。

刪除操作實現時,需要使用兩個指針記錄節點的位置信息,當遇到滿足鍵的節點時,就將該節點從鏈表內刪除即可。如果刪除鏈表的第一個節點,需要做特殊處理。

以上,便是一個較簡單的hash_map的實現了,代碼行約80行左右,當然這肯定不是最簡單的,如果你有興趣可以再做進一步的簡化。

如果考慮哈希表的鍵值類型、特殊鍵類型的hash映射(字符串類型鍵如何映射為數值)、特殊鍵類型的比較處理(怎么比較兩個自定義類型鍵是否相等)、索引運算重載這些問題的話,hash_map的實現就變得復雜了。不過這些在STL內實現的比較完整,若你感興趣可以多做了解。這里我給出自己的一個考慮以上問題的簡單hash_map實現。

 

struct str_hash
{
    size_t  operator()( const  char* str) const
    {
        size_t ret= 0;
         while(*str)ret=(ret<< 5)+*str++;
         return ret;
    }
};

struct str_cmp
{
     bool  operator()( const  char* str1, const  char* str2) const
    {
         while(*str1&&*str2)
        {
             if(*str1++!=*str2++) return  false;
        }
         return !*str1&&!*str2;
    }
};

template< class K, class T>
class Hashnode
{
public:
    Hashnode(K k,T d):key(k),data(d),next(NULL)
    {}
    K key;
    T data;
    Hashnode*next;
};


template< class K, class T, class H, class C>
class Hashmap
{
    Hashnode<K,T>**map; // hash鏈表數組
    size_t div; // 數組大小
    size_t hash(K key) // hash函數,獲取桶號——除留余數法
    {
         return h(key)%div;
    }
    H h;
    C c;
    Hashnode<K,T>* _find(K key)
    {
        size_t pos=hash(key); // 獲取桶號
         for(Hashnode<K,T>*p=map[pos];p;p=p->next)
        {
             if(c(p->key,key)) // 找到了key
            {
                 return p;
            }
        }
         return NULL;
    }
public:
    size_t size; // hash表容量
    size_t count; // 元素個數
    Hashmap(size_t sz= 6):size( 6),div( 2),count( 0)
    {
         if(sz> 6)
        {
            size=sz;
            div=size/ 3;
        }
        map= new Hashnode<K,T>*[div];
         for(size_t i= 0;i<div;i++)map[i]=NULL;
    }

    ~Hashmap()
    {
         for(size_t i= 0;i<div;i++)
        {
             while( true)
            {
                Hashnode<K,T>*p=map[i];
                 if(p)
                {
                    map[i]=p->next;
                    delete p;
                }
                 else  break;
            }
        }
        delete[] map;
    }

     void insert(K key,T data)
    {
         if(count>=size) // 表滿,增加空間
        {
            Hashnode<K,T>**oldmap=map;
            size_t olddiv=div;
            size*= 2;
            div=size/ 3;
            map= new Hashnode<K,T>*[div];
             for(size_t i= 0;i<div;i++)map[i]=NULL;
             for(size_t i= 0;i<olddiv;i++)
            {
                 for(Hashnode<K,T>*p=oldmap[i],*t;p;p=t)
                {
                    t=p->next;
                    p->next=NULL; // 消除后繼信息
                    size_t pos=hash(p->key); // 重新映射
                    Hashnode<K,T>*q;
                     for(q=map[pos];q&&q->next;q=q->next);
                     if(!q)map[pos]=p;
                     else q->next=p;
                }
            }
            delete oldmap;
        }
        Hashnode<K,T>*p=_find(key); // 已經存在,替換數據
         if(p)p->data=data;
         else
        {
            size_t pos=hash(key); // 獲取桶號
            Hashnode<K,T>*p;
             for(p=map[pos];p&&p->next;p=p->next); // 索引到最后位置
            Hashnode<K,T>*q= new Hashnode<K,T>(key,data);
             if(!p)map[pos]=q; // 插入數據節點
             else p->next=q;
            count++;
        }        
    }

     void remove(K key)
    {
         if(count<=size/ 2) // 元素少於一半
        {
            Hashnode<K,T>**oldmap=map;
            size_t olddiv=div;
            size/= 2;
            div=size/ 3;
            map= new Hashnode<K,T>*[div];
             for(size_t i= 0;i<div;i++)map[i]=NULL;
             for(size_t i= 0;i<olddiv;i++)
            {
                 for(Hashnode<K,T>*p=oldmap[i],*t;p;p=t)
                {
                    t=p->next;
                    p->next=NULL; // 消除后繼信息
                    size_t pos=hash(p->key); // 重新映射
                    Hashnode<K,T>*q;
                     for(q=map[pos];q&&q->next;q=q->next);
                     if(!q)map[pos]=p;
                     else q->next=p;
                }
            }
            delete oldmap;
        }
        size_t pos=hash(key); // 獲取桶號
         for(Hashnode<K,T>*p=map[pos],*q=NULL;p;p=p->next)
        {
             if(c(p->key,key)) // 找到了key
            {
                 if(q)q->next=p->next;
                 else map[pos]=p->next;
                delete p;
                count--;
                 break;
            }
            q=p;
        }
    }

    T&  get(K key)
    {
        Hashnode<K,T>*p=_find(key);
         if(p) return p->data;
         // 沒有key,插入map[key]=0
        insert(key, 0);
         return  get(key);
    }

    T&  operator[](K key)
    {
         return  get(key);
    }

     bool find(K key)
    {
         return !!_find(key);
    }
};
int main()
{
    Hashmap< char*, int,str_hash,str_cmp> hashmap;
    hashmap[ " A "]= 1;
    hashmap[ " B "]= 2;
    hashmap[ " C "]= 3;
    hashmap[ " D "]= 4;
    hashmap[ " E "]= 5;
    hashmap[ " F "]= 6;
    hashmap[ " G "]= 7;
    hashmap[ " H "]= 8;
    cout<<hashmap[ " A "]<< "   "<<hashmap[ " B "]<<endl;
    cout<<hashmap[ " C "]<< "   "<<hashmap[ " D "]<<endl;
    cout<<hashmap[ " E "]<< "   "<<hashmap[ " F "]<<endl;
    cout<<hashmap[ " G "]<< "   "<<hashmap[ " H "]<<endl;
    cout<<hashmap.size<<endl;
    hashmap.remove( " A ");
    hashmap.remove( " B ");
    hashmap.remove( " C ");
    hashmap.remove( " D ");
    hashmap.remove( " E ");
    cout<<hashmap.size<<endl;
    cout<<hashmap[ " A "]<< "   "<<hashmap[ " B "]<<endl;
    cout<<hashmap[ " C "]<< "   "<<hashmap[ " D "]<<endl;
    cout<<hashmap[ " E "]<< "   "<<hashmap[ " F "]<<endl;
    cout<<hashmap[ " G "]<< "   "<<hashmap[ " H "]<<endl;
    cout<<hashmap.size<<endl;
     return  0;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM