注意:
本文中的所有代碼你可以在這裏
https://github.com/qeesung/algorithm/tree/master/chapter11/11-2/hashTable(這裏會及時更新)
或者這裏
http://download.csdn.net/detail/ii1245712564/8804203
找到
散列表之鏈接法
在之前的文章《散列表之直接尋址表》中介紹了散列表中一種最基本的類型,直接尋址表。直接尋址表是通過鍵值直接來尋找在在數組裏面的映射位置,但是直接尋址表存在一個缺陷,在鍵值範圍較大且元素個數不多的時候,空間利用率不高,比如現在我們只需要映射兩個元素
於是,針對上面的情況,我們想到了下面的解決方案
通過散列函數
散列表的定義
在直接尋址的方式下,具有關鍵字hash(hash function)
傳入鍵值
假設我們有一集合
這裏的散列表大小
散列表的基本操作
散列表的基本操作有:
- INSERT:插入一個元素
- DELETE:刪除一個元素
- SEARCH:搜索一個元素
在我們進行編碼實現之前,先來考慮一下下面幾個問題
問題1:散列函數
hash 該怎麼設計?
回答1:散列函數的設計關乎到整個散列表的運行效率,所以不可等閒待之,需要針對輸入的元素集合來設計合適的散列函數,這也是散列表中最大的難點,比如在之前的例子中,我們設計的散列函數很簡單,如果輸入的key是1,就返回0,如果輸入的key是1000,那麼就返回1.如果把這個集合運用到其他集合上,那麼就是一個很差勁的散列函數
問題2:因爲集合
U 的大小|U| 遠大於散列表的大小m ,所以經常會發生不同鍵值卻有相同散列值的情況,於是散列表的同一個位置就會發生衝突,這時應該怎麼辦?
回答2:我們可以採用鏈表的形式,將散列值相同的元素放入同一鏈表裏面,再將列表掛在散列表的對應位置上。
散列表的編碼實現
下面是用到的文件,我們只列出部分的文件的內容,你可以在這裏獲得全部內容:
https://github.com/qeesung/algorithm/tree/master/chapter11/11-2/hashTable
.
├── hash_table.cc 散列表源文件
├── hash_table.h 散列表頭文件
├── hash_table_test.cc 散列表測試源文件
├── hash_table_test.h 散列表測試頭文件
├── link_list.cc 鏈式鏈表源文件
├── link_list.h 鏈式鏈表頭文件
├── list.h 鏈表接口文件
├── main.cc 主測試文件
├── Makefile
└── README.md
0 directories, 10 files
散列表的設計
hash_table.h
#ifndef HASHTABLE_INC
#define HASHTABLE_INC
#include "link_list.h"
#include <vector>
/**
* Class: LinkHashTable
* Description: 鏈接法散列表
*/
template < class T >
class LinkHashTable
{
public:
/** ==================== LIFECYCLE ======================================= */
LinkHashTable (int table_size=0 , int(*_HASH)(const T &)=0); /** constructor */
/** ==================== MUTATORS ======================================= */
bool hash_insert(const T &); /*插入操作*/
bool hash_delete(const T &); /*刪除操作*/
std::vector<T> hash_search(int) const; /*查找操作*/
void clear();
void printToVec(std::vector<std::vector<T> > & );
private:
/** ==================== DATA MEMBERS ======================================= */
int (*HASH)(const T &);// 定義一個散列函數指針
LinkList<T> * array; //鏈表的數組
const size_t table_size;// 散列表的大小
}; /** ---------- end of template class LinkHashTable ---------- */
#include "hash_table.cc"
#endif /* ----- #ifndef HASHTABLE_INC ----- */
散列表的成員中有一個散列函數的指針
HASH
,用戶在構造散列表的時候需要制定對應的散列函數,還有一個array
指針,是用來指向散列表的起始地址的。
hash_table.cc
#include "link_list.h"
/**
* Class: LinkHashTable
* Method: LinkHashTable
* Description:
*/
template < class T >
LinkHashTable < T >::LinkHashTable (int _table_size , int(*_HASH)(const T &)) :\
table_size(_table_size),HASH(_HASH)
{
array = new LinkList<T>[table_size]();
} /** ---------- end of constructor of template class LinkHashTable ---------- */
template < class T >
bool LinkHashTable<T>::hash_insert (const T & ele)
{
int index = HASH(ele);
if(index >= table_size || index <0)
return false;
return array[index].insert(ele);
} /** ----- end of method LinkHashTable<T>::hash_insert ----- */
template < class T >
bool LinkHashTable<T>::hash_delete (const T & ele)
{
int index = HASH(ele);
if(index >= table_size || index <0)
return false;
return array[index].remove(ele);
} /** ----- end of method LinkHashTable<T>::hash_delete ----- */
template < class T >
std::vector<T> LinkHashTable<T>::hash_search (int k) const
{
if(k < 0 || k >= table_size)
return std::vector<T>();
std::vector<int> vec;
array[k].printToVec(vec);
return vec;
} /** ----- end of method LinkHashTable<T>::hash_search ----- */
template < class T >
void LinkHashTable<T>::clear ()
{
for(int i =0 ; i < table_size ; ++i)
{
array[i].clear();
}
return ;
} /** ----- end of method LinkHashTable<T>::clear ----- */
template < class T >
void LinkHashTable<T>::printToVec (std::vector<std::vector<T> > & vec)
{
for(int i =0 ; i < table_size ; ++i)
{
std::vector<int> tempVec;
array[i].printToVec(tempVec);
vec.push_back(tempVec);
}
return ;
} /** ----- end of method LinkHashTable<T>::printToVec ----- */
主測試文件
main.cc
#include <stdlib.h>
#include "hash_table_test.h"
#include <cppunit/ui/text/TestRunner.h>
#include <cppunit/TestCaller.h>
#include <cppunit/TestSuite.h>
/**
* +++ FUNCTION +++
* Name: main
* Description: 測試的主函數
*/
int main ( int argc, char *argv[] )
{
CppUnit::TextUi::TestRunner runner;
CppUnit::TestSuite * suite = new CppUnit::TestSuite();
suite->addTest(new CppUnit::TestCaller<HashTableTest>("Test insert by qeesung", &HashTableTest::test_insert));
suite->addTest(new CppUnit::TestCaller<HashTableTest>("Test delete by qeesung", &HashTableTest::test_delete));
suite->addTest(new CppUnit::TestCaller<HashTableTest>("Test search by qeesung", &HashTableTest::test_search));
runner.addTest(suite);
runner.run("",true);
return EXIT_SUCCESS;
return EXIT_SUCCESS;
} /** ---------- end of function main ---------- */
編譯運行
編譯方法
- 如果你安裝了
cppunit
make
- 如果沒有安裝
cppunit
,你需要這樣編譯
g++ hash_table_test.cc youSourceFile.cc -o target
運行方法
./hashTest(or your target)
結論
散列表最重要的是設計一個性能良好的散列函數(hash function)
,這也是散列表的難點,如果散列函數設計差勁的話,有可能造成數據分佈的不平衡,刪除和查找操作性能變差。我們考慮最差勁的散列函數,就是將所有的鍵值都映射爲同一個散列值,那麼對散列表的查找和刪除操作的運行時間將和操作一個鏈式鏈表一樣,都爲