資料結構與演算法——雜湊表類的C++實現(分離連結雜湊表)

readyao發表於2016-04-12

雜湊表簡介:

雜湊表的實現常被稱為雜湊。雜湊是一種用於以常數平均時間執行插入、刪除和查詢的技術。

雜湊的基本思想:

理想的雜湊表資料結構只不過是一個包含一些項的具有固定大小的陣列。(表的大小一般為素數)
設該陣列的大小為TbaleSize,我們向該雜湊表中插入資料,首先我們將該資料用一個函式(雜湊函式)對映一個數值x(位於0到TbaleSize1-1之間);然後將該資料插入到雜湊表的第x的單元。(如果有多個資料對映到同一個數值,這個時候就會發生衝突)

雜湊函式介紹:

為了避免雜湊函式生成的值不是均勻分佈,有一個比較好的雜湊函式可以使用。
在該雜湊函式中涉及資料中所有的字元,並且一般可以分佈的很好,它計算的是0到KeySize-1進行求和Key[KeySize-i-1]*(37^i);

下面是該雜湊函式的實現:

/****************************************************************
*   函式名稱:hash(const string & key, int tableSize)
*   功能描述: 根據鍵值求個hash值 
*   引數列表: key 資料項的鍵值 
*             tableSize 雜湊表的大小
*   返回結果:返回hash值
*****************************************************************/
int hash(const string & key, int tableSize)
{
    int hashVal = 0;

    //用雜湊函式的那個公式求和
    for(int i = 0; i < key.length(); ++i)
        hashVal = 37*hashVal + key[i];

    hashVal %= tableSize;//求得的hash值

    if(hashVal < 0)
        hashVal += tableSize;

    return hashVal;
}


雜湊函式完成之後下面就是解決hash值的衝突問題。

解決衝突的方法主要有:分離連結法和開放定址法

分離連結法:

思路是做法是將雜湊到同一個值的所有元素保留到一個連結串列中,該連結串列可以直接使用STL中的list類實現(該連結串列是雙向連結串列)。
此時雜湊表的結構為: vector<list<HashedObj> > v;



雜湊表類的主要成員函式:


bool containes(const HashedObj & x) const;//判斷是否包含資料項x
void makeEmpty();//清空雜湊表
bool isEmpty();
bool insert(const HashedObj & x);//插入項x
bool remove(const HashedObj & x);//刪除項x

void print();//輸出雜湊表中的內容
HashedObj findElement(const HashedObj & x);//根據名字查詢資料項
void rehash();//再雜湊
int myhash(const HashedObj & x) const;//雜湊函式
int nextPrime(int n);//求的距離N最近的一個素數

主要成員函式介紹:

1、再雜湊rehash():

如果雜湊表的大小太小了就要進行再雜湊,在分離連結法中很簡單,就是將雜湊表的大小擴大原來的兩倍。
然後將原來雜湊表的內容拷貝到新的雜湊表中。nextPrime函式是為了求的一個素數,因為一般我們讓雜湊表的大小為一個素數。

/****************************************************************
*   函式名稱:rehash()
*   功能描述: 擴大雜湊表的大小
*   引數列表: 無
*   返回結果:無 
*****************************************************************/
template<typename HashedObj>
void HashTable<HashedObj>::rehash()
{
    vector<list<HashedObj> > oldLists = theLists;
    
    //建立一個新的大小為原來兩倍大小的雜湊表
    theLists.resize(nextPrime(2 * theLists.size()));

    for(int i = 0; i < theLists.size(); ++i)
        theLists[i].clear();

    //複製雜湊表
    for(int i = 0; i < oldLists.size(); ++i){
        typename  list<HashedObj>::iterator it = oldLists[i].begin();
        while(it != oldLists[i].end())
            insert(*it++);
    }
}

2、雜湊函式myhash()

該函式會呼叫hash(key),使用了函式過載,目的是將不同型別的資料進行hash計算求hash值

/****************************************************************
*   函式名稱:myhash(const HashedObj & key)
*   功能描述: 根據鍵值求個hash值 
*   引數列表: key 資料項的鍵值 
*   返回結果:返回hash值
*****************************************************************/
template<typename HashedObj>
int HashTable<HashedObj>::myhash(const HashedObj & key) const
{
    int hashVal = hash(key);
    
    hashVal %= theLists.size();

    if(hashVal < 0)
        hashVal += theLists.size();

    return hashVal;
}

3、插入函式insert(const HashedObj & x)

/****************************************************************
*   函式名稱:insert(const HashedObj & x)
*   功能描述: 在雜湊表中插入元素x,如果插入項已經存在,則什麼都不做。
*             否則將其放在表的前端
*   引數列表: x資料項
*   返回結果:插入成功返回true, 否則返回false
*****************************************************************/
template<typename HashedObj>
bool HashTable<HashedObj>::insert(const HashedObj & x)
{
    list<HashedObj> & whichList = theLists[myhash(x)];
    
    if(find(whichList.begin(), whichList.end(), x) != whichList.end())
        return false;

    whichList.push_back(x);

    //rehash
    if(++currentSize > theLists.size())
        rehash();//擴充表的大小

    return true;

}

4、刪除函式remove(const HashedObj & x)

/****************************************************************
*   函式名稱:containes(const HashedObj & x) const
*   功能描述: 判斷雜湊表是否包含值為x的元素 
*   引數列表: x資料項
*   返回結果:如果包括x則返回true,否則返回false
*****************************************************************/
template<typename HashedObj>
bool HashTable<HashedObj>::remove(const HashedObj & x)
{
    list<HashedObj> & whichList = theLists[myhash(x)];

    typename list<HashedObj>::iterator it = find(whichList.begin(), whichList.end(), x);

    if(it == whichList.end())
        return false;

    whichList.erase(it);//刪除元素x
    --currentSize;
    return true;
}


定義資料類:

class Employee{
    public:
        Employee(){name=""; salary=0.0; seniority=0;};
        Employee(const string & n, double sal, int sen):name(n), salary(sal), seniority(sen){}
        //獲得該類的name成員
        const string & getName() const
        { return name; }

        //過載==運算子 
        bool operator==(const Employee & rhs) const
        { return getName() == rhs.getName(); }

        //過載!=運算子 
        bool operator!=(const Employee & rhs) const
        { return !(*this == rhs); }

        friend ostream & operator<<(const ostream & os, const Employee & e){
            cout << "name: " << e.name << ",\tsalary: " << e.salary << ",\tseniority: " << e.seniority << endl;
        }
    private:
        string name;
        double salary;
        int seniority;
};

將該類的物件插入到雜湊表中進行測試。在main函式可以看到。

下面是main函式,主要是對雜湊表類進行測試。


int main()
{
    Employee e1("linux", 101.00, 1);
    Employee e2("ever", 102.00, 2);
    Employee e3("peter", 103.00, 3);
    Employee e4("may", 104.00, 4);
    Employee e5("usa", 105.00, 5);
    Employee e6("sal", 106.00, 6);
    Employee e7("usa", 107.00, 7);//和上面的值重複,所以這個值會被忽略
    Employee e8("jan", 108.00, 8);
    Employee e9("kro", 109.00, 9);
    Employee e10("bei", 110.00, 10);
    
    Employee e12("bbb", 110.00, 10);

    vector<Employee> v;
    v.push_back(e1);
    v.push_back(e2);
    v.push_back(e3);
    v.push_back(e4);
    v.push_back(e5);
    v.push_back(e6);
    v.push_back(e7);
    v.push_back(e8);
    v.push_back(e9);
    v.push_back(e10);

    cout << "v: " << endl;
    for(unsigned i = 0; i < v.size(); ++i)
        cout << v[i];
    cout << endl;

    HashTable<Employee> hashTable;
    
    for(unsigned i = 0; i < v.size(); ++i)
        hashTable.insert(v[i]);

    hashTable.print();

    cout << endl;
    //測試查詢函式findElement
    cout << "測試查詢函式findElement: " << endl;
    Employee e11 = hashTable.findElement(e12);
    cout << "e11 = " << e11;
    Employee e13 = hashTable.findElement(e10);
    cout << "e13 = " << e13;
    cout << endl;

    cout << "測試包含函式containes: " << endl; 
    if(hashTable.containes(e10))
        cout << "containe e10" << endl;
    else 
        cout << "not containe e10" << endl;

    if(hashTable.containes(e11))
        cout << "containe e11" << endl;
    else 
        cout << "not containe e11" << endl;
        
    cout << "測試remove():" << endl;
    hashTable.remove(e10);
    if(hashTable.containes(e10))
        cout << "containe e10" << endl;
    else 
        cout << "not containe e10" << endl;
    cout << endl;

    cout << "測試isEmpty(): " << endl;
    if(hashTable.isEmpty())
        cout << "hashTable is Empty " << endl;
    else
        cout << "hashTable is not Empty " << endl;
    cout << endl;

    cout << "測試makeEmpty(): " << endl;
    hashTable.makeEmpty();
    if(hashTable.isEmpty())
        cout << "hashTable is Empty " << endl << endl;
    else
        cout << "hashTable is not Empty " << endl;
    cout << endl;


    return 0;
}


下面是該雜湊表類的原始碼:


/*************************************************************************
	> File Name: hashTable.cpp
	> Author: 
	> Mail: 
	> Created Time: 2016年04月12日 星期二 10時35分14秒
 ************************************************************************/

#include <iostream>
#include <vector>
#include <list>
#include <algorithm>
using namespace std;


/****************************************************************
*   資料類名稱:Employee
*   內容描述: 作為雜湊表的資料專案
*****************************************************************/

class Employee{
    public:
        Employee(){name=""; salary=0.0; seniority=0;};
        Employee(const string & n, double sal, int sen):name(n), salary(sal), seniority(sen){}
        //獲得該類的name成員
        const string & getName() const
        { return name; }

        //過載==運算子 
        bool operator==(const Employee & rhs) const
        { return getName() == rhs.getName(); }

        //過載!=運算子 
        bool operator!=(const Employee & rhs) const
        { return !(*this == rhs); }

        friend ostream & operator<<(const ostream & os, const Employee & e){
            cout << "name: " << e.name << ",\tsalary: " << e.salary << ",\tseniority: " << e.seniority << endl;
        }
    private:
        string name;
        double salary;
        int seniority;
};

/****************************************************************
*   函式名稱:hash(const HashedObj & key)
*   功能描述: 根據鍵值求個hash值,這個函式是根據一個特定的數學公式 
*   引數列表: key 資料項的鍵值 
*   返回結果:返回一個通過雜湊函式求得的值
*****************************************************************/
int hash(const string & key)
{
    int hashVal = 0;

    //用雜湊函式的那個公式求和
    for(int i = 0; i < key.length(); ++i)
        hashVal = 37*hashVal + key[i];

    return hashVal;
}

/****************************************************************
*   函式名稱:hash(const HashedObj & key)
*   功能描述: 根據鍵值求個hash值,這個函式是根據一個特定的數學公式 
*   引數列表: key 資料項的鍵值 
*   返回結果:返回一個通過雜湊函式求得的值
*****************************************************************/
int hash(const Employee & item)
{
    return hash(item.getName());
}


/****************************************************************
*   雜湊表類名稱:HashTable
*   內容描述: 雜湊表類
*****************************************************************/
template<typename HashedObj>
class HashTable{
    public:
        explicit HashTable(int size = 11):theLists(size), currentSize(0){}
        ~HashTable();

        bool containes(const HashedObj & x) const;//判斷是否包含資料項x
        void makeEmpty();//清空雜湊表
        bool isEmpty();
        bool insert(const HashedObj & x);//插入項x
        bool remove(const HashedObj & x);//刪除項x

        void print();//輸出雜湊表中的內容
        HashedObj findElement(const HashedObj & x);//根據名字查詢資料項


    private:
        vector<list<HashedObj> > theLists;//雜湊表的結構,theLists大小預設初始化為11
        int currentSize;//雜湊表中當前存放的元素個數
    private:
        void rehash();//再雜湊
        int myhash(const HashedObj & x) const;//雜湊函式
        int nextPrime(int n);//求的距離N最近的一個素數
};

/****************************************************************
*   函式名稱:findElement(const HashedObj & x) const
*   功能描述: 查詢元素x 
*   引數列表: x是要查詢的元素
*   返回結果:如果找到則返回該元素,否則返回一個預設構造的元素值
*****************************************************************/
template<typename HashedObj>
HashedObj HashTable<HashedObj>::findElement(const HashedObj & x)
{
    list<HashedObj> & whichList = theLists[myhash(x)];
    
    typename list<HashedObj>::iterator it = find(whichList.begin(), whichList.end(), x);
    if(it != whichList.end())
        return *it;
    else{
        HashedObj obj;//返回一個成員值為0的物件
        return obj;
    }
}


/****************************************************************
*   函式名稱:print()
*   功能描述: 輸出雜湊表中的內容
*   引數列表: 無 
*   返回結果:無
*****************************************************************/
template<typename HashedObj>
void HashTable<HashedObj>::print()
{
    cout << "輸出雜湊表中的內容: " << endl;
    for(unsigned i = 0; i < theLists.size(); ++i){
        cout << i << ": " << endl;
        for(typename list<HashedObj>::iterator it = theLists[i].begin(); it != theLists[i].end(); ++it){
            cout << *it; 
        }
    }
}

/****************************************************************
*   函式名稱:isEmpty()
*   功能描述: 判斷雜湊表是否為空
*   引數列表: 無 
*   返回結果:無
*****************************************************************/
template<typename HashedObj>
bool HashTable<HashedObj>::isEmpty()
{
    return currentSize == 0;
}

/****************************************************************
*   函式名稱:makeEmpty()
*   功能描述: 清空雜湊表 
*   引數列表: 無 
*   返回結果:無
*****************************************************************/
template<typename HashedObj>
void HashTable<HashedObj>::makeEmpty()
{
    for(int i = 0; i < theLists.size(); ++i)
        theLists[i].clear();

    currentSize = 0;//當前元素個數設為0
}

/****************************************************************
*   函式名稱:containes(const HashedObj & x) const
*   功能描述: 判斷雜湊表是否包含值為x的元素 
*   引數列表: x資料項
*   返回結果:如果包括x則返回true,否則返回false
*****************************************************************/
template<typename HashedObj>
bool HashTable<HashedObj>::containes(const HashedObj & x) const
{
    const list<HashedObj> & whichList = theLists[myhash(x)];
    return find(whichList.begin(), whichList.end(), x) != whichList.end();
}


/****************************************************************
*   函式名稱:containes(const HashedObj & x) const
*   功能描述: 判斷雜湊表是否包含值為x的元素 
*   引數列表: x資料項
*   返回結果:如果包括x則返回true,否則返回false
*****************************************************************/
template<typename HashedObj>
bool HashTable<HashedObj>::remove(const HashedObj & x)
{
    list<HashedObj> & whichList = theLists[myhash(x)];

    typename list<HashedObj>::iterator it = find(whichList.begin(), whichList.end(), x);

    if(it == whichList.end())
        return false;

    whichList.erase(it);//刪除元素x
    --currentSize;
    return true;
}

/****************************************************************
*   函式名稱:insert(const HashedObj & x)
*   功能描述: 在雜湊表中插入元素x,如果插入項已經存在,則什麼都不做。
*             否則將其放在表的前端
*   引數列表: x資料項
*   返回結果:插入成功返回true, 否則返回false
*****************************************************************/
template<typename HashedObj>
bool HashTable<HashedObj>::insert(const HashedObj & x)
{
    list<HashedObj> & whichList = theLists[myhash(x)];
    
    if(find(whichList.begin(), whichList.end(), x) != whichList.end())
        return false;

    whichList.push_back(x);

    //rehash
    if(++currentSize > theLists.size())
        rehash();//擴充表的大小

    return true;

}


/****************************************************************
*   函式名稱:~HashTable()
*   功能描述: 解構函式
*   引數列表: 無
*   返回結果:無
*****************************************************************/
template<typename HashedObj>
HashTable<HashedObj>::~HashTable()
{
    
}

/****************************************************************
*   函式名稱:nextPrime(int n)
*   功能描述: 獲得距離n最近的一個素數 
*   引數列表: n表示數值 
*   返回結果:返回一個素數 
*****************************************************************/
template<typename HashedObj>
int HashTable<HashedObj>::nextPrime(int n)
{
    int i;

    if(n % 2 == 0)
        n++;

    for(; ; n += 2){
        for(i = 3; i*i <= n; i += 2)
            if(n % i == 0)
                goto ContOuter;
        return n;
        ContOuter: ;
    }
}
/****************************************************************
*   函式名稱:rehash()
*   功能描述: 擴大雜湊表的大小
*   引數列表: 無
*   返回結果:無 
*****************************************************************/
template<typename HashedObj>
void HashTable<HashedObj>::rehash()
{
    vector<list<HashedObj> > oldLists = theLists;
    
    //建立一個新的大小為原來兩倍大小的雜湊表
    theLists.resize(nextPrime(2 * theLists.size()));

    for(int i = 0; i < theLists.size(); ++i)
        theLists[i].clear();

    //複製雜湊表
    for(int i = 0; i < oldLists.size(); ++i){
        typename  list<HashedObj>::iterator it = oldLists[i].begin();
        while(it != oldLists[i].end())
            insert(*it++);
    }
}

/****************************************************************
*   函式名稱:myhash(const HashedObj & key)
*   功能描述: 根據鍵值求個hash值 
*   引數列表: key 資料項的鍵值 
*   返回結果:返回hash值
*****************************************************************/
template<typename HashedObj>
int HashTable<HashedObj>::myhash(const HashedObj & key) const
{
    int hashVal = hash(key);
    
    hashVal %= theLists.size();

    if(hashVal < 0)
        hashVal += theLists.size();

    return hashVal;
}




int main()
{
    Employee e1("linux", 101.00, 1);
    Employee e2("ever", 102.00, 2);
    Employee e3("peter", 103.00, 3);
    Employee e4("may", 104.00, 4);
    Employee e5("usa", 105.00, 5);
    Employee e6("sal", 106.00, 6);
    Employee e7("usa", 107.00, 7);//和上面的值重複,所以這個值會被忽略
    Employee e8("jan", 108.00, 8);
    Employee e9("kro", 109.00, 9);
    Employee e10("bei", 110.00, 10);
    
    Employee e12("bbb", 110.00, 10);

    vector<Employee> v;
    v.push_back(e1);
    v.push_back(e2);
    v.push_back(e3);
    v.push_back(e4);
    v.push_back(e5);
    v.push_back(e6);
    v.push_back(e7);
    v.push_back(e8);
    v.push_back(e9);
    v.push_back(e10);

    cout << "v: " << endl;
    for(unsigned i = 0; i < v.size(); ++i)
        cout << v[i];
    cout << endl;

    HashTable<Employee> hashTable;
    
    for(unsigned i = 0; i < v.size(); ++i)
        hashTable.insert(v[i]);

    hashTable.print();

    cout << endl;
    //測試查詢函式findElement
    cout << "測試查詢函式findElement: " << endl;
    Employee e11 = hashTable.findElement(e12);
    cout << "e11 = " << e11;
    Employee e13 = hashTable.findElement(e10);
    cout << "e13 = " << e13;
    cout << endl;

    cout << "測試包含函式containes: " << endl; 
    if(hashTable.containes(e10))
        cout << "containe e10" << endl;
    else 
        cout << "not containe e10" << endl;

    if(hashTable.containes(e11))
        cout << "containe e11" << endl;
    else 
        cout << "not containe e11" << endl;
        
    cout << "測試remove():" << endl;
    hashTable.remove(e10);
    if(hashTable.containes(e10))
        cout << "containe e10" << endl;
    else 
        cout << "not containe e10" << endl;
    cout << endl;

    cout << "測試isEmpty(): " << endl;
    if(hashTable.isEmpty())
        cout << "hashTable is Empty " << endl;
    else
        cout << "hashTable is not Empty " << endl;
    cout << endl;

    cout << "測試makeEmpty(): " << endl;
    hashTable.makeEmpty();
    if(hashTable.isEmpty())
        cout << "hashTable is Empty " << endl << endl;
    else
        cout << "hashTable is not Empty " << endl;
    cout << endl;


    return 0;
}

程式輸出結果:

v: 
name: linux,    salary: 101,    seniority: 1
name: ever,     salary: 102,    seniority: 2
name: peter,    salary: 103,    seniority: 3
name: may,      salary: 104,    seniority: 4
name: usa,      salary: 105,    seniority: 5
name: sal,      salary: 106,    seniority: 6
name: usa,      salary: 107,    seniority: 7
name: jan,      salary: 108,    seniority: 8
name: kro,      salary: 109,    seniority: 9
name: bei,      salary: 110,    seniority: 10

輸出雜湊表中的內容: 
0: 
name: peter,    salary: 103,    seniority: 3
1: 
2: 
name: kro,      salary: 109,    seniority: 9
3: 
4: 
name: ever,     salary: 102,    seniority: 2
name: sal,      salary: 106,    seniority: 6
5: 
name: jan,      salary: 108,    seniority: 8
6: 
7: 
8: 
9: 
name: linux,    salary: 101,    seniority: 1
name: may,      salary: 104,    seniority: 4
name: usa,      salary: 105,    seniority: 5
name: bei,      salary: 110,    seniority: 10
10: 

測試查詢函式findElement: 
e11 = name: ,   salary: 0,      seniority: 0
e13 = name: bei,        salary: 110,    seniority: 10

測試包含函式containes: 
containe e10
not containe e11
測試remove():
not containe e10

測試isEmpty(): 
hashTable is not Empty 

測試makeEmpty(): 
hashTable is Empty 

相關文章