【算法导论】散列表
散列表是普通数组的扩展,它是一种支持字典操作的动态集合。
直接寻址散列表:利用普通数组可以直接寻址,使得能在内时间内访问数组中的任意位置。
链接法散列表:为了解决两个相同的关键字映射到相同的一个槽中,用链接法解决这个冲突。其思路就是将相同关键字值的节点组成一个链表,每个相同的值插到链表的结尾处。
template<class _key> class cHashTable { public: cHashTable(int slotNum=0, int (*hashFunction)(_key)){ this.slotNum=slotNum; this.hashFunction=hashFunction; hashTableChain=new List<_key>*[slotNum]; for (int i = 0; i < slotNum; ++i) { hashTableChain[i]=NULL; /* code */ } }; ~cHashTable(){ delete[] hashFunction; delete[] hashTableChain; }; /* data */ private: int slotNum; int (*hashFunction)(_key); List** hashTableChain; public: void chainedHashInsert(_key x); Node<_key>* chainedHashSearch(_key x); bool chainedHashDelete(_key x); }; void cHashTable::chainedHashInsert(_key x){ int key=hashFunction(x); if(hashTableChain[key]!=NULL){ Node* cur=hashTableChain[key]->next; while(cur!=NULL){ cur=cur->next; } Node* newNode=new Node(x); hashTableChain[key]=new List<_key>*(); hashTableChain[key].insert(newNode); } else{ Node* newNode=new Node(x); hashTableChain[key].insert(newNode); } } bool cHashTable::chainedHashDelete(_key x){ int key=hashFunction(x); if(hashTableChain[key]==NULL){ return false; }else{ Node* dNode=hashTableChain[key].search(x); hashTableChain[key].delete(dNode); } } Node<_key>* cHashTable::chainedHashSearch(Node* x){ int key=hashFunction(x->value); if(hashTableChain[key]==NULL){ return NULL; }else{ Node* searchResult=hashTableChain[key].search(x->value); return searchResult; } }
散列函数:好的散列函数可以将关键字尽可能的散列到插槽位置中的任一个,并与其它关键字散列到哪个插槽无关。比如可以将关键字转换成自然数,在ASCII字符集中,p=112,t=116,如果以128为基数,那么字符串p就可以表示为112*128+116.
除法散列法:
,m常常选择一个不太接近2的整数幂的素数,这样可以避免无效的散列。
乘法散列法:
,m可以选择2的某个幂次,k为某个小数。
全域散列法:是为了避免恶意的操作使得关键字全部散列到同一个槽中发生,采取随机地选择散列函数,使之独立于关键字的方法。全域散列是随机地选择一个hash函数,该hash函数被选择之后,对于本次应用的所有操作,hash函数都将都将不再改变。
开放寻址法
template <typename K> class cOpenAdressHash { public: cOpenAdressHash(int hashSize, int(* hashFunction)(K x,int i)){ this.hashSize=hashSize; this.hashFunction=hashFunction; hashData=new int(hashSize); }; ~cOpenAdressHash(){ delete[] hashFunction; }; public: bool hashInsert(K x); int hashSearch(K x); bool hashDelete(int index); /* data */ private: int hashSize; int (* hashFunction)(K x,int i); K* hashData; K hasDeleted; }; template <typename K> bool cOpenAdressHash::hashInsert(k x){ int i=0; int key=hashFunction(x,i); while(i<hashSize&&hashData[key]==NULL&&hashData[key]!=hasDeleted){ i=i+1; key=hashFunction(x,i); } if(i<hashSize){ hashSize&&hashData[key]=x; return true; } else return false; } template<typename K> hashSearch(K x){ int i=0; int key=hashFunction(x,i); while(i<hashSize&&hashData[key]!=x){ i=i+1; key=hashFunction(x,i); } if(i<hashSize){ return key; } else return false; } template <typename K>hashDelete(int index){ if(index<0||index>hashSize) return false; if(hashData[index]==NULL||hashData[index]==hasDeleted) return false; hasDeleted[index]=hasDeleted; }
在开放寻址中有一个重要的概念就是“探寻”,探寻就是沿着散列表前进步长的大小,可以是线性探寻、二次探寻、双重探寻。
线性探寻:容易实现,但存在着依次群集的问题,随着占用槽的增长,平均的查找时间也在增长.
二次探寻:
双重探寻:
完全散列:就是采用两级散列,每级上都采用全域散列.